From 30ce346f343ca61f921ec7d6faf2f06311c04e71 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 5 Apr 2020 18:10:56 +0200 Subject: [PATCH 01/72] Enable nullabe reference types for MediaBrowser.Model --- Emby.Dlna/Ssdp/DeviceDiscovery.cs | 16 ++---- .../ServerConfigurationManager.cs | 2 +- .../Cryptography/CryptographyProvider.cs | 4 +- .../Devices/DeviceManager.cs | 16 ++---- .../EntryPoints/UdpServerEntryPoint.cs | 3 +- .../Library/MediaSourceManager.cs | 6 +- .../Library/UserManager.cs | 10 ++-- .../LiveTv/EmbyTV/TimerManager.cs | 4 +- .../LiveTv/LiveTvManager.cs | 57 +++++-------------- .../Playlists/PlaylistManager.cs | 5 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 11 ++-- .../ScheduledTasks/TaskManager.cs | 11 +--- .../Updates/InstallationManager.cs | 2 +- MediaBrowser.Api/EnvironmentService.cs | 15 +---- MediaBrowser.Api/Images/RemoteImageService.cs | 3 +- .../LiveTv/TimerEventInfo.cs | 13 ++++- .../Activity/ActivityLogEntry.cs | 1 + .../ApiClient/ServerDiscoveryInfo.cs | 1 + .../Branding/BrandingOptions.cs | 1 + .../Channels/ChannelFeatures.cs | 1 + MediaBrowser.Model/Channels/ChannelInfo.cs | 1 + MediaBrowser.Model/Channels/ChannelQuery.cs | 6 ++ .../BaseApplicationConfiguration.cs | 1 + .../Configuration/EncodingOptions.cs | 13 +++++ .../Configuration/LibraryOptions.cs | 1 + .../Configuration/MetadataOptions.cs | 4 ++ .../Configuration/MetadataPlugin.cs | 1 + .../Configuration/MetadataPluginSummary.cs | 1 + .../Configuration/ServerConfiguration.cs | 1 + .../Configuration/UserConfiguration.cs | 1 + .../Configuration/XbmcMetadataOptions.cs | 1 + .../Cryptography/ICryptoProvider.cs | 2 +- .../Devices/ContentUploadHistory.cs | 6 +- MediaBrowser.Model/Devices/DeviceInfo.cs | 1 + MediaBrowser.Model/Devices/DevicesOptions.cs | 3 + MediaBrowser.Model/Devices/LocalFileInfo.cs | 4 ++ MediaBrowser.Model/Diagnostics/IProcess.cs | 8 +++ .../Diagnostics/IProcessFactory.cs | 11 ++++ MediaBrowser.Model/Dlna/AudioOptions.cs | 6 ++ MediaBrowser.Model/Dlna/CodecProfile.cs | 1 + MediaBrowser.Model/Dlna/ConditionProcessor.cs | 36 ++---------- MediaBrowser.Model/Dlna/ContainerProfile.cs | 4 +- .../Dlna/ContentFeatureBuilder.cs | 6 +- .../Dlna/DeviceIdentification.cs | 1 + MediaBrowser.Model/Dlna/DeviceProfile.cs | 1 + MediaBrowser.Model/Dlna/DeviceProfileInfo.cs | 1 + MediaBrowser.Model/Dlna/DirectPlayProfile.cs | 1 + MediaBrowser.Model/Dlna/HttpHeaderInfo.cs | 1 + MediaBrowser.Model/Dlna/ITranscoderSupport.cs | 5 ++ .../Dlna/MediaFormatProfileResolver.cs | 24 ++++---- MediaBrowser.Model/Dlna/ProfileCondition.cs | 25 ++++---- .../Dlna/ResolutionConfiguration.cs | 1 + .../Dlna/ResolutionNormalizer.cs | 5 +- MediaBrowser.Model/Dlna/ResolutionOptions.cs | 1 + MediaBrowser.Model/Dlna/ResponseProfile.cs | 4 +- MediaBrowser.Model/Dlna/SearchCriteria.cs | 9 ++- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 + MediaBrowser.Model/Dlna/StreamInfo.cs | 1 + MediaBrowser.Model/Dlna/SubtitleProfile.cs | 1 + MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs | 9 +++ MediaBrowser.Model/Dlna/TranscodingProfile.cs | 1 + MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs | 4 ++ MediaBrowser.Model/Dlna/VideoOptions.cs | 1 + MediaBrowser.Model/Dlna/XmlAttribute.cs | 1 + MediaBrowser.Model/Drawing/DrawingUtils.cs | 5 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 1 + MediaBrowser.Model/Dto/BaseItemPerson.cs | 1 + MediaBrowser.Model/Dto/IHasServerId.cs | 1 + MediaBrowser.Model/Dto/ImageByNameInfo.cs | 1 + MediaBrowser.Model/Dto/ImageInfo.cs | 5 +- MediaBrowser.Model/Dto/ImageOptions.cs | 17 +++--- MediaBrowser.Model/Dto/ItemIndex.cs | 20 ------- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 1 + MediaBrowser.Model/Dto/MetadataEditorInfo.cs | 1 + MediaBrowser.Model/Dto/NameIdPair.cs | 1 + MediaBrowser.Model/Dto/NameValuePair.cs | 2 +- MediaBrowser.Model/Dto/RecommendationDto.cs | 1 + MediaBrowser.Model/Dto/UserDto.cs | 1 + MediaBrowser.Model/Dto/UserItemDataDto.cs | 1 + MediaBrowser.Model/Entities/ChapterInfo.cs | 1 + .../Entities/DisplayPreferences.cs | 1 + .../Entities/IHasProviderIds.cs | 2 +- .../Entities/LibraryUpdateInfo.cs | 29 +++++----- .../Entities/MediaAttachment.cs | 1 + MediaBrowser.Model/Entities/MediaStream.cs | 1 + MediaBrowser.Model/Entities/MediaUrl.cs | 1 + .../Entities/PackageReviewInfo.cs | 13 +++-- MediaBrowser.Model/Entities/ParentalRating.cs | 24 ++++---- .../Entities/ProviderIdsExtensions.cs | 12 ++-- .../Entities/VirtualFolderInfo.cs | 1 + MediaBrowser.Model/Events/GenericEventArgs.cs | 7 --- MediaBrowser.Model/Extensions/ListHelper.cs | 2 + MediaBrowser.Model/Extensions/StringHelper.cs | 4 +- .../Globalization/CountryInfo.cs | 1 + .../Globalization/CultureDto.cs | 1 + .../Globalization/ILocalizationManager.cs | 1 + .../Globalization/LocalizationOption.cs | 2 + MediaBrowser.Model/IO/FileSystemEntryInfo.cs | 27 ++++++--- MediaBrowser.Model/IO/FileSystemMetadata.cs | 1 + MediaBrowser.Model/IO/IFileSystem.cs | 1 + MediaBrowser.Model/IO/IIsoManager.cs | 1 - MediaBrowser.Model/IO/IIsoMount.cs | 2 +- MediaBrowser.Model/IO/IIsoMounter.cs | 12 ++-- MediaBrowser.Model/IO/IStreamHelper.cs | 1 + MediaBrowser.Model/Library/UserViewQuery.cs | 12 ++-- MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs | 1 + MediaBrowser.Model/LiveTv/GuideInfo.cs | 1 + .../LiveTv/LiveTvChannelQuery.cs | 1 + MediaBrowser.Model/LiveTv/LiveTvInfo.cs | 12 ++-- MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 1 + .../LiveTv/LiveTvServiceInfo.cs | 1 + MediaBrowser.Model/LiveTv/RecordingQuery.cs | 1 + .../LiveTv/SeriesTimerInfoDto.cs | 3 +- MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs | 2 +- MediaBrowser.Model/LiveTv/TimerInfoDto.cs | 1 + MediaBrowser.Model/LiveTv/TimerQuery.cs | 1 + MediaBrowser.Model/MediaBrowser.Model.csproj | 2 + MediaBrowser.Model/MediaInfo/AudioCodec.cs | 4 +- .../MediaInfo/BlurayDiscInfo.cs | 1 + .../MediaInfo/LiveStreamRequest.cs | 18 +++--- .../MediaInfo/LiveStreamResponse.cs | 7 ++- MediaBrowser.Model/MediaInfo/MediaInfo.cs | 1 + .../MediaInfo/PlaybackInfoRequest.cs | 1 + .../MediaInfo/PlaybackInfoResponse.cs | 2 +- .../MediaInfo/SubtitleTrackEvent.cs | 4 ++ MediaBrowser.Model/Net/EndPointInfo.cs | 1 + MediaBrowser.Model/Net/ISocket.cs | 1 + MediaBrowser.Model/Net/MimeTypes.cs | 18 +++--- MediaBrowser.Model/Net/NetworkShare.cs | 1 + MediaBrowser.Model/Net/SocketReceiveResult.cs | 10 +++- MediaBrowser.Model/Net/WebSocketMessage.cs | 1 + .../Notifications/NotificationOption.cs | 16 +++--- .../Notifications/NotificationOptions.cs | 37 ++++++------ .../Notifications/NotificationRequest.cs | 1 + .../Notifications/NotificationTypeInfo.cs | 1 + .../Playlists/PlaylistCreationRequest.cs | 1 + .../Playlists/PlaylistCreationResult.cs | 7 ++- .../Playlists/PlaylistItemQuery.cs | 39 ------------- MediaBrowser.Model/Plugins/PluginInfo.cs | 1 + MediaBrowser.Model/Plugins/PluginPageInfo.cs | 1 + .../Providers/ExternalIdInfo.cs | 1 + MediaBrowser.Model/Providers/ExternalUrl.cs | 1 + .../Providers/ImageProviderInfo.cs | 11 ++-- .../Providers/RemoteImageInfo.cs | 1 + .../Providers/RemoteImageQuery.cs | 7 ++- .../Providers/RemoteImageResult.cs | 1 + .../Providers/RemoteSearchResult.cs | 20 +++++-- .../Providers/RemoteSubtitleInfo.cs | 1 + .../Providers/SubtitleOptions.cs | 1 + .../Providers/SubtitleProviderInfo.cs | 1 + .../Querying/AllThemeMediaResult.cs | 13 +++-- MediaBrowser.Model/Querying/EpisodeQuery.cs | 1 + .../Querying/ItemCountsQuery.cs | 20 ------- MediaBrowser.Model/Querying/ItemSortBy.cs | 54 +++++++++++++----- .../Querying/LatestItemsQuery.cs | 20 ++++--- .../Querying/MovieRecommendationQuery.cs | 1 + MediaBrowser.Model/Querying/NextUpQuery.cs | 1 + MediaBrowser.Model/Querying/QueryFilters.cs | 1 + MediaBrowser.Model/Querying/QueryResult.cs | 1 + .../Querying/ThemeMediaResult.cs | 2 +- .../Querying/UpcomingEpisodesQuery.cs | 1 + MediaBrowser.Model/Search/SearchHint.cs | 1 + MediaBrowser.Model/Search/SearchHintResult.cs | 1 + MediaBrowser.Model/Search/SearchQuery.cs | 1 + .../Serialization/IJsonSerializer.cs | 1 + .../Serialization/IXmlSerializer.cs | 1 + .../Services/ApiMemberAttribute.cs | 1 + .../Services/IHasRequestFilter.cs | 10 ++-- MediaBrowser.Model/Services/IHttpRequest.cs | 4 +- MediaBrowser.Model/Services/IHttpResult.cs | 11 ++-- MediaBrowser.Model/Services/IRequest.cs | 1 + .../Services/QueryParamCollection.cs | 6 +- MediaBrowser.Model/Services/RouteAttribute.cs | 1 + MediaBrowser.Model/Session/BrowseRequest.cs | 1 + .../Session/ClientCapabilities.cs | 1 + MediaBrowser.Model/Session/GeneralCommand.cs | 1 + MediaBrowser.Model/Session/MessageCommand.cs | 1 + MediaBrowser.Model/Session/PlayRequest.cs | 1 + .../Session/PlaybackProgressInfo.cs | 1 + .../Session/PlaybackStopInfo.cs | 1 + MediaBrowser.Model/Session/PlayerStateInfo.cs | 1 + .../Session/PlaystateRequest.cs | 2 +- MediaBrowser.Model/Session/SessionUserInfo.cs | 2 + MediaBrowser.Model/Session/TranscodingInfo.cs | 5 +- .../Session/UserDataChangeInfo.cs | 1 + MediaBrowser.Model/Sync/SyncJob.cs | 1 + MediaBrowser.Model/Sync/SyncTarget.cs | 1 + MediaBrowser.Model/System/LogFile.cs | 1 + MediaBrowser.Model/System/PublicSystemInfo.cs | 1 + MediaBrowser.Model/System/SystemInfo.cs | 2 +- MediaBrowser.Model/System/WakeOnLanInfo.cs | 31 +++++----- MediaBrowser.Model/Tasks/IScheduledTask.cs | 11 ++-- .../Tasks/IScheduledTaskWorker.cs | 1 + .../Tasks/ScheduledTaskHelpers.cs | 4 +- .../Tasks/TaskCompletionEventArgs.cs | 10 +++- MediaBrowser.Model/Tasks/TaskInfo.cs | 1 + MediaBrowser.Model/Tasks/TaskResult.cs | 1 + MediaBrowser.Model/Tasks/TaskTriggerInfo.cs | 1 + .../Updates/CheckForUpdateResult.cs | 1 + .../Updates/InstallationInfo.cs | 1 + MediaBrowser.Model/Updates/PackageInfo.cs | 1 + .../Updates/PackageVersionInfo.cs | 1 + .../Users/ForgotPasswordResult.cs | 1 + MediaBrowser.Model/Users/PinRedeemResult.cs | 1 + MediaBrowser.Model/Users/UserAction.cs | 1 + MediaBrowser.Model/Users/UserPolicy.cs | 1 + .../Manager/ItemImageProvider.cs | 24 +++++--- .../Manager/ProviderManager.cs | 6 +- 208 files changed, 636 insertions(+), 506 deletions(-) delete mode 100644 MediaBrowser.Model/Dto/ItemIndex.cs delete mode 100644 MediaBrowser.Model/Playlists/PlaylistItemQuery.cs delete mode 100644 MediaBrowser.Model/Querying/ItemCountsQuery.cs diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index f95b8ce7de..ab5e56ab07 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -100,15 +100,13 @@ namespace Emby.Dlna.Ssdp var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - var args = new GenericEventArgs - { - Argument = new UpnpDeviceInfo + var args = new GenericEventArgs( + new UpnpDeviceInfo { Location = e.DiscoveredDevice.DescriptionLocation, Headers = headers, LocalIpAddress = e.LocalIpAddress - } - }; + }); DeviceDiscoveredInternal?.Invoke(this, args); } @@ -121,14 +119,12 @@ namespace Emby.Dlna.Ssdp var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - var args = new GenericEventArgs - { - Argument = new UpnpDeviceInfo + var args = new GenericEventArgs( + new UpnpDeviceInfo { Location = e.DiscoveredDevice.DescriptionLocation, Headers = headers - } - }; + }); DeviceLeft?.Invoke(this, args); } diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index f407317ec7..5062683a17 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.Configuration ValidateMetadataPath(newConfig); ValidateSslCertificate(newConfig); - ConfigurationUpdating?.Invoke(this, new GenericEventArgs { Argument = newConfig }); + ConfigurationUpdating?.Invoke(this, new GenericEventArgs(newConfig)); base.ReplaceConfiguration(newConfiguration); } diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index de83b023d7..1e42dbf675 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Collections.Generic; using System.Security.Cryptography; @@ -134,8 +136,6 @@ namespace Emby.Server.Implementations.Cryptography _randomNumberGenerator.Dispose(); } - _randomNumberGenerator = null; - _disposed = true; } } diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index adb8e793d2..e8837892cc 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -86,13 +86,7 @@ namespace Emby.Server.Implementations.Devices { _authRepo.UpdateDeviceOptions(deviceId, options); - if (DeviceOptionsUpdated != null) - { - DeviceOptionsUpdated(this, new GenericEventArgs>() - { - Argument = new Tuple(deviceId, options) - }); - } + DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs>(new Tuple(deviceId, options))); } public DeviceOptions GetDeviceOptions(string deviceId) @@ -251,14 +245,12 @@ namespace Emby.Server.Implementations.Devices if (CameraImageUploaded != null) { - CameraImageUploaded?.Invoke(this, new GenericEventArgs - { - Argument = new CameraImageUploadInfo + CameraImageUploaded?.Invoke(this, new GenericEventArgs( + new CameraImageUploadInfo { Device = device, FileInfo = file - } - }); + })); } } diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 50ba0f8fac..fa566d24b6 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -44,10 +44,11 @@ namespace Emby.Server.Implementations.EntryPoints } /// - public async Task RunAsync() + public Task RunAsync() { _udpServer = new UdpServer(_logger, _appHost); _udpServer.Start(PortNumber, _cancellationTokenSource.Token); + return Task.CompletedTask; } /// diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 70d5bd9f4e..4f12ad0467 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -521,11 +521,7 @@ namespace Emby.Server.Implementations.Library SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); } - return new Tuple(new LiveStreamResponse - { - MediaSource = clone - - }, liveStream as IDirectStreamProvider); + return new Tuple(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider); } private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio) diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 7b17cc913f..614ab5669e 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.Library /// The user. private void OnUserUpdated(User user) { - UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + UserUpdated?.Invoke(this, new GenericEventArgs(user)); } /// @@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Library /// The user. private void OnUserDeleted(User user) { - UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); + UserDeleted?.Invoke(this, new GenericEventArgs(user)); } public NameIdPair[] GetAuthenticationProviders() @@ -755,7 +755,7 @@ namespace Emby.Server.Implementations.Library _userRepository.CreateUser(user); - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs(user), _logger); return user; } @@ -980,7 +980,7 @@ namespace Emby.Server.Implementations.Library if (fireEvent) { - UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + UserPolicyUpdated?.Invoke(this, new GenericEventArgs(user)); } } @@ -1050,7 +1050,7 @@ namespace Emby.Server.Implementations.Library if (fireEvent) { - UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + UserConfigurationUpdated?.Invoke(this, new GenericEventArgs(user)); } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 7ebb043d8e..285a59a249 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (startDate < now) { - TimerFired?.Invoke(this, new GenericEventArgs { Argument = item }); + TimerFired?.Invoke(this, new GenericEventArgs(item)); return; } @@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase)); if (timer != null) { - TimerFired?.Invoke(this, new GenericEventArgs { Argument = timer }); + TimerFired?.Invoke(this, new GenericEventArgs(timer)); } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index b64fe8634c..16c659532a 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -149,27 +149,18 @@ namespace Emby.Server.Implementations.LiveTv { var timerId = e.Argument; - TimerCancelled?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo - { - Id = timerId - } - }); + TimerCancelled?.Invoke(this, new GenericEventArgs(new TimerEventInfo(timerId))); } private void OnEmbyTvTimerCreated(object sender, GenericEventArgs e) { var timer = e.Argument; - TimerCreated?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo + TimerCreated?.Invoke(this, new GenericEventArgs( + new TimerEventInfo(timer.Id) { - ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId), - Id = timer.Id - } - }); + ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId) + })); } public List GetTunerHostTypes() @@ -1725,13 +1716,7 @@ namespace Emby.Server.Implementations.LiveTv if (!(service is EmbyTV.EmbyTV)) { - TimerCancelled?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo - { - Id = id - } - }); + TimerCancelled?.Invoke(this, new GenericEventArgs(new TimerEventInfo(id))); } } @@ -1748,13 +1733,7 @@ namespace Emby.Server.Implementations.LiveTv await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false); - SeriesTimerCancelled?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo - { - Id = id - } - }); + SeriesTimerCancelled?.Invoke(this, new GenericEventArgs(new TimerEventInfo(id))); } public async Task GetTimer(string id, CancellationToken cancellationToken) @@ -2073,14 +2052,11 @@ namespace Emby.Server.Implementations.LiveTv if (!(service is EmbyTV.EmbyTV)) { - TimerCreated?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo + TimerCreated?.Invoke(this, new GenericEventArgs( + new TimerEventInfo(newTimerId) { - ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId), - Id = newTimerId - } - }); + ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId) + })); } } @@ -2105,14 +2081,11 @@ namespace Emby.Server.Implementations.LiveTv await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false); } - SeriesTimerCreated?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo + SeriesTimerCreated?.Invoke(this, new GenericEventArgs( + new TimerEventInfo(newTimerId) { - ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId), - Id = newTimerId - } - }); + ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId) + })); } public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 9b1510ac97..021bc47cda 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -153,10 +153,7 @@ namespace Emby.Server.Implementations.Playlists }); } - return new PlaylistCreationResult - { - Id = playlist.Id.ToString("N", CultureInfo.InvariantCulture) - }; + return new PlaylistCreationResult(playlist.Id.ToString("N", CultureInfo.InvariantCulture)); } finally { diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 5b188d9626..ca983764b2 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -392,7 +392,7 @@ namespace Emby.Server.Implementations.ScheduledTasks ((TaskManager)TaskManager).OnTaskExecuting(this); - progress.ProgressChanged += progress_ProgressChanged; + progress.ProgressChanged += OnProgressChanged; TaskCompletionStatus status; CurrentExecutionStartTime = DateTime.UtcNow; @@ -426,7 +426,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var startTime = CurrentExecutionStartTime; var endTime = DateTime.UtcNow; - progress.ProgressChanged -= progress_ProgressChanged; + progress.ProgressChanged -= OnProgressChanged; CurrentCancellationTokenSource.Dispose(); CurrentCancellationTokenSource = null; CurrentProgress = null; @@ -439,16 +439,13 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The sender. /// The e. - void progress_ProgressChanged(object sender, double e) + private void OnProgressChanged(object sender, double e) { e = Math.Min(e, 100); CurrentProgress = e; - TaskProgress?.Invoke(this, new GenericEventArgs - { - Argument = e - }); + TaskProgress?.Invoke(this, new GenericEventArgs(e)); } /// diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index ecf58dbc0e..f2e04d1fb4 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -254,10 +254,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The task. internal void OnTaskExecuting(IScheduledTaskWorker task) { - TaskExecuting?.Invoke(this, new GenericEventArgs - { - Argument = task - }); + TaskExecuting?.Invoke(this, new GenericEventArgs(task)); } /// @@ -267,11 +264,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The result. internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result) { - TaskCompleted?.Invoke(task, new TaskCompletionEventArgs - { - Result = result, - Task = task - }); + TaskCompleted?.Invoke(task, new TaskCompletionEventArgs(task, result)); ExecuteQueuedTasks(); } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index c897036eb8..51563fd5d4 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -427,7 +427,7 @@ namespace Emby.Server.Implementations.Updates _config.SaveConfiguration(); } - PluginUninstalled?.Invoke(this, new GenericEventArgs { Argument = plugin }); + PluginUninstalled?.Invoke(this, new GenericEventArgs(plugin)); _applicationHost.NotifyPendingRestart(); } diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 36b03f09ce..10726e2aaf 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -226,12 +226,7 @@ namespace MediaBrowser.Api /// IEnumerable{FileSystemEntryInfo}. private IEnumerable GetDrives() { - return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo - { - Name = d.Name, - Path = d.FullName, - Type = FileSystemEntryType.Directory - }); + return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo(d.Name, d.FullName, FileSystemEntryType.Directory)); } /// @@ -266,13 +261,7 @@ namespace MediaBrowser.Api return true; }); - return entries.Select(f => new FileSystemEntryInfo - { - Name = f.Name, - Path = f.FullName, - Type = f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File - - }); + return entries.Select(f => new FileSystemEntryInfo(f.Name, f.FullName, f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File)); } public object Get(GetParentPath request) diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index f03f5efd8d..3e4198aae6 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -147,9 +147,8 @@ namespace MediaBrowser.Api.Images { var item = _libraryManager.GetItemById(request.Id); - var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery + var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery(request.ProviderName) { - ProviderName = request.ProviderName, IncludeAllLanguages = request.IncludeAllLanguages, IncludeDisabledProviders = true, ImageType = request.Type diff --git a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs index cfec39b4e9..1b8f41db69 100644 --- a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs @@ -1,10 +1,19 @@ +#nullable enable +#pragma warning disable CS1591 + using System; namespace MediaBrowser.Controller.LiveTv { public class TimerEventInfo { - public string Id { get; set; } - public Guid ProgramId { get; set; } + public TimerEventInfo(string id) + { + Id = id; + } + + public string Id { get; } + + public Guid? ProgramId { get; set; } } } diff --git a/MediaBrowser.Model/Activity/ActivityLogEntry.cs b/MediaBrowser.Model/Activity/ActivityLogEntry.cs index 80f01b66ee..865d07b2c4 100644 --- a/MediaBrowser.Model/Activity/ActivityLogEntry.cs +++ b/MediaBrowser.Model/Activity/ActivityLogEntry.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs b/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs index bb203f8958..fcc90a1f7a 100644 --- a/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs +++ b/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.ApiClient diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs index 8ab268a64d..5ddf1e7e6e 100644 --- a/MediaBrowser.Model/Branding/BrandingOptions.cs +++ b/MediaBrowser.Model/Branding/BrandingOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Branding diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs index c4e97ffe59..496102d83b 100644 --- a/MediaBrowser.Model/Channels/ChannelFeatures.cs +++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Channels/ChannelInfo.cs b/MediaBrowser.Model/Channels/ChannelInfo.cs index bfb34db559..f2432aaeb2 100644 --- a/MediaBrowser.Model/Channels/ChannelInfo.cs +++ b/MediaBrowser.Model/Channels/ChannelInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Channels diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs index 88fc94a6fc..d112600390 100644 --- a/MediaBrowser.Model/Channels/ChannelQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -13,8 +14,11 @@ namespace MediaBrowser.Model.Channels /// /// The fields. public ItemFields[] Fields { get; set; } + public bool? EnableImages { get; set; } + public int? ImageTypeLimit { get; set; } + public ImageType[] EnableImageTypes { get; set; } /// @@ -48,7 +52,9 @@ namespace MediaBrowser.Model.Channels /// /// null if [is favorite] contains no value, true if [is favorite]; otherwise, false. public bool? IsFavorite { get; set; } + public bool? IsRecordingsFolder { get; set; } + public bool RefreshLatestChannelItems { get; set; } } } diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs index cc2541f74f..cdd322c948 100644 --- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs +++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs @@ -1,3 +1,4 @@ +#nullable disable using System; using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 648568fd70..0c0e01f111 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration @@ -5,10 +6,15 @@ namespace MediaBrowser.Model.Configuration public class EncodingOptions { public int EncodingThreadCount { get; set; } + public string TranscodingTempPath { get; set; } + public double DownMixAudioBoost { get; set; } + public bool EnableThrottling { get; set; } + public int ThrottleDelaySeconds { get; set; } + public string HardwareAccelerationType { get; set; } /// @@ -20,12 +26,19 @@ namespace MediaBrowser.Model.Configuration /// The current FFmpeg path being used by the system and displayed on the transcode page. /// public string EncoderAppPathDisplay { get; set; } + public string VaapiDevice { get; set; } + public int H264Crf { get; set; } + public int H265Crf { get; set; } + public string EncoderPreset { get; set; } + public string DeinterlaceMethod { get; set; } + public bool EnableHardwareEncoding { get; set; } + public bool EnableSubtitleExtraction { get; set; } public string[] HardwareDecodingCodecs { get; set; } diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 4342ccd8ae..4229a4335b 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Configuration/MetadataOptions.cs b/MediaBrowser.Model/Configuration/MetadataOptions.cs index 625054b9e4..e7dc3da3cb 100644 --- a/MediaBrowser.Model/Configuration/MetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/MetadataOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -12,12 +13,15 @@ namespace MediaBrowser.Model.Configuration public string ItemType { get; set; } public string[] DisabledMetadataSavers { get; set; } + public string[] LocalMetadataReaderOrder { get; set; } public string[] DisabledMetadataFetchers { get; set; } + public string[] MetadataFetcherOrder { get; set; } public string[] DisabledImageFetchers { get; set; } + public string[] ImageFetcherOrder { get; set; } public MetadataOptions() diff --git a/MediaBrowser.Model/Configuration/MetadataPlugin.cs b/MediaBrowser.Model/Configuration/MetadataPlugin.cs index c2b47eb9bd..db8cd1875d 100644 --- a/MediaBrowser.Model/Configuration/MetadataPlugin.cs +++ b/MediaBrowser.Model/Configuration/MetadataPlugin.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration diff --git a/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs b/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs index 53063810b9..0c197ee021 100644 --- a/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs +++ b/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 3107ec2426..333805e316 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index a475c9910b..289047d6b4 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs index d6c1295f44..c48a381928 100644 --- a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index 656c04f463..d8b7d848a2 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Cryptography IEnumerable GetSupportedHashMethods(); - byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); + byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt); byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); diff --git a/MediaBrowser.Model/Devices/ContentUploadHistory.cs b/MediaBrowser.Model/Devices/ContentUploadHistory.cs index c493760d51..868956df2b 100644 --- a/MediaBrowser.Model/Devices/ContentUploadHistory.cs +++ b/MediaBrowser.Model/Devices/ContentUploadHistory.cs @@ -1,15 +1,19 @@ +#nullable disable #pragma warning disable CS1591 +using System; + namespace MediaBrowser.Model.Devices { public class ContentUploadHistory { public string DeviceId { get; set; } + public LocalFileInfo[] FilesUploaded { get; set; } public ContentUploadHistory() { - FilesUploaded = new LocalFileInfo[] { }; + FilesUploaded = Array.Empty(); } } } diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index d2563d1d0f..0cccf931c4 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Devices/DevicesOptions.cs b/MediaBrowser.Model/Devices/DevicesOptions.cs index 02570650e9..327b5836f7 100644 --- a/MediaBrowser.Model/Devices/DevicesOptions.cs +++ b/MediaBrowser.Model/Devices/DevicesOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -7,7 +8,9 @@ namespace MediaBrowser.Model.Devices public class DevicesOptions { public string[] EnabledCameraUploadDevices { get; set; } + public string CameraUploadPath { get; set; } + public bool EnableCameraUploadSubfolders { get; set; } public DevicesOptions() diff --git a/MediaBrowser.Model/Devices/LocalFileInfo.cs b/MediaBrowser.Model/Devices/LocalFileInfo.cs index 63a8dc2aa5..c3158b2f2e 100644 --- a/MediaBrowser.Model/Devices/LocalFileInfo.cs +++ b/MediaBrowser.Model/Devices/LocalFileInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Devices @@ -5,8 +6,11 @@ namespace MediaBrowser.Model.Devices public class LocalFileInfo { public string Name { get; set; } + public string Id { get; set; } + public string Album { get; set; } + public string MimeType { get; set; } } } diff --git a/MediaBrowser.Model/Diagnostics/IProcess.cs b/MediaBrowser.Model/Diagnostics/IProcess.cs index 514d1e7379..c067189a63 100644 --- a/MediaBrowser.Model/Diagnostics/IProcess.cs +++ b/MediaBrowser.Model/Diagnostics/IProcess.cs @@ -11,13 +11,21 @@ namespace MediaBrowser.Model.Diagnostics event EventHandler Exited; void Kill(); + bool WaitForExit(int timeMs); + Task 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 index 57082acc57..2d15aed7e6 100644 --- a/MediaBrowser.Model/Diagnostics/IProcessFactory.cs +++ b/MediaBrowser.Model/Diagnostics/IProcessFactory.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Diagnostics @@ -10,15 +11,25 @@ namespace MediaBrowser.Model.Diagnostics 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 40081b2824..fc555c5f70 100644 --- a/MediaBrowser.Model/Dlna/AudioOptions.cs +++ b/MediaBrowser.Model/Dlna/AudioOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -19,12 +20,17 @@ namespace MediaBrowser.Model.Dlna } public bool EnableDirectPlay { get; set; } + public bool EnableDirectStream { get; set; } + public bool ForceDirectPlay { get; set; } + public bool ForceDirectStream { get; set; } public Guid ItemId { get; set; } + public MediaSourceInfo[] MediaSources { get; set; } + public DeviceProfile Profile { get; set; } /// diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs index 756e500dd7..cc5b840c76 100644 --- a/MediaBrowser.Model/Dlna/CodecProfile.cs +++ b/MediaBrowser.Model/Dlna/CodecProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index 7423efaf65..f3aaef9307 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Dlna int? height, int? videoBitDepth, int? videoBitrate, - string videoProfile, + string? videoProfile, double? videoLevel, float? videoFramerate, int? packetLength, @@ -25,7 +25,7 @@ namespace MediaBrowser.Model.Dlna int? refFrames, int? numVideoStreams, int? numAudioStreams, - string videoCodecTag, + string? videoCodecTag, bool? isAvc) { switch (condition.Property) @@ -103,7 +103,7 @@ namespace MediaBrowser.Model.Dlna int? audioBitrate, int? audioSampleRate, int? audioBitDepth, - string audioProfile, + string? audioProfile, bool? isSecondaryTrack) { switch (condition.Property) @@ -154,7 +154,7 @@ namespace MediaBrowser.Model.Dlna return false; } - private static bool IsConditionSatisfied(ProfileCondition condition, string currentValue) + private static bool IsConditionSatisfied(ProfileCondition condition, string? currentValue) { if (string.IsNullOrEmpty(currentValue)) { @@ -203,34 +203,6 @@ namespace MediaBrowser.Model.Dlna return false; } - private static bool IsConditionSatisfied(ProfileCondition condition, float currentValue) - { - if (currentValue <= 0) - { - // If the value is unknown, it satisfies if not marked as required - return !condition.IsRequired; - } - - if (float.TryParse(condition.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var expected)) - { - switch (condition.Condition) - { - case ProfileConditionType.Equals: - return currentValue.Equals(expected); - case ProfileConditionType.GreaterThanEqual: - return currentValue >= expected; - case ProfileConditionType.LessThanEqual: - return currentValue <= expected; - case ProfileConditionType.NotEquals: - return !currentValue.Equals(expected); - default: - throw new InvalidOperationException("Unexpected ProfileConditionType: " + condition.Condition); - } - } - - return false; - } - private static bool IsConditionSatisfied(ProfileCondition condition, double? currentValue) { if (!currentValue.HasValue) diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index e6691c5139..1d18da6a01 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -10,6 +11,7 @@ namespace MediaBrowser.Model.Dlna { [XmlAttribute("type")] public DlnaProfileType Type { get; set; } + public ProfileCondition[] Conditions { get; set; } [XmlAttribute("container")] @@ -45,7 +47,7 @@ namespace MediaBrowser.Model.Dlna public static bool ContainsContainer(string profileContainers, string inputContainer) { var isNegativeList = false; - if (profileContainers != null && profileContainers.StartsWith("-")) + if (profileContainers != null && profileContainers.StartsWith("-", StringComparison.Ordinal)) { isNegativeList = true; profileContainers = profileContainers.Substring(1); diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index a20f11503c..b055ad41a4 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -32,7 +33,10 @@ namespace MediaBrowser.Model.Dlna DlnaFlags.InteractiveTransferMode | DlnaFlags.DlnaV15; - string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue)); + string dlnaflags = string.Format( + CultureInfo.InvariantCulture, + ";DLNA.ORG_FLAGS={0}", + DlnaMaps.FlagsToString(flagValue)); ResponseProfile mediaProfile = _profile.GetImageMediaProfile(container, width, diff --git a/MediaBrowser.Model/Dlna/DeviceIdentification.cs b/MediaBrowser.Model/Dlna/DeviceIdentification.cs index f1699d930b..85cc9e3c14 100644 --- a/MediaBrowser.Model/Dlna/DeviceIdentification.cs +++ b/MediaBrowser.Model/Dlna/DeviceIdentification.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 0cefbbe012..704d4ec375 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs b/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs index 3475839650..74c32c523e 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dlna diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index a5947bbf49..6f4b4ab1bf 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs b/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs index f23a240847..17c4dffcc0 100644 --- a/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs +++ b/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs index 7e35cc85ba..d9bd094d9f 100644 --- a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs +++ b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dlna @@ -5,7 +6,9 @@ namespace MediaBrowser.Model.Dlna public interface ITranscoderSupport { bool CanEncodeToAudioCodec(string codec); + bool CanEncodeToSubtitleCodec(string codec); + bool CanExtractSubtitles(string codec); } @@ -15,10 +18,12 @@ namespace MediaBrowser.Model.Dlna { return true; } + public bool CanEncodeToSubtitleCodec(string codec) { return true; } + public bool CanExtractSubtitles(string codec) { return true; diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs index 4cd318abb3..10e9179c08 100644 --- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs +++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -21,13 +22,13 @@ namespace MediaBrowser.Model.Dlna if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase)) { MediaFormatProfile? val = ResolveVideoASFFormat(videoCodec, audioCodec, width, height); - return val.HasValue ? new MediaFormatProfile[] { val.Value } : new MediaFormatProfile[] { }; + return val.HasValue ? new MediaFormatProfile[] { val.Value } : Array.Empty(); } if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase)) { MediaFormatProfile? val = ResolveVideoMP4Format(videoCodec, audioCodec, width, height); - return val.HasValue ? new MediaFormatProfile[] { val.Value } : new MediaFormatProfile[] { }; + return val.HasValue ? new MediaFormatProfile[] { val.Value } : Array.Empty(); } if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase)) @@ -61,18 +62,18 @@ namespace MediaBrowser.Model.Dlna if (string.Equals(container, "3gp", StringComparison.OrdinalIgnoreCase)) { MediaFormatProfile? val = ResolveVideo3GPFormat(videoCodec, audioCodec); - return val.HasValue ? new MediaFormatProfile[] { val.Value } : new MediaFormatProfile[] { }; + return val.HasValue ? new MediaFormatProfile[] { val.Value } : Array.Empty(); } if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase)) return new MediaFormatProfile[] { MediaFormatProfile.OGV }; - return new MediaFormatProfile[] { }; + return Array.Empty(); } private MediaFormatProfile[] ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) { - string suffix = ""; + string suffix = string.Empty; switch (timestampType) { @@ -92,16 +93,18 @@ namespace MediaBrowser.Model.Dlna if (string.Equals(videoCodec, "mpeg2video", StringComparison.OrdinalIgnoreCase)) { - var list = new List(); - - list.Add(ValueOf("MPEG_TS_SD_NA" + suffix)); - list.Add(ValueOf("MPEG_TS_SD_EU" + suffix)); - list.Add(ValueOf("MPEG_TS_SD_KO" + suffix)); + var list = new List + { + ValueOf("MPEG_TS_SD_NA" + suffix), + ValueOf("MPEG_TS_SD_EU" + suffix), + ValueOf("MPEG_TS_SD_KO" + suffix) + }; if ((timestampType == TransportStreamTimestamp.Valid) && string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)) { list.Add(MediaFormatProfile.MPEG_TS_JP_T); } + return list.ToArray(); } if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) @@ -115,6 +118,7 @@ namespace MediaBrowser.Model.Dlna { return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_DTS_ISO }; } + return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_DTS_T }; } diff --git a/MediaBrowser.Model/Dlna/ProfileCondition.cs b/MediaBrowser.Model/Dlna/ProfileCondition.cs index 2021038d8f..f8b5dee81e 100644 --- a/MediaBrowser.Model/Dlna/ProfileCondition.cs +++ b/MediaBrowser.Model/Dlna/ProfileCondition.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; @@ -6,18 +7,6 @@ namespace MediaBrowser.Model.Dlna { public class ProfileCondition { - [XmlAttribute("condition")] - public ProfileConditionType Condition { get; set; } - - [XmlAttribute("property")] - public ProfileConditionValue Property { get; set; } - - [XmlAttribute("value")] - public string Value { get; set; } - - [XmlAttribute("isRequired")] - public bool IsRequired { get; set; } - public ProfileCondition() { IsRequired = true; @@ -36,5 +25,17 @@ namespace MediaBrowser.Model.Dlna Value = value; IsRequired = isRequired; } + + [XmlAttribute("condition")] + public ProfileConditionType Condition { get; set; } + + [XmlAttribute("property")] + public ProfileConditionValue Property { get; set; } + + [XmlAttribute("value")] + public string Value { get; set; } + + [XmlAttribute("isRequired")] + public bool IsRequired { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs index c26eeec77f..30c44fbe0e 100644 --- a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs +++ b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Model.Dlna public class ResolutionConfiguration { public int MaxWidth { get; set; } + public int MaxBitrate { get; set; } public ResolutionConfiguration(int maxWidth, int maxBitrate) diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index 8235b72d13..102db3b44e 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -17,7 +18,8 @@ namespace MediaBrowser.Model.Dlna new ResolutionConfiguration(3840, 35000000) }; - public static ResolutionOptions Normalize(int? inputBitrate, + public static ResolutionOptions Normalize( + int? inputBitrate, int? unused1, int? unused2, int outputBitrate, @@ -83,6 +85,7 @@ namespace MediaBrowser.Model.Dlna { return .5; } + return 1; } diff --git a/MediaBrowser.Model/Dlna/ResolutionOptions.cs b/MediaBrowser.Model/Dlna/ResolutionOptions.cs index 5ea0252cb1..774592abc7 100644 --- a/MediaBrowser.Model/Dlna/ResolutionOptions.cs +++ b/MediaBrowser.Model/Dlna/ResolutionOptions.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Model.Dlna public class ResolutionOptions { public int? MaxWidth { get; set; } + public int? MaxHeight { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/ResponseProfile.cs b/MediaBrowser.Model/Dlna/ResponseProfile.cs index c264cb936c..48f53f06c2 100644 --- a/MediaBrowser.Model/Dlna/ResponseProfile.cs +++ b/MediaBrowser.Model/Dlna/ResponseProfile.cs @@ -1,5 +1,7 @@ +#nullable disable #pragma warning disable CS1591 +using System; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna @@ -28,7 +30,7 @@ namespace MediaBrowser.Model.Dlna public ResponseProfile() { - Conditions = new ProfileCondition[] { }; + Conditions = Array.Empty(); } public string[] GetContainers() diff --git a/MediaBrowser.Model/Dlna/SearchCriteria.cs b/MediaBrowser.Model/Dlna/SearchCriteria.cs index 394fb9af95..94f5bd3dbe 100644 --- a/MediaBrowser.Model/Dlna/SearchCriteria.cs +++ b/MediaBrowser.Model/Dlna/SearchCriteria.cs @@ -34,9 +34,9 @@ namespace MediaBrowser.Model.Dlna public SearchCriteria(string search) { - if (string.IsNullOrEmpty(search)) + if (search.Length == 0) { - throw new ArgumentNullException(nameof(search)); + throw new ArgumentException("String can't be empty.", nameof(search)); } SearchType = SearchType.Unknown; @@ -48,11 +48,10 @@ namespace MediaBrowser.Model.Dlna if (subFactors.Length == 3) { - if (string.Equals("upnp:class", subFactors[0], StringComparison.OrdinalIgnoreCase) && - (string.Equals("=", subFactors[1]) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase))) + (string.Equals("=", subFactors[1], StringComparison.Ordinal) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase))) { - if (string.Equals("\"object.item.imageItem\"", subFactors[2]) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase)) + if (string.Equals("\"object.item.imageItem\"", subFactors[2], StringComparison.Ordinal) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase)) { SearchType = SearchType.Image; } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 58755b1719..a18ad36c5a 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -339,6 +340,7 @@ namespace MediaBrowser.Model.Dlna { transcodeReasons.Add(transcodeReason.Value); } + all = false; break; } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index c9fe679e1f..2444638030 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dlna/SubtitleProfile.cs b/MediaBrowser.Model/Dlna/SubtitleProfile.cs index 6a8f655ac5..f565fb0250 100644 --- a/MediaBrowser.Model/Dlna/SubtitleProfile.cs +++ b/MediaBrowser.Model/Dlna/SubtitleProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs index 02b3a198c3..2f01836bdf 100644 --- a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs +++ b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dlna @@ -5,13 +6,21 @@ namespace MediaBrowser.Model.Dlna public class SubtitleStreamInfo { public string Url { get; set; } + public string Language { get; set; } + public string Name { get; set; } + public bool IsForced { get; set; } + public string Format { get; set; } + public string DisplayTitle { get; set; } + public int Index { get; set; } + public SubtitleDeliveryMethod DeliveryMethod { get; set; } + public bool IsExternalUrl { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 570ee7baa9..f05e31047c 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs index 3dc1fca367..d71013f019 100644 --- a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs +++ b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -9,8 +10,11 @@ namespace MediaBrowser.Model.Dlna public class UpnpDeviceInfo { public Uri Location { get; set; } + public Dictionary Headers { get; set; } + public IPAddress LocalIpAddress { get; set; } + public int LocalPort { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/VideoOptions.cs b/MediaBrowser.Model/Dlna/VideoOptions.cs index 5b12fff1cf..4194f17c6e 100644 --- a/MediaBrowser.Model/Dlna/VideoOptions.cs +++ b/MediaBrowser.Model/Dlna/VideoOptions.cs @@ -8,6 +8,7 @@ namespace MediaBrowser.Model.Dlna public class VideoOptions : AudioOptions { public int? AudioStreamIndex { get; set; } + public int? SubtitleStreamIndex { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/XmlAttribute.cs b/MediaBrowser.Model/Dlna/XmlAttribute.cs index 31603a7542..3a8939a797 100644 --- a/MediaBrowser.Model/Dlna/XmlAttribute.cs +++ b/MediaBrowser.Model/Dlna/XmlAttribute.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Drawing/DrawingUtils.cs b/MediaBrowser.Model/Drawing/DrawingUtils.cs index 0be30b0baf..1512c52337 100644 --- a/MediaBrowser.Model/Drawing/DrawingUtils.cs +++ b/MediaBrowser.Model/Drawing/DrawingUtils.cs @@ -16,7 +16,8 @@ namespace MediaBrowser.Model.Drawing /// A max fixed width, if desired. /// A max fixed height, if desired. /// A new size object. - public static ImageDimensions Resize(ImageDimensions size, + public static ImageDimensions Resize( + ImageDimensions size, int width, int height, int maxWidth, @@ -62,7 +63,7 @@ namespace MediaBrowser.Model.Drawing /// Height of the current. /// Width of the current. /// The new height. - /// the new width + /// The new width. private static int GetNewWidth(int currentHeight, int currentWidth, int newHeight) => Convert.ToInt32((double)newHeight / currentHeight * currentWidth); diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 607355d8d3..55393d32c6 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs index 5b7eefd70b..b080f3e4ac 100644 --- a/MediaBrowser.Model/Dto/BaseItemPerson.cs +++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Text.Json.Serialization; namespace MediaBrowser.Model.Dto diff --git a/MediaBrowser.Model/Dto/IHasServerId.cs b/MediaBrowser.Model/Dto/IHasServerId.cs index 8c9798c5cb..c754d276c5 100644 --- a/MediaBrowser.Model/Dto/IHasServerId.cs +++ b/MediaBrowser.Model/Dto/IHasServerId.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dto diff --git a/MediaBrowser.Model/Dto/ImageByNameInfo.cs b/MediaBrowser.Model/Dto/ImageByNameInfo.cs index d2e43634d2..06cc3e73cf 100644 --- a/MediaBrowser.Model/Dto/ImageByNameInfo.cs +++ b/MediaBrowser.Model/Dto/ImageByNameInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dto diff --git a/MediaBrowser.Model/Dto/ImageInfo.cs b/MediaBrowser.Model/Dto/ImageInfo.cs index 57942ac23b..1e9b47267c 100644 --- a/MediaBrowser.Model/Dto/ImageInfo.cs +++ b/MediaBrowser.Model/Dto/ImageInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Dto @@ -20,9 +21,9 @@ namespace MediaBrowser.Model.Dto public int? ImageIndex { get; set; } /// - /// The image tag + /// Gets or sets the image tag. /// - public string ImageTag; + public string ImageTag { get; set; } /// /// Gets or sets the path. diff --git a/MediaBrowser.Model/Dto/ImageOptions.cs b/MediaBrowser.Model/Dto/ImageOptions.cs index 4e672a007e..158e622a85 100644 --- a/MediaBrowser.Model/Dto/ImageOptions.cs +++ b/MediaBrowser.Model/Dto/ImageOptions.cs @@ -1,3 +1,4 @@ +#nullable disable using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; @@ -8,6 +9,14 @@ namespace MediaBrowser.Model.Dto /// public class ImageOptions { + /// + /// Initializes a new instance of the class. + /// + public ImageOptions() + { + EnableImageEnhancers = true; + } + /// /// Gets or sets the type of the image. /// @@ -98,13 +107,5 @@ namespace MediaBrowser.Model.Dto /// /// The color of the background. public string BackgroundColor { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public ImageOptions() - { - EnableImageEnhancers = true; - } } } diff --git a/MediaBrowser.Model/Dto/ItemIndex.cs b/MediaBrowser.Model/Dto/ItemIndex.cs deleted file mode 100644 index 525576d614..0000000000 --- a/MediaBrowser.Model/Dto/ItemIndex.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MediaBrowser.Model.Dto -{ - /// - /// Class ItemIndex. - /// - public class ItemIndex - { - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the item count. - /// - /// The item count. - public int ItemCount { get; set; } - } -} diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 29613adbf4..74c2cb4f41 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs index 21d8a31f23..1d840a3000 100644 --- a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs +++ b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/NameIdPair.cs b/MediaBrowser.Model/Dto/NameIdPair.cs index 1b4800863c..efb2c157c2 100644 --- a/MediaBrowser.Model/Dto/NameIdPair.cs +++ b/MediaBrowser.Model/Dto/NameIdPair.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/NameValuePair.cs b/MediaBrowser.Model/Dto/NameValuePair.cs index 74040c2cb4..e71ff3c21b 100644 --- a/MediaBrowser.Model/Dto/NameValuePair.cs +++ b/MediaBrowser.Model/Dto/NameValuePair.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dto @@ -6,7 +7,6 @@ namespace MediaBrowser.Model.Dto { public NameValuePair() { - } public NameValuePair(string name, string value) diff --git a/MediaBrowser.Model/Dto/RecommendationDto.cs b/MediaBrowser.Model/Dto/RecommendationDto.cs index bc97dd6f1d..107f415406 100644 --- a/MediaBrowser.Model/Dto/RecommendationDto.cs +++ b/MediaBrowser.Model/Dto/RecommendationDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs index d36706c387..40222c9dca 100644 --- a/MediaBrowser.Model/Dto/UserDto.cs +++ b/MediaBrowser.Model/Dto/UserDto.cs @@ -1,3 +1,4 @@ +#nullable disable using System; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Users; diff --git a/MediaBrowser.Model/Dto/UserItemDataDto.cs b/MediaBrowser.Model/Dto/UserItemDataDto.cs index 92f06c9733..adb2cd2ab3 100644 --- a/MediaBrowser.Model/Dto/UserItemDataDto.cs +++ b/MediaBrowser.Model/Dto/UserItemDataDto.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Dto diff --git a/MediaBrowser.Model/Entities/ChapterInfo.cs b/MediaBrowser.Model/Entities/ChapterInfo.cs index bea7ec1dba..45554c3dc0 100644 --- a/MediaBrowser.Model/Entities/ChapterInfo.cs +++ b/MediaBrowser.Model/Entities/ChapterInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Entities/DisplayPreferences.cs b/MediaBrowser.Model/Entities/DisplayPreferences.cs index 2cd8bd3065..0e5db01dd5 100644 --- a/MediaBrowser.Model/Entities/DisplayPreferences.cs +++ b/MediaBrowser.Model/Entities/DisplayPreferences.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections.Generic; namespace MediaBrowser.Model.Entities diff --git a/MediaBrowser.Model/Entities/IHasProviderIds.cs b/MediaBrowser.Model/Entities/IHasProviderIds.cs index c117efde94..1310f68ae5 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 { /// - /// 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 repetition by using extension methods. /// public interface IHasProviderIds { diff --git a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs index b98c002405..6dd6653dc7 100644 --- a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs +++ b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs @@ -5,15 +5,29 @@ using System; namespace MediaBrowser.Model.Entities { /// - /// Class LibraryUpdateInfo + /// Class LibraryUpdateInfo. /// public class LibraryUpdateInfo { + /// + /// Initializes a new instance of the class. + /// + public LibraryUpdateInfo() + { + FoldersAddedTo = Array.Empty(); + FoldersRemovedFrom = Array.Empty(); + ItemsAdded = Array.Empty(); + ItemsRemoved = Array.Empty(); + ItemsUpdated = Array.Empty(); + CollectionFolders = Array.Empty(); + } + /// /// Gets or sets the folders added to. /// /// The folders added to. public string[] FoldersAddedTo { get; set; } + /// /// Gets or sets the folders removed from. /// @@ -41,18 +55,5 @@ namespace MediaBrowser.Model.Entities public string[] CollectionFolders { get; set; } public bool IsEmpty => FoldersAddedTo.Length == 0 && FoldersRemovedFrom.Length == 0 && ItemsAdded.Length == 0 && ItemsRemoved.Length == 0 && ItemsUpdated.Length == 0 && CollectionFolders.Length == 0; - - /// - /// Initializes a new instance of the class. - /// - public LibraryUpdateInfo() - { - FoldersAddedTo = Array.Empty(); - FoldersRemovedFrom = Array.Empty(); - ItemsAdded = Array.Empty(); - ItemsRemoved = Array.Empty(); - ItemsUpdated = Array.Empty(); - CollectionFolders = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/Entities/MediaAttachment.cs b/MediaBrowser.Model/Entities/MediaAttachment.cs index 167be18c95..34e3eabc97 100644 --- a/MediaBrowser.Model/Entities/MediaAttachment.cs +++ b/MediaBrowser.Model/Entities/MediaAttachment.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Entities { /// diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index e7e8d7cecd..d68f37c9c6 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Entities/MediaUrl.cs b/MediaBrowser.Model/Entities/MediaUrl.cs index e441437553..74f982437f 100644 --- a/MediaBrowser.Model/Entities/MediaUrl.cs +++ b/MediaBrowser.Model/Entities/MediaUrl.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Entities diff --git a/MediaBrowser.Model/Entities/PackageReviewInfo.cs b/MediaBrowser.Model/Entities/PackageReviewInfo.cs index a034de8ba8..1ebbc33231 100644 --- a/MediaBrowser.Model/Entities/PackageReviewInfo.cs +++ b/MediaBrowser.Model/Entities/PackageReviewInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -7,32 +8,32 @@ namespace MediaBrowser.Model.Entities public class PackageReviewInfo { /// - /// The package id (database key) for this review + /// Gets or sets the package id (database key) for this review. /// public int id { get; set; } /// - /// The rating value + /// Gets or sets the rating value. /// public int rating { get; set; } /// - /// Whether or not this review recommends this item + /// Gets or sets whether or not this review recommends this item. /// public bool recommend { get; set; } /// - /// A short description of the review + /// Gets or sets a short description of the review. /// public string title { get; set; } /// - /// A full review + /// Gets or sets the full review. /// public string review { get; set; } /// - /// Time of review + /// Gets or sets the time of review. /// public DateTime timestamp { get; set; } diff --git a/MediaBrowser.Model/Entities/ParentalRating.cs b/MediaBrowser.Model/Entities/ParentalRating.cs index 4b37bd64af..17b2868a31 100644 --- a/MediaBrowser.Model/Entities/ParentalRating.cs +++ b/MediaBrowser.Model/Entities/ParentalRating.cs @@ -1,12 +1,23 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Entities { /// - /// Class ParentalRating + /// Class ParentalRating. /// public class ParentalRating { + public ParentalRating() + { + } + + public ParentalRating(string name, int value) + { + Name = name; + Value = value; + } + /// /// Gets or sets the name. /// @@ -18,16 +29,5 @@ namespace MediaBrowser.Model.Entities /// /// The value. public int Value { get; set; } - - public ParentalRating() - { - - } - - public ParentalRating(string name, int value) - { - Name = name; - Value = value; - } } } diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index cd387bd546..e089dd1e54 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -20,23 +20,23 @@ namespace MediaBrowser.Model.Entities } /// - /// Gets a provider id + /// Gets a provider id. /// /// The instance. /// The provider. /// System.String. - public static string GetProviderId(this IHasProviderIds instance, MetadataProviders provider) + public static string? GetProviderId(this IHasProviderIds instance, MetadataProviders provider) { return instance.GetProviderId(provider.ToString()); } /// - /// Gets a provider id + /// Gets a provider id. /// /// The instance. /// The name. /// System.String. - public static string GetProviderId(this IHasProviderIds instance, string name) + public static string? GetProviderId(this IHasProviderIds instance, string name) { if (instance == null) { @@ -53,7 +53,7 @@ namespace MediaBrowser.Model.Entities } /// - /// Sets a provider id + /// Sets a provider id. /// /// The instance. /// The name. @@ -89,7 +89,7 @@ namespace MediaBrowser.Model.Entities } /// - /// Sets a provider id + /// Sets a provider id. /// /// The instance. /// The provider. diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs index dd30c9c842..2de02e403c 100644 --- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs +++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Events/GenericEventArgs.cs b/MediaBrowser.Model/Events/GenericEventArgs.cs index 1ef0b25c98..44f60f8115 100644 --- a/MediaBrowser.Model/Events/GenericEventArgs.cs +++ b/MediaBrowser.Model/Events/GenericEventArgs.cs @@ -22,12 +22,5 @@ namespace MediaBrowser.Model.Events { Argument = arg; } - - /// - /// Initializes a new instance of the class. - /// - public GenericEventArgs() - { - } } } diff --git a/MediaBrowser.Model/Extensions/ListHelper.cs b/MediaBrowser.Model/Extensions/ListHelper.cs index 90ce6f2e5e..b893a3509a 100644 --- a/MediaBrowser.Model/Extensions/ListHelper.cs +++ b/MediaBrowser.Model/Extensions/ListHelper.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -21,6 +22,7 @@ namespace MediaBrowser.Model.Extensions return true; } } + return false; } } diff --git a/MediaBrowser.Model/Extensions/StringHelper.cs b/MediaBrowser.Model/Extensions/StringHelper.cs index f819a295c6..8ffa3c4ba6 100644 --- a/MediaBrowser.Model/Extensions/StringHelper.cs +++ b/MediaBrowser.Model/Extensions/StringHelper.cs @@ -12,9 +12,9 @@ namespace MediaBrowser.Model.Extensions /// The string with the first character as uppercase. public static string FirstToUpper(string str) { - if (string.IsNullOrEmpty(str)) + if (str.Length == 0) { - return string.Empty; + return str; } if (char.IsUpper(str[0])) diff --git a/MediaBrowser.Model/Globalization/CountryInfo.cs b/MediaBrowser.Model/Globalization/CountryInfo.cs index 72362f4f31..6f6979316b 100644 --- a/MediaBrowser.Model/Globalization/CountryInfo.cs +++ b/MediaBrowser.Model/Globalization/CountryInfo.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Globalization { /// diff --git a/MediaBrowser.Model/Globalization/CultureDto.cs b/MediaBrowser.Model/Globalization/CultureDto.cs index f415840b0f..6af4a872ce 100644 --- a/MediaBrowser.Model/Globalization/CultureDto.cs +++ b/MediaBrowser.Model/Globalization/CultureDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index 613bfca695..baefeb39cf 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections.Generic; using System.Globalization; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Model/Globalization/LocalizationOption.cs b/MediaBrowser.Model/Globalization/LocalizationOption.cs index 00caf5e118..81f47d9783 100644 --- a/MediaBrowser.Model/Globalization/LocalizationOption.cs +++ b/MediaBrowser.Model/Globalization/LocalizationOption.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Globalization @@ -11,6 +12,7 @@ namespace MediaBrowser.Model.Globalization } public string Name { get; set; } + public string Value { get; set; } } } diff --git a/MediaBrowser.Model/IO/FileSystemEntryInfo.cs b/MediaBrowser.Model/IO/FileSystemEntryInfo.cs index a197f0fbe0..36ff5d041f 100644 --- a/MediaBrowser.Model/IO/FileSystemEntryInfo.cs +++ b/MediaBrowser.Model/IO/FileSystemEntryInfo.cs @@ -1,26 +1,39 @@ namespace MediaBrowser.Model.IO { /// - /// Class FileSystemEntryInfo + /// Class FileSystemEntryInfo. /// public class FileSystemEntryInfo { /// - /// Gets or sets the name. + /// Initializes a new instance of the class. + /// + /// The filename. + /// The file path. + /// The file type. + public FileSystemEntryInfo(string name, string path, FileSystemEntryType type) + { + Name = name; + Path = path; + Type = type; + } + + /// + /// Gets the name. /// /// The name. - public string Name { get; set; } + public string Name { get; } /// - /// Gets or sets the path. + /// Gets the path. /// /// The path. - public string Path { get; set; } + public string Path { get; } /// - /// Gets or sets the type. + /// Gets the type. /// /// The type. - public FileSystemEntryType Type { get; set; } + public FileSystemEntryType Type { get; } } } diff --git a/MediaBrowser.Model/IO/FileSystemMetadata.cs b/MediaBrowser.Model/IO/FileSystemMetadata.cs index 4b9102392c..b23119d08f 100644 --- a/MediaBrowser.Model/IO/FileSystemMetadata.cs +++ b/MediaBrowser.Model/IO/FileSystemMetadata.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index 53f23a8e0a..bba69d4b46 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/IO/IIsoManager.cs b/MediaBrowser.Model/IO/IIsoManager.cs index 8b6af019d6..299bb0a213 100644 --- a/MediaBrowser.Model/IO/IIsoManager.cs +++ b/MediaBrowser.Model/IO/IIsoManager.cs @@ -16,7 +16,6 @@ namespace MediaBrowser.Model.IO /// The iso path. /// The cancellation token. /// IsoMount. - /// isoPath /// Unable to create mount. Task Mount(string isoPath, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/IO/IIsoMount.cs b/MediaBrowser.Model/IO/IIsoMount.cs index 72ec673ee1..ea65d976a0 100644 --- a/MediaBrowser.Model/IO/IIsoMount.cs +++ b/MediaBrowser.Model/IO/IIsoMount.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Model.IO public interface IIsoMount : IDisposable { /// - /// Gets or sets the iso path. + /// Gets the iso path. /// /// The iso path. string IsoPath { get; } diff --git a/MediaBrowser.Model/IO/IIsoMounter.cs b/MediaBrowser.Model/IO/IIsoMounter.cs index 83fdb5fd6b..0d257395a9 100644 --- a/MediaBrowser.Model/IO/IIsoMounter.cs +++ b/MediaBrowser.Model/IO/IIsoMounter.cs @@ -9,6 +9,12 @@ namespace MediaBrowser.Model.IO { public interface IIsoMounter { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + /// /// Mounts the specified iso path. /// @@ -25,11 +31,5 @@ namespace MediaBrowser.Model.IO /// The path. /// true if this instance can mount the specified path; otherwise, false. bool CanMount(string path); - - /// - /// Gets the name. - /// - /// The name. - string Name { get; } } } diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs index e348cd7259..af5ba5b17f 100644 --- a/MediaBrowser.Model/IO/IStreamHelper.cs +++ b/MediaBrowser.Model/IO/IStreamHelper.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Model.IO Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken); Task CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken); + Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken); Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Library/UserViewQuery.cs b/MediaBrowser.Model/Library/UserViewQuery.cs index a538efd257..8a49b68637 100644 --- a/MediaBrowser.Model/Library/UserViewQuery.cs +++ b/MediaBrowser.Model/Library/UserViewQuery.cs @@ -6,6 +6,12 @@ namespace MediaBrowser.Model.Library { public class UserViewQuery { + public UserViewQuery() + { + IncludeExternalContent = true; + PresetViews = Array.Empty(); + } + /// /// Gets or sets the user identifier. /// @@ -25,11 +31,5 @@ namespace MediaBrowser.Model.Library public bool IncludeHidden { get; set; } public string[] PresetViews { get; set; } - - public UserViewQuery() - { - IncludeExternalContent = true; - PresetViews = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs index 064ce65202..45970cf6b1 100644 --- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/GuideInfo.cs b/MediaBrowser.Model/LiveTv/GuideInfo.cs index a224d73b7e..b1cc8cfdf8 100644 --- a/MediaBrowser.Model/LiveTv/GuideInfo.cs +++ b/MediaBrowser.Model/LiveTv/GuideInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs index 8154fbd0ef..d1a94d8b30 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/LiveTvInfo.cs b/MediaBrowser.Model/LiveTv/LiveTvInfo.cs index 85b77af245..9767509d09 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvInfo.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvInfo.cs @@ -6,6 +6,12 @@ namespace MediaBrowser.Model.LiveTv { public class LiveTvInfo { + public LiveTvInfo() + { + Services = Array.Empty(); + EnabledUsers = Array.Empty(); + } + /// /// Gets or sets the services. /// @@ -23,11 +29,5 @@ namespace MediaBrowser.Model.LiveTv /// /// The enabled users. public string[] EnabledUsers { get; set; } - - public LiveTvInfo() - { - Services = Array.Empty(); - EnabledUsers = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index dc8e0f91bf..69c43efd47 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs b/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs index 09e900643a..856f638c5c 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index c75092b79c..2649829300 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs index e30dd84dcd..90422d19c3 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -14,7 +15,7 @@ namespace MediaBrowser.Model.LiveTv public SeriesTimerInfoDto() { ImageTags = new Dictionary(); - Days = new DayOfWeek[] { }; + Days = Array.Empty(); Type = "SeriesTimer"; } diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs index bb553a5766..bda46dd2bc 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Model.LiveTv /// Gets or sets the sort by - SortName, Priority /// /// The sort by. - public string SortBy { get; set; } + public string? SortBy { get; set; } /// /// Gets or sets the sort order. diff --git a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs index a1fbc51776..19039d4488 100644 --- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using MediaBrowser.Model.Dto; diff --git a/MediaBrowser.Model/LiveTv/TimerQuery.cs b/MediaBrowser.Model/LiveTv/TimerQuery.cs index 1ef6dd67e2..367c45b9df 100644 --- a/MediaBrowser.Model/LiveTv/TimerQuery.cs +++ b/MediaBrowser.Model/LiveTv/TimerQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.LiveTv diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 0fdfe57619..b24409fcc2 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -12,6 +12,8 @@ false true true + enable + latest diff --git a/MediaBrowser.Model/MediaInfo/AudioCodec.cs b/MediaBrowser.Model/MediaInfo/AudioCodec.cs index dcb6fa270d..8b17757b89 100644 --- a/MediaBrowser.Model/MediaInfo/AudioCodec.cs +++ b/MediaBrowser.Model/MediaInfo/AudioCodec.cs @@ -10,9 +10,9 @@ namespace MediaBrowser.Model.MediaInfo public static string GetFriendlyName(string codec) { - if (string.IsNullOrEmpty(codec)) + if (codec.Length == 0) { - return string.Empty; + return codec; } switch (codec.ToLowerInvariant()) diff --git a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs index 29ba10dbbf..83f982a5c8 100644 --- a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs +++ b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs index 52348f8026..ea5d4e7d77 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -7,6 +8,13 @@ namespace MediaBrowser.Model.MediaInfo { public class LiveStreamRequest { + public LiveStreamRequest() + { + EnableDirectPlay = true; + EnableDirectStream = true; + DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http }; + } + public string OpenToken { get; set; } public Guid UserId { get; set; } public string PlaySessionId { get; set; } @@ -22,12 +30,7 @@ namespace MediaBrowser.Model.MediaInfo public bool EnableDirectStream { get; set; } public MediaProtocol[] DirectPlayProtocols { get; set; } - public LiveStreamRequest() - { - EnableDirectPlay = true; - EnableDirectStream = true; - DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http }; - } + public LiveStreamRequest(AudioOptions options) { @@ -38,8 +41,7 @@ namespace MediaBrowser.Model.MediaInfo DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http }; - var videoOptions = options as VideoOptions; - if (videoOptions != null) + if (options is VideoOptions videoOptions) { AudioStreamIndex = videoOptions.AudioStreamIndex; SubtitleStreamIndex = videoOptions.SubtitleStreamIndex; diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs b/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs index 45b8fcce99..f017c1a117 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs @@ -6,6 +6,11 @@ namespace MediaBrowser.Model.MediaInfo { public class LiveStreamResponse { - public MediaSourceInfo MediaSource { get; set; } + public LiveStreamResponse(MediaSourceInfo mediaSource) + { + MediaSource = mediaSource; + } + + public MediaSourceInfo MediaSource { get; } } } diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs index ad174f15d9..97b9799357 100644 --- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs +++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index a2f1634222..82e13e0ebe 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs index 440818c3ef..2733501822 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Model.MediaInfo /// Gets or sets the play session identifier. /// /// The play session identifier. - public string PlaySessionId { get; set; } + public string? PlaySessionId { get; set; } /// /// Gets or sets the error code. diff --git a/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs b/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs index 5b0ccb28a4..72bb3d9c63 100644 --- a/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs +++ b/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.MediaInfo @@ -5,8 +6,11 @@ namespace MediaBrowser.Model.MediaInfo public class SubtitleTrackEvent { public string Id { get; set; } + public string Text { get; set; } + public long StartPositionTicks { get; set; } + public long EndPositionTicks { get; set; } } } diff --git a/MediaBrowser.Model/Net/EndPointInfo.cs b/MediaBrowser.Model/Net/EndPointInfo.cs index f5ac3d1698..034734a9e1 100644 --- a/MediaBrowser.Model/Net/EndPointInfo.cs +++ b/MediaBrowser.Model/Net/EndPointInfo.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Model.Net public class EndPointInfo { public bool IsLocal { get; set; } + public bool IsInNetwork { get; set; } } } diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs index 2bfbfcb20c..5b6ed92df1 100644 --- a/MediaBrowser.Model/Net/ISocket.cs +++ b/MediaBrowser.Model/Net/ISocket.cs @@ -17,6 +17,7 @@ namespace MediaBrowser.Model.Net Task ReceiveAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback); + SocketReceiveResult EndReceive(IAsyncResult result); /// diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 68bcc590c4..cfac4d1e2d 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -8,12 +8,12 @@ using System.Linq; namespace MediaBrowser.Model.Net { /// - /// Class MimeTypes + /// Class MimeTypes. /// public static class MimeTypes { /// - /// Any extension in this list is considered a video file + /// Any extension in this list is considered a video file. /// private static readonly HashSet _videoFileExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { @@ -141,16 +141,16 @@ namespace MediaBrowser.Model.Net return dict; } - public static string GetMimeType(string path) => GetMimeType(path, true); + public static string? GetMimeType(string path) => GetMimeType(path, true); /// /// Gets the type of the MIME. /// - public static string GetMimeType(string path, bool enableStreamDefault) + public static string? GetMimeType(string path, bool enableStreamDefault) { - if (string.IsNullOrEmpty(path)) + if (path.Length == 0) { - throw new ArgumentNullException(nameof(path)); + throw new ArgumentException("String can't be empty.", nameof(path)); } var ext = Path.GetExtension(path); @@ -188,11 +188,11 @@ namespace MediaBrowser.Model.Net return enableStreamDefault ? "application/octet-stream" : null; } - public static string ToExtension(string mimeType) + public static string? ToExtension(string mimeType) { - if (string.IsNullOrEmpty(mimeType)) + if (mimeType.Length == 0) { - throw new ArgumentNullException(nameof(mimeType)); + throw new ArgumentException("String can't be empty.", nameof(mimeType)); } // handle text/html; charset=UTF-8 diff --git a/MediaBrowser.Model/Net/NetworkShare.cs b/MediaBrowser.Model/Net/NetworkShare.cs index 744c6ec14e..a40cf73e49 100644 --- a/MediaBrowser.Model/Net/NetworkShare.cs +++ b/MediaBrowser.Model/Net/NetworkShare.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Net diff --git a/MediaBrowser.Model/Net/SocketReceiveResult.cs b/MediaBrowser.Model/Net/SocketReceiveResult.cs index 141ae16089..54139fe9c5 100644 --- a/MediaBrowser.Model/Net/SocketReceiveResult.cs +++ b/MediaBrowser.Model/Net/SocketReceiveResult.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#nullable disable using System.Net; @@ -10,12 +10,12 @@ namespace MediaBrowser.Model.Net public sealed class SocketReceiveResult { /// - /// The buffer to place received data into. + /// Gets or sets the buffer to place received data into. /// public byte[] Buffer { get; set; } /// - /// The number of bytes received. + /// Gets or sets the number of bytes received. /// public int ReceivedBytes { get; set; } @@ -23,6 +23,10 @@ namespace MediaBrowser.Model.Net /// The the data was received from. /// public IPEndPoint RemoteEndPoint { get; set; } + + /// + /// The local . + /// public IPAddress LocalIPAddress { get; set; } } } diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs index 7575224d4e..962b81b959 100644 --- a/MediaBrowser.Model/Net/WebSocketMessage.cs +++ b/MediaBrowser.Model/Net/WebSocketMessage.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Net diff --git a/MediaBrowser.Model/Notifications/NotificationOption.cs b/MediaBrowser.Model/Notifications/NotificationOption.cs index 4fb724515c..144949a3b7 100644 --- a/MediaBrowser.Model/Notifications/NotificationOption.cs +++ b/MediaBrowser.Model/Notifications/NotificationOption.cs @@ -6,6 +6,15 @@ namespace MediaBrowser.Model.Notifications { public class NotificationOption { + public NotificationOption(string type) + { + Type = type; + + DisabledServices = Array.Empty(); + DisabledMonitorUsers = Array.Empty(); + SendToUsers = Array.Empty(); + } + public string Type { get; set; } /// @@ -35,12 +44,5 @@ namespace MediaBrowser.Model.Notifications /// /// The send to user mode. public SendToUserType SendToUserMode { get; set; } - - public NotificationOption() - { - DisabledServices = Array.Empty(); - DisabledMonitorUsers = Array.Empty(); - SendToUsers = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index 79a128e9be..7b299b1aaa 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -14,63 +15,53 @@ namespace MediaBrowser.Model.Notifications { Options = new[] { - new NotificationOption + new NotificationOption(NotificationType.TaskFailed.ToString()) { - Type = NotificationType.TaskFailed.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.ServerRestartRequired.ToString()) { - Type = NotificationType.ServerRestartRequired.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.ApplicationUpdateAvailable.ToString()) { - Type = NotificationType.ApplicationUpdateAvailable.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.ApplicationUpdateInstalled.ToString()) { - Type = NotificationType.ApplicationUpdateInstalled.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.PluginUpdateInstalled.ToString()) { - Type = NotificationType.PluginUpdateInstalled.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.PluginUninstalled.ToString()) { - Type = NotificationType.PluginUninstalled.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.InstallationFailed.ToString()) { - Type = NotificationType.InstallationFailed.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.PluginInstalled.ToString()) { - Type = NotificationType.PluginInstalled.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.PluginError.ToString()) { - Type = NotificationType.PluginError.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.UserLockedOut.ToString()) { - Type = NotificationType.UserLockedOut.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins } @@ -81,8 +72,12 @@ namespace MediaBrowser.Model.Notifications { foreach (NotificationOption i in Options) { - if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase)) 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 ffcfab24ff..febc2bc099 100644 --- a/MediaBrowser.Model/Notifications/NotificationRequest.cs +++ b/MediaBrowser.Model/Notifications/NotificationRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs b/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs index bfa163b402..402fbe81a0 100644 --- a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs +++ b/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Notifications diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs index b7003c4c8a..ef435b21ec 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs b/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs index 4f2067b98a..f3a1518ed1 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs @@ -4,6 +4,11 @@ namespace MediaBrowser.Model.Playlists { public class PlaylistCreationResult { - public string Id { get; set; } + public PlaylistCreationResult(string id) + { + Id = id; + } + + public string Id { get; } } } diff --git a/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs b/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs deleted file mode 100644 index 324a38e700..0000000000 --- a/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs +++ /dev/null @@ -1,39 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Model.Playlists -{ - public class PlaylistItemQuery - { - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public string UserId { get; set; } - - /// - /// Gets or sets the start index. - /// - /// The start index. - public int? StartIndex { get; set; } - - /// - /// Gets or sets the limit. - /// - /// The limit. - public int? Limit { get; set; } - - /// - /// Gets or sets the fields. - /// - /// The fields. - public ItemFields[] Fields { get; set; } - } -} diff --git a/MediaBrowser.Model/Plugins/PluginInfo.cs b/MediaBrowser.Model/Plugins/PluginInfo.cs index 9ff9ea457f..c13f1a89f1 100644 --- a/MediaBrowser.Model/Plugins/PluginInfo.cs +++ b/MediaBrowser.Model/Plugins/PluginInfo.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Plugins { /// diff --git a/MediaBrowser.Model/Plugins/PluginPageInfo.cs b/MediaBrowser.Model/Plugins/PluginPageInfo.cs index eb6a1527d2..ca72e19ee1 100644 --- a/MediaBrowser.Model/Plugins/PluginPageInfo.cs +++ b/MediaBrowser.Model/Plugins/PluginPageInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Plugins diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index 2b481ad7ed..f2e6d8ef32 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Providers diff --git a/MediaBrowser.Model/Providers/ExternalUrl.cs b/MediaBrowser.Model/Providers/ExternalUrl.cs index d4f4fa8403..9467a2b003 100644 --- a/MediaBrowser.Model/Providers/ExternalUrl.cs +++ b/MediaBrowser.Model/Providers/ExternalUrl.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Providers diff --git a/MediaBrowser.Model/Providers/ImageProviderInfo.cs b/MediaBrowser.Model/Providers/ImageProviderInfo.cs index a22ec3c079..c63a2ceda8 100644 --- a/MediaBrowser.Model/Providers/ImageProviderInfo.cs +++ b/MediaBrowser.Model/Providers/ImageProviderInfo.cs @@ -10,6 +10,12 @@ namespace MediaBrowser.Model.Providers /// public class ImageProviderInfo { + public ImageProviderInfo(string name, ImageType[] supportedImages) + { + Name = name; + SupportedImages = supportedImages; + } + /// /// Gets or sets the name. /// @@ -17,10 +23,5 @@ namespace MediaBrowser.Model.Providers public string Name { get; set; } public ImageType[] SupportedImages { get; set; } - - public ImageProviderInfo() - { - SupportedImages = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/Providers/RemoteImageInfo.cs b/MediaBrowser.Model/Providers/RemoteImageInfo.cs index ee2b9d8fd1..78ab6c706c 100644 --- a/MediaBrowser.Model/Providers/RemoteImageInfo.cs +++ b/MediaBrowser.Model/Providers/RemoteImageInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Model/Providers/RemoteImageQuery.cs b/MediaBrowser.Model/Providers/RemoteImageQuery.cs index 2873c10038..b7fad87aba 100644 --- a/MediaBrowser.Model/Providers/RemoteImageQuery.cs +++ b/MediaBrowser.Model/Providers/RemoteImageQuery.cs @@ -6,7 +6,12 @@ namespace MediaBrowser.Model.Providers { public class RemoteImageQuery { - public string ProviderName { get; set; } + public RemoteImageQuery(string providerName) + { + ProviderName = providerName; + } + + public string ProviderName { get; } public ImageType? ImageType { get; set; } diff --git a/MediaBrowser.Model/Providers/RemoteImageResult.cs b/MediaBrowser.Model/Providers/RemoteImageResult.cs index 5ca00f7701..e6067ee6ef 100644 --- a/MediaBrowser.Model/Providers/RemoteImageResult.cs +++ b/MediaBrowser.Model/Providers/RemoteImageResult.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Providers { /// diff --git a/MediaBrowser.Model/Providers/RemoteSearchResult.cs b/MediaBrowser.Model/Providers/RemoteSearchResult.cs index 161e048214..c96eb0b59a 100644 --- a/MediaBrowser.Model/Providers/RemoteSearchResult.cs +++ b/MediaBrowser.Model/Providers/RemoteSearchResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -8,23 +9,34 @@ namespace MediaBrowser.Model.Providers { public class RemoteSearchResult : IHasProviderIds { + public RemoteSearchResult() + { + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + Artists = Array.Empty(); + } + /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } + /// /// Gets or sets the provider ids. /// /// The provider ids. public Dictionary ProviderIds { get; set; } + /// /// Gets or sets the year. /// /// The year. public int? ProductionYear { get; set; } + public int? IndexNumber { get; set; } + public int? IndexNumberEnd { get; set; } + public int? ParentIndexNumber { get; set; } public DateTime? PremiereDate { get; set; } @@ -32,15 +44,13 @@ namespace MediaBrowser.Model.Providers public string ImageUrl { get; set; } public string SearchProviderName { get; set; } + public string Overview { get; set; } public RemoteSearchResult AlbumArtist { get; set; } + public RemoteSearchResult[] Artists { get; set; } - public RemoteSearchResult() - { - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - Artists = new RemoteSearchResult[] { }; - } + } } diff --git a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs index 06f29df3f9..d9f7a852ca 100644 --- a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs +++ b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs index 9e60492463..c073795704 100644 --- a/MediaBrowser.Model/Providers/SubtitleOptions.cs +++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs index fca93d176e..ee25be4b6c 100644 --- a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs +++ b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Providers diff --git a/MediaBrowser.Model/Querying/AllThemeMediaResult.cs b/MediaBrowser.Model/Querying/AllThemeMediaResult.cs index a264c6178c..6b503ba6ba 100644 --- a/MediaBrowser.Model/Querying/AllThemeMediaResult.cs +++ b/MediaBrowser.Model/Querying/AllThemeMediaResult.cs @@ -1,15 +1,10 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Querying { public class AllThemeMediaResult { - public ThemeMediaResult ThemeVideosResult { get; set; } - - public ThemeMediaResult ThemeSongsResult { get; set; } - - public ThemeMediaResult SoundtrackSongsResult { get; set; } - public AllThemeMediaResult() { ThemeVideosResult = new ThemeMediaResult(); @@ -18,5 +13,11 @@ namespace MediaBrowser.Model.Querying SoundtrackSongsResult = new ThemeMediaResult(); } + + public ThemeMediaResult ThemeVideosResult { get; set; } + + public ThemeMediaResult ThemeSongsResult { get; set; } + + public ThemeMediaResult SoundtrackSongsResult { get; set; } } } diff --git a/MediaBrowser.Model/Querying/EpisodeQuery.cs b/MediaBrowser.Model/Querying/EpisodeQuery.cs index 6fb4df676d..13b1a0dcbf 100644 --- a/MediaBrowser.Model/Querying/EpisodeQuery.cs +++ b/MediaBrowser.Model/Querying/EpisodeQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/ItemCountsQuery.cs b/MediaBrowser.Model/Querying/ItemCountsQuery.cs deleted file mode 100644 index f113cf3808..0000000000 --- a/MediaBrowser.Model/Querying/ItemCountsQuery.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MediaBrowser.Model.Querying -{ - /// - /// Class ItemCountsQuery. - /// - public class ItemCountsQuery - { - /// - /// Gets or sets the user id. - /// - /// The user id. - public string UserId { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is favorite. - /// - /// null if [is favorite] contains no value, true if [is favorite]; otherwise, false. - public bool? IsFavorite { get; set; } - } -} diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index 15b60ad84b..edf71c1a77 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -8,73 +8,99 @@ namespace MediaBrowser.Model.Querying public static class ItemSortBy { public const string AiredEpisodeOrder = "AiredEpisodeOrder"; + /// - /// The album + /// The album. /// public const string Album = "Album"; + /// - /// The album artist + /// The album artist. /// public const string AlbumArtist = "AlbumArtist"; + /// - /// The artist + /// The artist. /// public const string Artist = "Artist"; + /// - /// The date created + /// The date created. /// public const string DateCreated = "DateCreated"; + /// - /// The official rating + /// The official rating. /// public const string OfficialRating = "OfficialRating"; + /// - /// The date played + /// The date played. /// public const string DatePlayed = "DatePlayed"; + /// - /// The premiere date + /// The premiere date. /// public const string PremiereDate = "PremiereDate"; + public const string StartDate = "StartDate"; + /// - /// The sort name + /// The sort name. /// public const string SortName = "SortName"; + public const string Name = "Name"; + /// - /// The random + /// The random. /// public const string Random = "Random"; + /// - /// The runtime + /// The runtime. /// public const string Runtime = "Runtime"; + /// - /// The community rating + /// The community rating. /// public const string CommunityRating = "CommunityRating"; + /// - /// The production year + /// The production year. /// public const string ProductionYear = "ProductionYear"; + /// - /// The play count + /// The play count. /// public const string PlayCount = "PlayCount"; + /// - /// The critic rating + /// The critic rating. /// public const string CriticRating = "CriticRating"; + public const string IsFolder = "IsFolder"; + public const string IsUnplayed = "IsUnplayed"; + public const string IsPlayed = "IsPlayed"; + public const string SeriesSortName = "SeriesSortName"; + public const string VideoBitRate = "VideoBitRate"; + public const string AirTime = "AirTime"; + public const string Studio = "Studio"; + public const string IsFavoriteOrLiked = "IsFavoriteOrLiked"; + public const string DateLastContentAdded = "DateLastContentAdded"; + public const string SeriesDatePlayed = "SeriesDatePlayed"; } } diff --git a/MediaBrowser.Model/Querying/LatestItemsQuery.cs b/MediaBrowser.Model/Querying/LatestItemsQuery.cs index 84e29e76a9..7954ef4b43 100644 --- a/MediaBrowser.Model/Querying/LatestItemsQuery.cs +++ b/MediaBrowser.Model/Querying/LatestItemsQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -7,8 +8,13 @@ namespace MediaBrowser.Model.Querying { public class LatestItemsQuery { + public LatestItemsQuery() + { + EnableImageTypes = Array.Empty(); + } + /// - /// The user to localize search results for + /// The user to localize search results for. /// /// The user id. public Guid UserId { get; set; } @@ -26,13 +32,13 @@ namespace MediaBrowser.Model.Querying public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. public int? Limit { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. public ItemFields[] Fields { get; set; } @@ -54,25 +60,23 @@ namespace MediaBrowser.Model.Querying /// /// true if [group items]; otherwise, false. public bool GroupItems { get; set; } + /// /// Gets or sets a value indicating whether [enable images]. /// /// null if [enable images] contains no value, true if [enable images]; otherwise, false. public bool? EnableImages { get; set; } + /// /// Gets or sets the image type limit. /// /// The image type limit. public int? ImageTypeLimit { get; set; } + /// /// Gets or sets the enable image types. /// /// The enable image types. public ImageType[] EnableImageTypes { get; set; } - - public LatestItemsQuery() - { - EnableImageTypes = new ImageType[] { }; - } } } diff --git a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs index 93de0a8cd1..1c8875890a 100644 --- a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs +++ b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index 1543aea16f..0df86cb22d 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/QueryFilters.cs b/MediaBrowser.Model/Querying/QueryFilters.cs index 8d879c1748..e04208f766 100644 --- a/MediaBrowser.Model/Querying/QueryFilters.cs +++ b/MediaBrowser.Model/Querying/QueryFilters.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/QueryResult.cs b/MediaBrowser.Model/Querying/QueryResult.cs index 266f1c7e65..42586243da 100644 --- a/MediaBrowser.Model/Querying/QueryResult.cs +++ b/MediaBrowser.Model/Querying/QueryResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/ThemeMediaResult.cs b/MediaBrowser.Model/Querying/ThemeMediaResult.cs index bae954d78e..5afedeeaf7 100644 --- a/MediaBrowser.Model/Querying/ThemeMediaResult.cs +++ b/MediaBrowser.Model/Querying/ThemeMediaResult.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.Querying { /// - /// Class ThemeMediaResult + /// Class ThemeMediaResult. /// public class ThemeMediaResult : QueryResult { diff --git a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs index 123d0fad23..ed1aa7ac6d 100644 --- a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs +++ b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs index 6e52314fa9..c7a721df6e 100644 --- a/MediaBrowser.Model/Search/SearchHint.cs +++ b/MediaBrowser.Model/Search/SearchHint.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Search/SearchHintResult.cs b/MediaBrowser.Model/Search/SearchHintResult.cs index 3c4fbec9e8..92ba4139ea 100644 --- a/MediaBrowser.Model/Search/SearchHintResult.cs +++ b/MediaBrowser.Model/Search/SearchHintResult.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Search { /// diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs index 8a018312e0..4470f1ad97 100644 --- a/MediaBrowser.Model/Search/SearchQuery.cs +++ b/MediaBrowser.Model/Search/SearchQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Serialization/IJsonSerializer.cs b/MediaBrowser.Model/Serialization/IJsonSerializer.cs index 6223bb5598..09b6ff9b5a 100644 --- a/MediaBrowser.Model/Serialization/IJsonSerializer.cs +++ b/MediaBrowser.Model/Serialization/IJsonSerializer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Serialization/IXmlSerializer.cs b/MediaBrowser.Model/Serialization/IXmlSerializer.cs index 1edd98fadd..16d126ac7c 100644 --- a/MediaBrowser.Model/Serialization/IXmlSerializer.cs +++ b/MediaBrowser.Model/Serialization/IXmlSerializer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Services/ApiMemberAttribute.cs b/MediaBrowser.Model/Services/ApiMemberAttribute.cs index 8e50836f4d..7c23eee448 100644 --- a/MediaBrowser.Model/Services/ApiMemberAttribute.cs +++ b/MediaBrowser.Model/Services/ApiMemberAttribute.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Services diff --git a/MediaBrowser.Model/Services/IHasRequestFilter.cs b/MediaBrowser.Model/Services/IHasRequestFilter.cs index 3d2e9c0dcf..332ba113c7 100644 --- a/MediaBrowser.Model/Services/IHasRequestFilter.cs +++ b/MediaBrowser.Model/Services/IHasRequestFilter.cs @@ -7,18 +7,18 @@ namespace MediaBrowser.Model.Services public interface IHasRequestFilter { /// - /// Order in which Request Filters are executed. + /// Gets the order in which Request Filters are executed. /// <0 Executed before global request filters - /// >0 Executed after global request filters + /// >0 Executed after global request filters. /// int Priority { get; } /// /// The request filter is executed before the service. /// - /// The http request wrapper - /// The http response wrapper - /// The request DTO + /// The http request wrapper. + /// The http response wrapper. + /// The request DTO. void RequestFilter(IRequest req, HttpResponse res, object requestDto); } } diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs index 4dccd2d686..3ea65195cd 100644 --- a/MediaBrowser.Model/Services/IHttpRequest.cs +++ b/MediaBrowser.Model/Services/IHttpRequest.cs @@ -5,12 +5,12 @@ namespace MediaBrowser.Model.Services public interface IHttpRequest : IRequest { /// - /// The HTTP Verb + /// Gets the HTTP Verb. /// string HttpMethod { get; } /// - /// The value of the Accept HTTP Request Header + /// Gets the value of the Accept HTTP Request Header. /// string Accept { get; } } diff --git a/MediaBrowser.Model/Services/IHttpResult.cs b/MediaBrowser.Model/Services/IHttpResult.cs index b153f15ec3..abc581d8e2 100644 --- a/MediaBrowser.Model/Services/IHttpResult.cs +++ b/MediaBrowser.Model/Services/IHttpResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Net; @@ -7,27 +8,27 @@ namespace MediaBrowser.Model.Services public interface IHttpResult : IHasHeaders { /// - /// The HTTP Response Status + /// The HTTP Response Status. /// int Status { get; set; } /// - /// The HTTP Response Status Code + /// The HTTP Response Status Code. /// HttpStatusCode StatusCode { get; set; } /// - /// The HTTP Response ContentType + /// The HTTP Response ContentType. /// string ContentType { get; set; } /// - /// Response DTO + /// Response DTO. /// object Response { get; set; } /// - /// Holds the request call context + /// Holds the request call context. /// IRequest RequestContext { get; set; } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 3f4edced61..f413f1e177 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 19e9e53e76..d07ff15482 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -19,11 +20,6 @@ namespace MediaBrowser.Model.Services return StringComparison.OrdinalIgnoreCase; } - private static StringComparer GetStringComparer() - { - return StringComparer.OrdinalIgnoreCase; - } - /// /// Adds a new query parameter. /// diff --git a/MediaBrowser.Model/Services/RouteAttribute.cs b/MediaBrowser.Model/Services/RouteAttribute.cs index 197ba05e58..162576aa7e 100644 --- a/MediaBrowser.Model/Services/RouteAttribute.cs +++ b/MediaBrowser.Model/Services/RouteAttribute.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/BrowseRequest.cs b/MediaBrowser.Model/Session/BrowseRequest.cs index f485d680e2..1c997d5846 100644 --- a/MediaBrowser.Model/Session/BrowseRequest.cs +++ b/MediaBrowser.Model/Session/BrowseRequest.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Session { /// diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index 5da4998e84..51db66d212 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/GeneralCommand.cs b/MediaBrowser.Model/Session/GeneralCommand.cs index 980e1f88b2..9794bd2929 100644 --- a/MediaBrowser.Model/Session/GeneralCommand.cs +++ b/MediaBrowser.Model/Session/GeneralCommand.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/MessageCommand.cs b/MediaBrowser.Model/Session/MessageCommand.cs index 473a7bccc9..09abfbb3f2 100644 --- a/MediaBrowser.Model/Session/MessageCommand.cs +++ b/MediaBrowser.Model/Session/MessageCommand.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Session diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs index bdb2b24394..62b68b49ea 100644 --- a/MediaBrowser.Model/Session/PlayRequest.cs +++ b/MediaBrowser.Model/Session/PlayRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs index 5687ba84bb..6b4cfe4f0d 100644 --- a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/PlaybackStopInfo.cs b/MediaBrowser.Model/Session/PlaybackStopInfo.cs index f8cfacc201..b0827ac99c 100644 --- a/MediaBrowser.Model/Session/PlaybackStopInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackStopInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/PlayerStateInfo.cs b/MediaBrowser.Model/Session/PlayerStateInfo.cs index 0f99568739..0f10605ea1 100644 --- a/MediaBrowser.Model/Session/PlayerStateInfo.cs +++ b/MediaBrowser.Model/Session/PlayerStateInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Session diff --git a/MediaBrowser.Model/Session/PlaystateRequest.cs b/MediaBrowser.Model/Session/PlaystateRequest.cs index 493a8063ad..ba2c024b76 100644 --- a/MediaBrowser.Model/Session/PlaystateRequest.cs +++ b/MediaBrowser.Model/Session/PlaystateRequest.cs @@ -12,6 +12,6 @@ namespace MediaBrowser.Model.Session /// Gets or sets the controlling user identifier. /// /// The controlling user identifier. - public string ControllingUserId { get; set; } + public string? ControllingUserId { get; set; } } } diff --git a/MediaBrowser.Model/Session/SessionUserInfo.cs b/MediaBrowser.Model/Session/SessionUserInfo.cs index 42a56b92b9..4d6f35efc7 100644 --- a/MediaBrowser.Model/Session/SessionUserInfo.cs +++ b/MediaBrowser.Model/Session/SessionUserInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Session @@ -12,6 +13,7 @@ namespace MediaBrowser.Model.Session /// /// The user identifier. public Guid UserId { get; set; } + /// /// Gets or sets the name of the user. /// diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index 8f4e688f09..d6dc83413b 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/MediaBrowser.Model/Session/TranscodingInfo.cs @@ -1,5 +1,8 @@ +#nullable disable #pragma warning disable CS1591 +using System; + namespace MediaBrowser.Model.Session { public class TranscodingInfo @@ -22,7 +25,7 @@ namespace MediaBrowser.Model.Session public TranscodingInfo() { - TranscodeReasons = new TranscodeReason[] { }; + TranscodeReasons = Array.Empty(); } } diff --git a/MediaBrowser.Model/Session/UserDataChangeInfo.cs b/MediaBrowser.Model/Session/UserDataChangeInfo.cs index 0872eb4b11..0fd24edccd 100644 --- a/MediaBrowser.Model/Session/UserDataChangeInfo.cs +++ b/MediaBrowser.Model/Session/UserDataChangeInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.Session diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs index 30bf27f381..3cc9ff7267 100644 --- a/MediaBrowser.Model/Sync/SyncJob.cs +++ b/MediaBrowser.Model/Sync/SyncJob.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Sync/SyncTarget.cs b/MediaBrowser.Model/Sync/SyncTarget.cs index 20a0c8cc77..9e6bbbc009 100644 --- a/MediaBrowser.Model/Sync/SyncTarget.cs +++ b/MediaBrowser.Model/Sync/SyncTarget.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Sync diff --git a/MediaBrowser.Model/System/LogFile.cs b/MediaBrowser.Model/System/LogFile.cs index a2b7016648..aec910c92c 100644 --- a/MediaBrowser.Model/System/LogFile.cs +++ b/MediaBrowser.Model/System/LogFile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs index 1775470b54..b6196a43fc 100644 --- a/MediaBrowser.Model/System/PublicSystemInfo.cs +++ b/MediaBrowser.Model/System/PublicSystemInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.System diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index cfa7684c91..7582cb7485 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -34,7 +35,6 @@ namespace MediaBrowser.Model.System /// The display name of the operating system. public string OperatingSystemDisplayName { get; set; } - /// /// Get or sets the package name. /// diff --git a/MediaBrowser.Model/System/WakeOnLanInfo.cs b/MediaBrowser.Model/System/WakeOnLanInfo.cs index 534ad19ecc..b2cbe737d1 100644 --- a/MediaBrowser.Model/System/WakeOnLanInfo.cs +++ b/MediaBrowser.Model/System/WakeOnLanInfo.cs @@ -7,36 +7,21 @@ namespace MediaBrowser.Model.System /// public class WakeOnLanInfo { - /// - /// Returns the MAC address of the device. - /// - /// The MAC address. - public string MacAddress { get; set; } - - /// - /// Returns the wake-on-LAN port. - /// - /// The wake-on-LAN port. - public int Port { get; set; } - /// /// Initializes a new instance of the class. /// /// The MAC address. - public WakeOnLanInfo(PhysicalAddress macAddress) + public WakeOnLanInfo(PhysicalAddress macAddress) : this(macAddress.ToString()) { - MacAddress = macAddress.ToString(); - Port = 9; } /// /// Initializes a new instance of the class. /// /// The MAC address. - public WakeOnLanInfo(string macAddress) + public WakeOnLanInfo(string macAddress) : this() { MacAddress = macAddress; - Port = 9; } /// @@ -46,5 +31,17 @@ namespace MediaBrowser.Model.System { Port = 9; } + + /// + /// Gets the MAC address of the device. + /// + /// The MAC address. + public string? MacAddress { get; set; } + + /// + /// Gets or sets the wake-on-LAN port. + /// + /// The wake-on-LAN port. + public int Port { get; set; } } } diff --git a/MediaBrowser.Model/Tasks/IScheduledTask.cs b/MediaBrowser.Model/Tasks/IScheduledTask.cs index ed160e1762..bf87088e45 100644 --- a/MediaBrowser.Model/Tasks/IScheduledTask.cs +++ b/MediaBrowser.Model/Tasks/IScheduledTask.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Threading; @@ -8,16 +6,19 @@ using System.Threading.Tasks; namespace MediaBrowser.Model.Tasks { /// - /// Interface IScheduledTaskWorker + /// Interface IScheduledTaskWorker. /// public interface IScheduledTask { /// - /// Gets the name of the task + /// Gets the name of the task. /// /// The name. string Name { get; } + /// + /// Gets the key of the task. + /// string Key { get; } /// @@ -33,7 +34,7 @@ namespace MediaBrowser.Model.Tasks string Category { get; } /// - /// Executes the task + /// Executes the task. /// /// The cancellation token. /// The progress. diff --git a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs index 4dd1bb5d04..c79d7fe75d 100644 --- a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs +++ b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs @@ -1,3 +1,4 @@ +#nullable disable using System; using MediaBrowser.Model.Events; diff --git a/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs b/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs index ca0743ccae..9063903ae0 100644 --- a/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs +++ b/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs @@ -14,9 +14,7 @@ namespace MediaBrowser.Model.Tasks { var isHidden = false; - var configurableTask = task.ScheduledTask as IConfigurableScheduledTask; - - if (configurableTask != null) + if (task.ScheduledTask is IConfigurableScheduledTask configurableTask) { isHidden = configurableTask.IsHidden; } diff --git a/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs b/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs index cc6c2b62b3..48950667e6 100644 --- a/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs +++ b/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs @@ -6,8 +6,14 @@ namespace MediaBrowser.Model.Tasks { public class TaskCompletionEventArgs : EventArgs { - public IScheduledTaskWorker Task { get; set; } + public TaskCompletionEventArgs(IScheduledTaskWorker task, TaskResult result) + { + Task = task; + Result = result; + } - public TaskResult Result { get; set; } + public IScheduledTaskWorker Task { get; } + + public TaskResult Result { get; } } } diff --git a/MediaBrowser.Model/Tasks/TaskInfo.cs b/MediaBrowser.Model/Tasks/TaskInfo.cs index 5144c035ab..77100dfe76 100644 --- a/MediaBrowser.Model/Tasks/TaskInfo.cs +++ b/MediaBrowser.Model/Tasks/TaskInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Tasks diff --git a/MediaBrowser.Model/Tasks/TaskResult.cs b/MediaBrowser.Model/Tasks/TaskResult.cs index c6f92e7ed5..31001aeb22 100644 --- a/MediaBrowser.Model/Tasks/TaskResult.cs +++ b/MediaBrowser.Model/Tasks/TaskResult.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Tasks diff --git a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs index 699e0ea3a8..5aeaffc2b3 100644 --- a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs +++ b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Updates/CheckForUpdateResult.cs b/MediaBrowser.Model/Updates/CheckForUpdateResult.cs index be1b082238..9c59a7c886 100644 --- a/MediaBrowser.Model/Updates/CheckForUpdateResult.cs +++ b/MediaBrowser.Model/Updates/CheckForUpdateResult.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Updates { /// diff --git a/MediaBrowser.Model/Updates/InstallationInfo.cs b/MediaBrowser.Model/Updates/InstallationInfo.cs index 42c2105f54..4651a4169d 100644 --- a/MediaBrowser.Model/Updates/InstallationInfo.cs +++ b/MediaBrowser.Model/Updates/InstallationInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Updates diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs index abbe91eff6..b5a5068e7b 100644 --- a/MediaBrowser.Model/Updates/PackageInfo.cs +++ b/MediaBrowser.Model/Updates/PackageInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using System; using System.Collections.Generic; diff --git a/MediaBrowser.Model/Updates/PackageVersionInfo.cs b/MediaBrowser.Model/Updates/PackageVersionInfo.cs index 3eef965dd6..9ef67966bc 100644 --- a/MediaBrowser.Model/Updates/PackageVersionInfo.cs +++ b/MediaBrowser.Model/Updates/PackageVersionInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Users/ForgotPasswordResult.cs b/MediaBrowser.Model/Users/ForgotPasswordResult.cs index 368c642e8f..6bb13d4c9d 100644 --- a/MediaBrowser.Model/Users/ForgotPasswordResult.cs +++ b/MediaBrowser.Model/Users/ForgotPasswordResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Users/PinRedeemResult.cs b/MediaBrowser.Model/Users/PinRedeemResult.cs index ab868cad43..7e4553bac8 100644 --- a/MediaBrowser.Model/Users/PinRedeemResult.cs +++ b/MediaBrowser.Model/Users/PinRedeemResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Users diff --git a/MediaBrowser.Model/Users/UserAction.cs b/MediaBrowser.Model/Users/UserAction.cs index f6bb6451b6..36b8e6ee52 100644 --- a/MediaBrowser.Model/Users/UserAction.cs +++ b/MediaBrowser.Model/Users/UserAction.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index ae2b3fd4e9..9f85022ef5 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 6ef0e44a2b..48e1c94adc 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -230,7 +230,9 @@ namespace MediaBrowser.Providers.Manager /// The result. /// The cancellation token. /// Task. - private async Task RefreshFromProvider(BaseItem item, LibraryOptions libraryOptions, + private async Task RefreshFromProvider( + BaseItem item, + LibraryOptions libraryOptions, IRemoteImageProvider provider, ImageRefreshOptions refreshOptions, TypeOptions savedOptions, @@ -256,20 +258,24 @@ namespace MediaBrowser.Providers.Manager _logger.LogDebug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); - var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery - { - ProviderName = provider.Name, - IncludeAllLanguages = false, - IncludeDisabledProviders = false, - - }, cancellationToken).ConfigureAwait(false); + var images = await _providerManager.GetAvailableRemoteImages( + item, + new RemoteImageQuery(provider.Name) + { + IncludeAllLanguages = false, + IncludeDisabledProviders = false, + }, + cancellationToken).ConfigureAwait(false); var list = images.ToList(); int minWidth; foreach (var imageType in _singularImages) { - if (!IsEnabled(savedOptions, imageType, item)) continue; + if (!IsEnabled(savedOptions, imageType, item)) + { + continue; + } if (!HasImage(item, imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Contains(imageType))) { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 7125f34c55..bf3677850a 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -264,11 +264,7 @@ namespace MediaBrowser.Providers.Manager /// IEnumerable{IImageProvider}. public IEnumerable GetRemoteImageProviderInfo(BaseItem item) { - return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo - { - Name = i.Name, - SupportedImages = i.GetSupportedImages(item).ToArray() - }); + return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(item).ToArray())); } public IEnumerable GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions) From 9cca964b0880dc41792fe27ee03e69609ff2ac7e Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 9 Apr 2020 19:22:29 +0200 Subject: [PATCH 02/72] Address comments --- .../MediaInfo/LiveStreamRequest.cs | 32 +++++++++---------- .../Providers/ImageProviderInfo.cs | 12 +++---- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs index ea5d4e7d77..cce508809b 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs @@ -15,23 +15,6 @@ namespace MediaBrowser.Model.MediaInfo DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http }; } - public string OpenToken { get; set; } - public Guid UserId { get; set; } - public string PlaySessionId { get; set; } - public long? MaxStreamingBitrate { get; set; } - public long? StartTimeTicks { get; set; } - public int? AudioStreamIndex { get; set; } - public int? SubtitleStreamIndex { get; set; } - public int? MaxAudioChannels { get; set; } - public Guid ItemId { get; set; } - public DeviceProfile DeviceProfile { get; set; } - - public bool EnableDirectPlay { get; set; } - public bool EnableDirectStream { get; set; } - public MediaProtocol[] DirectPlayProtocols { get; set; } - - - public LiveStreamRequest(AudioOptions options) { MaxStreamingBitrate = options.MaxBitrate; @@ -47,5 +30,20 @@ namespace MediaBrowser.Model.MediaInfo SubtitleStreamIndex = videoOptions.SubtitleStreamIndex; } } + + public string OpenToken { get; set; } + public Guid UserId { get; set; } + public string PlaySessionId { get; set; } + public long? MaxStreamingBitrate { get; set; } + public long? StartTimeTicks { get; set; } + public int? AudioStreamIndex { get; set; } + public int? SubtitleStreamIndex { get; set; } + public int? MaxAudioChannels { get; set; } + public Guid ItemId { get; set; } + public DeviceProfile DeviceProfile { get; set; } + + public bool EnableDirectPlay { get; set; } + public bool EnableDirectStream { get; set; } + public MediaProtocol[] DirectPlayProtocols { get; set; } } } diff --git a/MediaBrowser.Model/Providers/ImageProviderInfo.cs b/MediaBrowser.Model/Providers/ImageProviderInfo.cs index c63a2ceda8..d1a7c25069 100644 --- a/MediaBrowser.Model/Providers/ImageProviderInfo.cs +++ b/MediaBrowser.Model/Providers/ImageProviderInfo.cs @@ -1,6 +1,3 @@ -#pragma warning disable CS1591 - -using System; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Providers @@ -17,11 +14,14 @@ namespace MediaBrowser.Model.Providers } /// - /// Gets or sets the name. + /// Gets the name. /// /// The name. - public string Name { get; set; } + public string Name { get; } - public ImageType[] SupportedImages { get; set; } + /// + /// Gets the supported image types. + /// + public ImageType[] SupportedImages { get; } } } From 27ce10d0c13bc30fa1b08682e13bab67784b289d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 15 Apr 2020 11:18:13 +0200 Subject: [PATCH 03/72] Fix release build --- MediaBrowser.Model/Providers/ImageProviderInfo.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MediaBrowser.Model/Providers/ImageProviderInfo.cs b/MediaBrowser.Model/Providers/ImageProviderInfo.cs index d1a7c25069..19af81c857 100644 --- a/MediaBrowser.Model/Providers/ImageProviderInfo.cs +++ b/MediaBrowser.Model/Providers/ImageProviderInfo.cs @@ -7,6 +7,11 @@ namespace MediaBrowser.Model.Providers /// public class ImageProviderInfo { + /// + /// Initializes a new instance of the class. + /// + /// The name of the image provider. + /// The image types supported by the image provider. public ImageProviderInfo(string name, ImageType[] supportedImages) { Name = name; From 534b372b8124bbd5343759cf13bed621b0ebed99 Mon Sep 17 00:00:00 2001 From: dkanada Date: Thu, 23 Apr 2020 22:56:32 +0900 Subject: [PATCH 04/72] remove several deprecated utilities in the web package --- .../Api/DashboardService.cs | 155 +---------------- .../Api/PackageCreator.cs | 161 ------------------ 2 files changed, 7 insertions(+), 309 deletions(-) delete mode 100644 MediaBrowser.WebDashboard/Api/PackageCreator.cs diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 133a35527d..55fc463d0e 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -52,12 +52,6 @@ namespace MediaBrowser.WebDashboard.Api public string Name { get; set; } } - [Route("/web/Package", "GET", IsHidden = true)] - public class GetDashboardPackage - { - public string Mode { get; set; } - } - [Route("/robots.txt", "GET", IsHidden = true)] public class GetRobotsTxt { @@ -225,7 +219,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"), () => PackageCreator.ModifyHtml(false, stream, null, _appHost.ApplicationVersionString, null)); + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); } throw new ResourceNotFoundException(); @@ -328,154 +322,19 @@ namespace MediaBrowser.WebDashboard.Api throw new ResourceNotFoundException(); } - var path = request.ResourceName; - - var contentType = MimeTypes.GetMimeType(path); + var path = request?.ResourceName; var basePath = DashboardUIPath; // Bounce them to the startup wizard if it hasn't been completed yet - if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted && - Request.RawUrl.IndexOf("wizard", StringComparison.OrdinalIgnoreCase) == -1 && - PackageCreator.IsCoreHtml(path)) + if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted + && !Request.RawUrl.Contains("wizard", StringComparison.OrdinalIgnoreCase) + && Request.RawUrl.Contains("index", StringComparison.OrdinalIgnoreCase)) { - // But don't redirect if an html import is being requested. - if (path.IndexOf("bower_components", StringComparison.OrdinalIgnoreCase) == -1) - { - Request.Response.Redirect("index.html?start=wizard#!/wizardstart.html"); - return null; - } - } - - var localizationCulture = GetLocalizationCulture(); - - // Don't cache if not configured to do so - // But always cache images to simulate production - if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && - !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) && - !contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) - { - var stream = await GetResourceStream(basePath, path, localizationCulture).ConfigureAwait(false); - return _resultFactory.GetResult(Request, stream, contentType); - } - - TimeSpan? cacheDuration = null; - - // Cache images unconditionally - updates to image files will require new filename - // If there's a version number in the query string we can cache this unconditionally - if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase) || !string.IsNullOrEmpty(request.V)) - { - cacheDuration = TimeSpan.FromDays(365); - } - - var cacheKey = (_appHost.ApplicationVersionString + (localizationCulture ?? string.Empty) + path).GetMD5(); - - // html gets modified on the fly - if (contentType.StartsWith("text/html", StringComparison.OrdinalIgnoreCase)) - { - return await _resultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(basePath, path, localizationCulture)).ConfigureAwait(false); + Request.Response.Redirect("index.html?start=wizard#!/wizardstart.html"); + return null; } return await _resultFactory.GetStaticFileResult(Request, _resourceFileManager.GetResourcePath(basePath, path)).ConfigureAwait(false); } - - private string GetLocalizationCulture() - { - return _serverConfigurationManager.Configuration.UICulture; - } - - /// - /// Gets the resource stream. - /// - private Task GetResourceStream(string basePath, string virtualPath, string localizationCulture) - { - return GetPackageCreator(basePath) - .GetResource(virtualPath, null, localizationCulture, _appHost.ApplicationVersionString); - } - - private PackageCreator GetPackageCreator(string basePath) - { - return new PackageCreator(basePath, _resourceFileManager); - } - - public async Task Get(GetDashboardPackage request) - { - if (!_appConfig.HostWebClient() || DashboardUIPath == null) - { - throw new ResourceNotFoundException(); - } - - var mode = request.Mode; - - var inputPath = string.IsNullOrWhiteSpace(mode) ? - DashboardUIPath - : "C:\\dev\\emby-web-mobile-master\\dist"; - - var targetPath = !string.IsNullOrWhiteSpace(mode) ? - inputPath - : "C:\\dev\\emby-web-mobile\\src"; - - var packageCreator = GetPackageCreator(inputPath); - - if (!string.Equals(inputPath, targetPath, StringComparison.OrdinalIgnoreCase)) - { - try - { - Directory.Delete(targetPath, true); - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting {Path}", targetPath); - } - - CopyDirectory(inputPath, targetPath); - } - - var appVersion = _appHost.ApplicationVersionString; - - await DumpHtml(packageCreator, inputPath, targetPath, mode, appVersion).ConfigureAwait(false); - - return string.Empty; - } - - private async Task DumpHtml(PackageCreator packageCreator, string source, string destination, string mode, string appVersion) - { - foreach (var file in _fileSystem.GetFiles(source)) - { - var filename = file.Name; - - if (!string.Equals(file.Extension, ".html", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - await DumpFile(packageCreator, filename, Path.Combine(destination, filename), mode, appVersion).ConfigureAwait(false); - } - } - - 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 = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - await stream.CopyToAsync(fs).ConfigureAwait(false); - } - } - - private void CopyDirectory(string source, string destination) - { - Directory.CreateDirectory(destination); - - // Now Create all of the directories - foreach (var dirPath in _fileSystem.GetDirectories(source, true)) - { - Directory.CreateDirectory(dirPath.FullName.Replace(source, destination, StringComparison.Ordinal)); - } - - // 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, StringComparison.Ordinal), true); - } - } } } diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs deleted file mode 100644 index b7c15a840e..0000000000 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ /dev/null @@ -1,161 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller; - -namespace MediaBrowser.WebDashboard.Api -{ - public class PackageCreator - { - private readonly string _basePath; - private readonly IResourceFileManager _resourceFileManager; - - public PackageCreator(string basePath, IResourceFileManager resourceFileManager) - { - _basePath = basePath; - _resourceFileManager = resourceFileManager; - } - - public async Task GetResource( - string virtualPath, - string mode, - string localizationCulture, - string appVersion) - { - var resourcePath = _resourceFileManager.GetResourcePath(_basePath, virtualPath); - Stream resourceStream = File.OpenRead(resourcePath); - - if (resourceStream != null && IsCoreHtml(virtualPath)) - { - bool isMainIndexPage = string.Equals(virtualPath, "index.html", StringComparison.OrdinalIgnoreCase); - resourceStream = await ModifyHtml(isMainIndexPage, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); - } - - return resourceStream; - } - - public static bool IsCoreHtml(string path) - { - if (path.IndexOf(".template.html", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - - return string.Equals(Path.GetExtension(path), ".html", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Modifies the source HTML stream by adding common meta tags, css and js. - /// - /// True if the stream contains content for the main index page. - /// The stream whose content should be modified. - /// The client mode ('cordova', 'android', etc). - /// The application version. - /// The localization culture. - /// - /// 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. - /// - public static async Task ModifyHtml( - bool isMainIndexPage, - Stream sourceStream, - string mode, - string appVersion, - string localizationCulture) - { - string html; - using (var reader = new StreamReader(sourceStream, Encoding.UTF8)) - { - html = await reader.ReadToEndAsync().ConfigureAwait(false); - } - - if (isMainIndexPage && !string.IsNullOrWhiteSpace(localizationCulture)) - { - var lang = localizationCulture.Split('-')[0]; - - html = html.Replace("", "" + GetMetaTags(mode), StringComparison.Ordinal); - } - - // Disable embedded scripts from plugins. We'll run them later once resources have loaded - if (html.IndexOf("", "-->", StringComparison.Ordinal); - } - - if (isMainIndexPage) - { - html = html.Replace("", GetCommonJavascript(mode, appVersion) + "", StringComparison.Ordinal); - } - - var bytes = Encoding.UTF8.GetBytes(html); - - return new MemoryStream(bytes); - } - - /// - /// Gets the meta tags. - /// - /// System.String. - private static string GetMetaTags(string mode) - { - var sb = new StringBuilder(); - - if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) - || string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase)) - { - sb.Append(""); - } - - return sb.ToString(); - } - - /// - /// Gets the common javascript. - /// - /// The mode. - /// The version. - /// System.String. - private static string GetCommonJavascript(string mode, string version) - { - var builder = new StringBuilder(); - - builder.Append(""); - - if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) - { - builder.Append(""); - } - - builder.Append(""); - - return builder.ToString(); - } - } -} From 10c2c62f07fb4088480ff580ab67c1bc10a057a4 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 1 Apr 2020 17:52:42 +0200 Subject: [PATCH 05/72] Implement syncplay backend --- .../ApplicationHost.cs | 4 + .../Session/SessionManager.cs | 17 + .../Syncplay/SyncplayController.cs | 418 ++++++++++++++++++ .../Syncplay/SyncplayManager.cs | 248 +++++++++++ MediaBrowser.Api/Syncplay/SyncplayService.cs | 261 +++++++++++ .../Session/ISessionManager.cs | 19 + MediaBrowser.Controller/Syncplay/GroupInfo.cs | 148 +++++++ .../Syncplay/GroupMember.cs | 28 ++ .../Syncplay/ISyncplayController.cs | 61 +++ .../Syncplay/ISyncplayManager.cs | 62 +++ MediaBrowser.Model/Syncplay/GroupInfoModel.cs | 38 ++ .../Syncplay/SyncplayCommand.cs | 32 ++ .../Syncplay/SyncplayCommandType.cs | 21 + .../Syncplay/SyncplayGroupUpdate.cs | 26 ++ .../Syncplay/SyncplayGroupUpdateType.cs | 41 ++ .../Syncplay/SyncplayRequestInfo.cs | 34 ++ .../Syncplay/SyncplayRequestType.cs | 33 ++ 17 files changed, 1491 insertions(+) create mode 100644 Emby.Server.Implementations/Syncplay/SyncplayController.cs create mode 100644 Emby.Server.Implementations/Syncplay/SyncplayManager.cs create mode 100644 MediaBrowser.Api/Syncplay/SyncplayService.cs create mode 100644 MediaBrowser.Controller/Syncplay/GroupInfo.cs create mode 100644 MediaBrowser.Controller/Syncplay/GroupMember.cs create mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayController.cs create mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayManager.cs create mode 100644 MediaBrowser.Model/Syncplay/GroupInfoModel.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommand.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommandType.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestType.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 4d906a1bf8..8419014c2c 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,6 +47,7 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; +using Emby.Server.Implementations.Syncplay; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -80,6 +81,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.TV; +using MediaBrowser.Controller.Syncplay; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.Model.Activity; @@ -643,6 +645,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index c93c02c480..b1519b5729 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -25,6 +25,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Syncplay; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -1154,6 +1155,22 @@ namespace Emby.Server.Implementations.Session await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } + /// + public async Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken) + { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); + await SendMessageToSession(session, "SyncplayCommand", command, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken) + { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); + await SendMessageToSession(session, "SyncplayGroupUpdate", command, cancellationToken).ConfigureAwait(false); + } + private IEnumerable TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs new file mode 100644 index 0000000000..4a20ceba04 --- /dev/null +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -0,0 +1,418 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.Syncplay; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Syncplay +{ + /// + /// Class SyncplayController. + /// + public class SyncplayController : ISyncplayController, IDisposable + { + private enum BroadcastType + { + AllGroup = 0, + SingleUser = 1, + AllExceptUser = 2, + AllReady = 3 + } + + /// + /// The logger. + /// + private readonly ILogger _logger; + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The syncplay manager. + /// + private readonly ISyncplayManager _syncplayManager; + + /// + /// The group to manage. + /// + private readonly GroupInfo _group = new GroupInfo(); + + /// + public Guid GetGroupId() => _group.GroupId; + + /// + public Guid GetPlayingItemId() => _group.PlayingItem.Id; + + /// + public bool IsGroupEmpty() => _group.IsEmpty(); + + private bool _disposed = false; + + public SyncplayController( + ILogger logger, + ISessionManager sessionManager, + ISyncplayManager syncplayManager) + { + _logger = logger; + _sessionManager = sessionManager; + _syncplayManager = syncplayManager; + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + // TODO: use this somewhere + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + private SessionInfo[] FilterUsers(SessionInfo from, BroadcastType type) + { + if (type == BroadcastType.SingleUser) + { + return new SessionInfo[] { from }; + } + else if (type == BroadcastType.AllGroup) + { + return _group.Partecipants.Values.Select( + user => user.Session + ).ToArray(); + } + else if (type == BroadcastType.AllExceptUser) + { + return _group.Partecipants.Values.Select( + user => user.Session + ).Where( + user => !user.Id.Equals(from.Id) + ).ToArray(); + } + else if (type == BroadcastType.AllReady) + { + return _group.Partecipants.Values.Where( + user => !user.IsBuffering + ).Select( + user => user.Session + ).ToArray(); + } + else + { + return new SessionInfo[] {}; + } + } + + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, SyncplayGroupUpdate message) + { + IEnumerable GetTasks() + { + SessionInfo[] users = FilterUsers(from, type); + foreach (var user in users) + { + yield return _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), message, CancellationToken.None); + } + } + + return Task.WhenAll(GetTasks()); + } + + private Task SendCommand(SessionInfo from, BroadcastType type, SyncplayCommand message) + { + IEnumerable GetTasks() + { + SessionInfo[] users = FilterUsers(from, type); + foreach (var user in users) + { + yield return _sessionManager.SendSyncplayCommand(user.Id.ToString(), message, CancellationToken.None); + } + } + + return Task.WhenAll(GetTasks()); + } + + private SyncplayCommand NewSyncplayCommand(SyncplayCommandType type) { + var command = new SyncplayCommand(); + command.GroupId = _group.GroupId.ToString(); + command.Command = type; + command.PositionTicks = _group.PositionTicks; + command.When = _group.LastActivity.ToUniversalTime().ToString("o"); + return command; + } + + private SyncplayGroupUpdate NewSyncplayGroupUpdate(SyncplayGroupUpdateType type, T data) + { + var command = new SyncplayGroupUpdate(); + command.GroupId = _group.GroupId.ToString(); + command.Type = type; + command.Data = data; + return command; + } + + /// + public void InitGroup(SessionInfo user) + { + _group.AddUser(user); + _syncplayManager.MapUserToGroup(user, this); + + _group.PlayingItem = user.FullNowPlayingItem; + _group.IsPaused = true; + _group.PositionTicks = user.PlayState.PositionTicks ??= 0; + _group.LastActivity = DateTime.UtcNow; + + var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); + SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, pauseCommand); + } + + /// + public void UserJoin(SessionInfo user) + { + if (user.NowPlayingItem != null && user.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) + { + _group.AddUser(user); + _syncplayManager.MapUserToGroup(user, this); + + var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); + SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserJoined, user.UserName); + SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + + // Client join and play, syncing will happen client side + if (!_group.IsPaused) + { + var playCommand = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.SingleUser, playCommand); + } + else + { + var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, pauseCommand); + } + } + else + { + var playRequest = new PlayRequest(); + playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; + playRequest.StartPositionTicks = _group.PositionTicks; + var update = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.PrepareSession, playRequest); + SendGroupUpdate(user, BroadcastType.SingleUser, update); + } + } + + /// + public void UserLeave(SessionInfo user) + { + _group.RemoveUser(user); + _syncplayManager.UnmapUserFromGroup(user, this); + + var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); + SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, user.UserName); + SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + } + + /// + public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + { + if (request.Type.Equals(SyncplayRequestType.Play)) + { + if (_group.IsPaused) + { + var delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.IsPaused = false; + _group.LastActivity = DateTime.UtcNow.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.AllGroup, command); + } + else + { + // Client got lost + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + else if (request.Type.Equals(SyncplayRequestType.Pause)) + { + if (!_group.IsPaused) + { + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.AllGroup, command); + } + else + { + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + else if (request.Type.Equals(SyncplayRequestType.Seek)) + { + // Sanitize PositionTicks + var ticks = request.PositionTicks ??= 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem.RunTimeTicks != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; + } + + _group.IsPaused = true; + _group.PositionTicks = ticks; + _group.LastActivity = DateTime.UtcNow; + + var command = NewSyncplayCommand(SyncplayCommandType.Seek); + SendCommand(user, BroadcastType.AllGroup, command); + } + // TODO: client does not implement this yet + else if (request.Type.Equals(SyncplayRequestType.Buffering)) + { + if (!_group.IsPaused) + { + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + _group.SetBuffering(user, true); + + // Send pause command to all non-buffering users + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.AllReady, command); + + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, user.UserName); + SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + } + else + { + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + // TODO: client does not implement this yet + else if (request.Type.Equals(SyncplayRequestType.BufferingComplete)) + { + if (_group.IsPaused) + { + _group.SetBuffering(user, false); + + if (_group.IsBuffering()) { + // Others are buffering, tell this client to pause when ready + var when = request.When ??= DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; + + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + command.When = currentTime.AddMilliseconds( + delay + ).ToUniversalTime().ToString("o"); + SendCommand(user, BroadcastType.SingleUser, command); + } + else + { + // Let other clients resume as soon as the buffering client catches up + var when = request.When ??= DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; + + _group.IsPaused = false; + + if (delay > _group.GetHighestPing() * 2) + { + // Client that was buffering is recovering, notifying others to resume + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.AllExceptUser, command); + } + else + { + // Client, that was buffering, resumed playback but did not update others in time + delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.AllGroup, command); + } + } + } + else + { + // Make sure client has latest group state + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + else if (request.Type.Equals(SyncplayRequestType.KeepAlive)) + { + _group.UpdatePing(user, request.Ping ??= _group.DefaulPing); + + var keepAlive = new SyncplayGroupUpdate(); + keepAlive.GroupId = _group.GroupId.ToString(); + keepAlive.Type = SyncplayGroupUpdateType.KeepAlive; + SendGroupUpdate(user, BroadcastType.SingleUser, keepAlive); + } + } + + /// + public GroupInfoView GetInfo() + { + var info = new GroupInfoView(); + info.GroupId = GetGroupId().ToString(); + info.PlayingItemName = _group.PlayingItem.Name; + info.PlayingItemId = _group.PlayingItem.Id.ToString(); + info.PositionTicks = _group.PositionTicks; + info.Partecipants = _group.Partecipants.Values.Select(user => user.Session.UserName).ToArray(); + return info; + } + } +} diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs new file mode 100644 index 0000000000..6bfd6aa9b0 --- /dev/null +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Logging; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Syncplay; + +namespace Emby.Server.Implementations.Syncplay +{ + /// + /// Class SyncplayManager. + /// + public class SyncplayManager : ISyncplayManager, IDisposable + { + /// + /// The logger. + /// + private readonly ILogger _logger; + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The map between users and groups. + /// + private readonly ConcurrentDictionary _userToGroupMap = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// The groups. + /// + private readonly ConcurrentDictionary _groups = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + private bool _disposed = false; + + public SyncplayManager( + ILogger logger, + ISessionManager sessionManager) + { + _logger = logger; + _sessionManager = sessionManager; + + _sessionManager.SessionEnded += _sessionManager_SessionEnded; + _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + } + + /// + /// Gets all groups. + /// + /// All groups. + public IEnumerable Groups => _groups.Values; + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + + _disposed = true; + } + + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + { + LeaveGroup(e.SessionInfo); + } + + void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + { + LeaveGroup(e.Session); + } + + private bool IsUserInGroup(SessionInfo user) + { + return _userToGroupMap.ContainsKey(user.Id); + } + + private Guid? GetUserGroup(SessionInfo user) + { + ISyncplayController group; + _userToGroupMap.TryGetValue(user.Id, out group); + if (group != null) + { + return group.GetGroupId(); + } + else + { + return null; + } + } + + /// + public void NewGroup(SessionInfo user) + { + if (IsUserInGroup(user)) + { + LeaveGroup(user); + } + + var group = new SyncplayController(_logger, _sessionManager, this); + _groups[group.GetGroupId().ToString()] = group; + + group.InitGroup(user); + } + + /// + public void JoinGroup(SessionInfo user, string groupId) + { + if (IsUserInGroup(user)) + { + if (GetUserGroup(user).Equals(groupId)) return; + LeaveGroup(user); + } + + ISyncplayController group; + _groups.TryGetValue(groupId, out group); + + if (group == null) + { + _logger.LogError("Syncplaymanager JoinGroup: " + groupId + " does not exist."); + + var update = new SyncplayGroupUpdate(); + update.Type = SyncplayGroupUpdateType.NotInGroup; + _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + return; + } + group.UserJoin(user); + } + + /// + public void LeaveGroup(SessionInfo user) + { + ISyncplayController group; + _userToGroupMap.TryGetValue(user.Id, out group); + + if (group == null) + { + _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + + var update = new SyncplayGroupUpdate(); + update.Type = SyncplayGroupUpdateType.NotInGroup; + _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + return; + } + group.UserLeave(user); + + if (group.IsGroupEmpty()) + { + _groups.Remove(group.GetGroupId().ToString(), out _); + } + } + + /// + public List ListGroups(SessionInfo user) + { + // Filter by playing item if the user is viewing something already + if (user.NowPlayingItem != null) + { + return _groups.Values.Where( + group => group.GetPlayingItemId().Equals(user.FullNowPlayingItem.Id) + ).Select( + group => group.GetInfo() + ).ToList(); + } + // Otherwise show all available groups + else + { + return _groups.Values.Select( + group => group.GetInfo() + ).ToList(); + } + } + + /// + public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + { + ISyncplayController group; + _userToGroupMap.TryGetValue(user.Id, out group); + + if (group == null) + { + _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + + var update = new SyncplayGroupUpdate(); + update.Type = SyncplayGroupUpdateType.NotInGroup; + _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + return; + } + group.HandleRequest(user, request); + } + + /// + public void MapUserToGroup(SessionInfo user, ISyncplayController group) + { + if (IsUserInGroup(user)) + { + throw new InvalidOperationException("User in other group already!"); + } + _userToGroupMap[user.Id] = group; + } + + /// + public void UnmapUserFromGroup(SessionInfo user, ISyncplayController group) + { + if (!IsUserInGroup(user)) + { + throw new InvalidOperationException("User not in any group!"); + } + + ISyncplayController tempGroup; + _userToGroupMap.Remove(user.Id, out tempGroup); + + if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) + { + throw new InvalidOperationException("User was in wrong group!"); + } + } + } +} diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs new file mode 100644 index 0000000000..f17cca9ee7 --- /dev/null +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.Syncplay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Api.Syncplay +{ + [Route("/Syncplay/{SessionId}/NewGroup", "POST", Summary = "Create a new Syncplay group")] + [Authenticated] + public class SyncplayNewGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing Syncplay group")] + [Authenticated] + public class SyncplayJoinGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string GroupId { get; set; } + } + + [Route("/Syncplay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined Syncplay group")] + [Authenticated] + public class SyncplayLeaveGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups playing same item")] + [Authenticated] + public class SyncplayListGroups : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")] + [Authenticated] + public class SyncplayPlayRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in Syncplay group")] + [Authenticated] + public class SyncplayPauseRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in Syncplay group")] + [Authenticated] + public class SyncplaySeekRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + } + + [Route("/Syncplay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in Syncplay group while buffering")] + [Authenticated] + public class SyncplayBufferingRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string When { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + + [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool Resume { get; set; } + } + + [Route("/Syncplay/{SessionId}/KeepAlive", "POST", Summary = "Keep session alive")] + [Authenticated] + public class SyncplayKeepAlive : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] + public double Ping { get; set; } + } + + [Route("/Syncplay/{SessionId}/GetUtcTime", "POST", Summary = "Get UtcTime")] + [Authenticated] + public class SyncplayGetUtcTime : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + /// + /// Class SyncplayService. + /// + public class SyncplayService : BaseApiService + { + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + private readonly ISessionContext _sessionContext; + + /// + /// The Syncplay manager. + /// + private readonly ISyncplayManager _syncplayManager; + + public SyncplayService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + ISessionManager sessionManager, + ISessionContext sessionContext, + ISyncplayManager syncplayManager) + : base(logger, serverConfigurationManager, httpResultFactory) + { + _sessionManager = sessionManager; + _sessionContext = sessionContext; + _syncplayManager = syncplayManager; + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayNewGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncplayManager.NewGroup(currentSession); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayJoinGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncplayManager.JoinGroup(currentSession, request.GroupId); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayLeaveGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncplayManager.LeaveGroup(currentSession); + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The requested list of groups. + public List Post(SyncplayListGroups request) + { + var currentSession = GetSession(_sessionContext); + return _syncplayManager.ListGroups(currentSession); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayPlayRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.Play; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayPauseRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.Pause; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplaySeekRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.Seek; + syncplayRequest.PositionTicks = request.PositionTicks; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayBufferingRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = request.Resume ? SyncplayRequestType.BufferingComplete : SyncplayRequestType.Buffering; + syncplayRequest.When = DateTime.Parse(request.When); + syncplayRequest.PositionTicks = request.PositionTicks; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayKeepAlive request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.KeepAlive; + syncplayRequest.Ping = Convert.ToInt64(request.Ping); + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The current UTC time. + public string Post(SyncplayGetUtcTime request) + { + return DateTime.UtcNow.ToUniversalTime().ToString("o"); + } + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 771027103b..4bfc0c73f3 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Syncplay; namespace MediaBrowser.Controller.Session { @@ -140,6 +141,24 @@ namespace MediaBrowser.Controller.Session /// Task. Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken); + /// + /// Sends the SyncplayCommand. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken); + + /// + /// Sends the SyncplayGroupUpdate. + /// + /// The session id. + /// The group update. + /// The cancellation token. + /// Task. + Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken); + /// /// Sends the browse command. /// diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs new file mode 100644 index 0000000000..d37e8563bc --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Class GroupInfo. + /// + public class GroupInfo + { + /// + /// Default ping value used for users. + /// + public readonly long DefaulPing = 500; + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public readonly Guid GroupId = Guid.NewGuid(); + + /// + /// Gets or sets the playing item. + /// + /// The playing item. + public BaseItem PlayingItem { get; set; } + + /// + /// Gets or sets whether playback is paused. + /// + /// Playback is paused. + public bool IsPaused { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the last activity. + /// + /// The last activity. + public DateTime LastActivity { get; set; } + + /// + /// Gets the partecipants. + /// + /// The partecipants. + public readonly ConcurrentDictionary Partecipants = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Checks if a user is in this group. + /// + /// true if the user is in this group; false otherwise. + public bool ContainsUser(string sessionId) + { + return Partecipants.ContainsKey(sessionId); + } + + /// + /// Adds the user to the group. + /// + /// The session. + public void AddUser(SessionInfo user) + { + if (ContainsUser(user.Id.ToString())) return; + var member = new GroupMember(); + member.Session = user; + member.Ping = DefaulPing; + member.IsBuffering = false; + Partecipants[user.Id.ToString()] = member; + } + + /// + /// Removes the user from the group. + /// + /// The session. + + public void RemoveUser(SessionInfo user) + { + if (!ContainsUser(user.Id.ToString())) return; + GroupMember member; + Partecipants.Remove(user.Id.ToString(), out member); + } + + /// + /// Updates the ping of a user. + /// + /// The session. + /// The ping. + public void UpdatePing(SessionInfo user, long ping) + { + if (!ContainsUser(user.Id.ToString())) return; + Partecipants[user.Id.ToString()].Ping = ping; + } + + /// + /// Gets the highest ping in the group. + /// + /// The highest ping in the group. + public long GetHighestPing() + { + long max = Int64.MinValue; + foreach (var user in Partecipants.Values) + { + max = Math.Max(max, user.Ping); + } + return max; + } + + /// + /// Sets the user's buffering state. + /// + /// The session. + /// The state. + public void SetBuffering(SessionInfo user, bool isBuffering) + { + if (!ContainsUser(user.Id.ToString())) return; + Partecipants[user.Id.ToString()].IsBuffering = isBuffering; + } + + /// + /// Gets the group buffering state. + /// + /// true if there is a user buffering in the group; false otherwise. + public bool IsBuffering() + { + foreach (var user in Partecipants.Values) + { + if (user.IsBuffering) return true; + } + return false; + } + + /// + /// Checks if the group is empty. + /// + /// true if the group is empty; false otherwise. + public bool IsEmpty() + { + return Partecipants.Count == 0; + } + } +} diff --git a/MediaBrowser.Controller/Syncplay/GroupMember.cs b/MediaBrowser.Controller/Syncplay/GroupMember.cs new file mode 100644 index 0000000000..7630428d76 --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/GroupMember.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Class GroupMember. + /// + public class GroupMember + { + /// + /// Gets or sets whether this member is buffering. + /// + /// true if member is buffering; false otherwise. + public bool IsBuffering { get; set; } + + /// + /// Gets or sets the session. + /// + /// The session. + public SessionInfo Session { get; set; } + + /// + /// Gets or sets the ping. + /// + /// The ping. + public long Ping { get; set; } + } +} diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs new file mode 100644 index 0000000000..c9465b27a8 --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -0,0 +1,61 @@ +using System; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Syncplay; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Interface ISyncplayController. + /// + public interface ISyncplayController + { + /// + /// Gets the group id. + /// + /// The group id. + Guid GetGroupId(); + + /// + /// Gets the playing item id. + /// + /// The playing item id. + Guid GetPlayingItemId(); + + /// + /// Checks if the group is empty. + /// + /// If the group is empty. + bool IsGroupEmpty(); + + /// + /// Initializes the group with the user's info. + /// + /// The session. + void InitGroup(SessionInfo user); + + /// + /// Adds the user to the group. + /// + /// The session. + void UserJoin(SessionInfo user); + + /// + /// Removes the user from the group. + /// + /// The session. + void UserLeave(SessionInfo user); + + /// + /// Handles the requested action by the user. + /// + /// The session. + /// The requested action. + void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + + /// + /// Gets the info about the group for the clients. + /// + /// The group info for the clients. + GroupInfoView GetInfo(); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs new file mode 100644 index 0000000000..ec91ea69d3 --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Syncplay; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Interface ISyncplayManager. + /// + public interface ISyncplayManager + { + /// + /// Creates a new group. + /// + /// The user that's creating the group. + void NewGroup(SessionInfo user); + + /// + /// Adds the user to a group. + /// + /// The session. + /// The group id. + void JoinGroup(SessionInfo user, string groupId); + + /// + /// Removes the user from a group. + /// + /// The session. + void LeaveGroup(SessionInfo user); + + /// + /// Gets list of available groups for a user. + /// + /// The user. + /// The list of available groups. + List ListGroups(SessionInfo user); + + /// + /// Handle a request by a user in a group. + /// + /// The session. + /// The request. + void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + + /// + /// Maps a user to a group. + /// + /// The user. + /// The group. + /// + void MapUserToGroup(SessionInfo user, ISyncplayController group); + + /// + /// Unmaps a user from a group. + /// + /// The user. + /// The group. + /// + void UnmapUserFromGroup(SessionInfo user, ISyncplayController group); + } +} diff --git a/MediaBrowser.Model/Syncplay/GroupInfoModel.cs b/MediaBrowser.Model/Syncplay/GroupInfoModel.cs new file mode 100644 index 0000000000..599c0dbfc9 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/GroupInfoModel.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class GroupInfoModel. + /// + public class GroupInfoView + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public string PlayingItemId { get; set; } + + /// + /// Gets or sets the playing item name. + /// + /// The playing item name. + public string PlayingItemName { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the partecipants. + /// + /// The partecipants. + public string[] Partecipants { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommand.cs b/MediaBrowser.Model/Syncplay/SyncplayCommand.cs new file mode 100644 index 0000000000..769316e805 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayCommand.cs @@ -0,0 +1,32 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SyncplayCommand. + /// + public class SyncplayCommand + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the UTC time when to execute the command. + /// + /// The UTC time when to execute the command. + public string When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the command. + /// + /// The command. + public SyncplayCommandType Command { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs b/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs new file mode 100644 index 0000000000..87b9ad66d6 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs @@ -0,0 +1,21 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SyncplayCommandType. + /// + public enum SyncplayCommandType + { + /// + /// The play command. Instructs users to start playback. + /// + Play = 0, + /// + /// The pause command. Instructs users to pause playback. + /// + Pause = 1, + /// + /// The seek command. Instructs users to seek to a specified time. + /// + Seek = 2 + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs new file mode 100644 index 0000000000..c5c2f35404 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs @@ -0,0 +1,26 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SyncplayGroupUpdate. + /// + public class SyncplayGroupUpdate + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the update type. + /// + /// The update type. + public SyncplayGroupUpdateType Type { get; set; } + + /// + /// Gets or sets the data. + /// + /// The data. + public T Data { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs new file mode 100644 index 0000000000..c7c5f534dc --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs @@ -0,0 +1,41 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SyncplayGroupUpdateType + /// + public enum SyncplayGroupUpdateType + { + /// + /// The user-joined update. Tells members of a group about a new user. + /// + UserJoined = 0, + /// + /// The user-left update. Tells members of a group that a user left. + /// + UserLeft = 1, + /// + /// The group-joined update. Tells a user that the group has been joined. + /// + GroupJoined = 2, + /// + /// The group-left update. Tells a user that the group has been left. + /// + GroupLeft = 3, + /// + /// The group-wait update. Tells members of the group that a user is buffering. + /// + GroupWait = 4, + /// + /// The prepare-session update. Tells a user to load some content. + /// + PrepareSession = 5, + /// + /// The keep-alive update. An update to keep alive the socket. + /// + KeepAlive = 6, + /// + /// The not-in-group update. Tells a user that no group has been joined. + /// + NotInGroup = 7 + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs new file mode 100644 index 0000000000..7dba74ae94 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs @@ -0,0 +1,34 @@ +using System; + +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SyncplayRequestInfo. + /// + public class SyncplayRequestInfo + { + /// + /// Gets or sets the request type. + /// + /// The request type. + public SyncplayRequestType Type; + + /// + /// Gets or sets when the request has been made by the client. + /// + /// The date of the request. + public DateTime? When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the ping time. + /// + /// The ping time. + public long? Ping { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs new file mode 100644 index 0000000000..44d7a0af26 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs @@ -0,0 +1,33 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SyncplayRequestType + /// + public enum SyncplayRequestType + { + /// + /// A user is requesting a play command for the group. + /// + Play = 0, + /// + /// A user is requesting a pause command for the group. + /// + Pause = 1, + /// + /// A user is requesting a seek command for the group. + /// + Seek = 2, + /// + /// A user is signaling that playback is buffering. + /// + Buffering = 3, + /// + /// A user is signaling that playback resumed. + /// + BufferingComplete = 4, + /// + /// A user is reporting its ping. + /// + KeepAlive = 5 + } +} From b3354ec6374e2491679c699aaff8ee407dd0ac7c Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 3 Apr 2020 10:11:55 +0200 Subject: [PATCH 06/72] Ignore unrelated events --- Emby.Server.Implementations/Syncplay/SyncplayManager.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 6bfd6aa9b0..7583793bb5 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -91,12 +91,16 @@ namespace Emby.Server.Implementations.Syncplay void _sessionManager_SessionEnded(object sender, SessionEventArgs e) { - LeaveGroup(e.SessionInfo); + var user = e.SessionInfo; + if (!IsUserInGroup(user)) return; + LeaveGroup(user); } void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) { - LeaveGroup(e.Session); + var user = e.Session; + if (!IsUserInGroup(user)) return; + LeaveGroup(user); } private bool IsUserInGroup(SessionInfo user) From f273995f5bd89f12322d80f3009ad6d8d20b8e81 Mon Sep 17 00:00:00 2001 From: gion Date: Sat, 4 Apr 2020 17:56:21 +0200 Subject: [PATCH 07/72] Refactor: rename user to session --- .../Syncplay/SyncplayController.cs | 124 +++++++++--------- .../Syncplay/SyncplayManager.cs | 88 ++++++------- MediaBrowser.Controller/Syncplay/GroupInfo.cs | 62 ++++----- .../Syncplay/ISyncplayController.cs | 24 ++-- .../Syncplay/ISyncplayManager.cs | 40 +++--- 5 files changed, 169 insertions(+), 169 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index 4a20ceba04..b156e5a879 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -19,8 +19,8 @@ namespace Emby.Server.Implementations.Syncplay private enum BroadcastType { AllGroup = 0, - SingleUser = 1, - AllExceptUser = 2, + SingleSession = 1, + AllExceptSession = 2, AllReady = 3 } @@ -95,32 +95,32 @@ namespace Emby.Server.Implementations.Syncplay } } - private SessionInfo[] FilterUsers(SessionInfo from, BroadcastType type) + private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) { - if (type == BroadcastType.SingleUser) + if (type == BroadcastType.SingleSession) { return new SessionInfo[] { from }; } else if (type == BroadcastType.AllGroup) { return _group.Partecipants.Values.Select( - user => user.Session + session => session.Session ).ToArray(); } - else if (type == BroadcastType.AllExceptUser) + else if (type == BroadcastType.AllExceptSession) { return _group.Partecipants.Values.Select( - user => user.Session + session => session.Session ).Where( - user => !user.Id.Equals(from.Id) + session => !session.Id.Equals(from.Id) ).ToArray(); } else if (type == BroadcastType.AllReady) { return _group.Partecipants.Values.Where( - user => !user.IsBuffering + session => !session.IsBuffering ).Select( - user => user.Session + session => session.Session ).ToArray(); } else @@ -133,10 +133,10 @@ namespace Emby.Server.Implementations.Syncplay { IEnumerable GetTasks() { - SessionInfo[] users = FilterUsers(from, type); - foreach (var user in users) + SessionInfo[] sessions = FilterSessions(from, type); + foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, CancellationToken.None); } } @@ -147,10 +147,10 @@ namespace Emby.Server.Implementations.Syncplay { IEnumerable GetTasks() { - SessionInfo[] users = FilterUsers(from, type); - foreach (var user in users) + SessionInfo[] sessions = FilterSessions(from, type); + foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayCommand(user.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, CancellationToken.None); } } @@ -176,46 +176,46 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void InitGroup(SessionInfo user) + public void InitGroup(SessionInfo session) { - _group.AddUser(user); - _syncplayManager.MapUserToGroup(user, this); + _group.AddSession(session); + _syncplayManager.MapSessionToGroup(session, this); - _group.PlayingItem = user.FullNowPlayingItem; + _group.PlayingItem = session.FullNowPlayingItem; _group.IsPaused = true; - _group.PositionTicks = user.PlayState.PositionTicks ??= 0; + _group.PositionTicks = session.PlayState.PositionTicks ??= 0; _group.LastActivity = DateTime.UtcNow; - var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); + SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.SingleUser, pauseCommand); + SendCommand(session, BroadcastType.SingleSession, pauseCommand); } /// - public void UserJoin(SessionInfo user) + public void SessionJoin(SessionInfo session) { - if (user.NowPlayingItem != null && user.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) + if (session.NowPlayingItem != null && session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) { - _group.AddUser(user); - _syncplayManager.MapUserToGroup(user, this); + _group.AddSession(session); + _syncplayManager.MapSessionToGroup(session, this); - var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); - SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); + SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserJoined, user.UserName); - SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserJoined, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); // Client join and play, syncing will happen client side if (!_group.IsPaused) { var playCommand = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.SingleUser, playCommand); + SendCommand(session, BroadcastType.SingleSession, playCommand); } else { var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.SingleUser, pauseCommand); + SendCommand(session, BroadcastType.SingleSession, pauseCommand); } } else @@ -224,25 +224,25 @@ namespace Emby.Server.Implementations.Syncplay playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; var update = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(user, BroadcastType.SingleUser, update); + SendGroupUpdate(session, BroadcastType.SingleSession, update); } } /// - public void UserLeave(SessionInfo user) + public void SessionLeave(SessionInfo session) { - _group.RemoveUser(user); - _syncplayManager.UnmapUserFromGroup(user, this); + _group.RemoveSession(session); + _syncplayManager.UnmapSessionFromGroup(session, this); - var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); + SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, user.UserName); - SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } /// - public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) { if (request.Type.Equals(SyncplayRequestType.Play)) { @@ -257,13 +257,13 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command); } else { // Client got lost var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } } else if (request.Type.Equals(SyncplayRequestType.Pause)) @@ -277,12 +277,12 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; var command = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command); } else { var command = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } } else if (request.Type.Equals(SyncplayRequestType.Seek)) @@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = DateTime.UtcNow; var command = NewSyncplayCommand(SyncplayCommandType.Seek); - SendCommand(user, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command); } // TODO: client does not implement this yet else if (request.Type.Equals(SyncplayRequestType.Buffering)) @@ -314,19 +314,19 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = currentTime; _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - _group.SetBuffering(user, true); + _group.SetBuffering(session, true); - // Send pause command to all non-buffering users + // Send pause command to all non-buffering sessions var command = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.AllReady, command); + SendCommand(session, BroadcastType.AllReady, command); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, user.UserName); - SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } else { var command = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } } // TODO: client does not implement this yet @@ -334,7 +334,7 @@ namespace Emby.Server.Implementations.Syncplay { if (_group.IsPaused) { - _group.SetBuffering(user, false); + _group.SetBuffering(session, false); if (_group.IsBuffering()) { // Others are buffering, tell this client to pause when ready @@ -348,7 +348,7 @@ namespace Emby.Server.Implementations.Syncplay command.When = currentTime.AddMilliseconds( delay ).ToUniversalTime().ToString("o"); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } else { @@ -368,7 +368,7 @@ namespace Emby.Server.Implementations.Syncplay delay ); var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.AllExceptUser, command); + SendCommand(session, BroadcastType.AllExceptSession, command); } else { @@ -381,7 +381,7 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command); } } } @@ -389,17 +389,17 @@ namespace Emby.Server.Implementations.Syncplay { // Make sure client has latest group state var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } } else if (request.Type.Equals(SyncplayRequestType.KeepAlive)) { - _group.UpdatePing(user, request.Ping ??= _group.DefaulPing); + _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); var keepAlive = new SyncplayGroupUpdate(); keepAlive.GroupId = _group.GroupId.ToString(); keepAlive.Type = SyncplayGroupUpdateType.KeepAlive; - SendGroupUpdate(user, BroadcastType.SingleUser, keepAlive); + SendGroupUpdate(session, BroadcastType.SingleSession, keepAlive); } } @@ -411,7 +411,7 @@ namespace Emby.Server.Implementations.Syncplay info.PlayingItemName = _group.PlayingItem.Name; info.PlayingItemId = _group.PlayingItem.Id.ToString(); info.PositionTicks = _group.PositionTicks; - info.Partecipants = _group.Partecipants.Values.Select(user => user.Session.UserName).ToArray(); + info.Partecipants = _group.Partecipants.Values.Select(session => session.Session.UserName).ToArray(); return info; } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 7583793bb5..f76d243d58 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -27,9 +27,9 @@ namespace Emby.Server.Implementations.Syncplay private readonly ISessionManager _sessionManager; /// - /// The map between users and groups. + /// The map between sessions and groups. /// - private readonly ConcurrentDictionary _userToGroupMap = + private readonly ConcurrentDictionary _sessionToGroupMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// @@ -91,27 +91,27 @@ namespace Emby.Server.Implementations.Syncplay void _sessionManager_SessionEnded(object sender, SessionEventArgs e) { - var user = e.SessionInfo; - if (!IsUserInGroup(user)) return; - LeaveGroup(user); + var session = e.SessionInfo; + if (!IsSessionInGroup(session)) return; + LeaveGroup(session); } void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) { - var user = e.Session; - if (!IsUserInGroup(user)) return; - LeaveGroup(user); + var session = e.Session; + if (!IsSessionInGroup(session)) return; + LeaveGroup(session); } - private bool IsUserInGroup(SessionInfo user) + private bool IsSessionInGroup(SessionInfo session) { - return _userToGroupMap.ContainsKey(user.Id); + return _sessionToGroupMap.ContainsKey(session.Id); } - private Guid? GetUserGroup(SessionInfo user) + private Guid? GetSessionGroup(SessionInfo session) { ISyncplayController group; - _userToGroupMap.TryGetValue(user.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out group); if (group != null) { return group.GetGroupId(); @@ -123,26 +123,26 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void NewGroup(SessionInfo user) + public void NewGroup(SessionInfo session) { - if (IsUserInGroup(user)) { - LeaveGroup(user); + if (IsSessionInGroup(session)) + LeaveGroup(session); } var group = new SyncplayController(_logger, _sessionManager, this); _groups[group.GetGroupId().ToString()] = group; - group.InitGroup(user); + group.InitGroup(session); } /// - public void JoinGroup(SessionInfo user, string groupId) + public void JoinGroup(SessionInfo session, string groupId) { - if (IsUserInGroup(user)) + if (IsSessionInGroup(session)) { - if (GetUserGroup(user).Equals(groupId)) return; - LeaveGroup(user); + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session); } ISyncplayController group; @@ -154,28 +154,28 @@ namespace Emby.Server.Implementations.Syncplay var update = new SyncplayGroupUpdate(); update.Type = SyncplayGroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } - group.UserJoin(user); + group.SessionJoin(session); } /// - public void LeaveGroup(SessionInfo user) + public void LeaveGroup(SessionInfo session) { ISyncplayController group; - _userToGroupMap.TryGetValue(user.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out group); if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); var update = new SyncplayGroupUpdate(); update.Type = SyncplayGroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } - group.UserLeave(user); + group.SessionLeave(session); if (group.IsGroupEmpty()) { @@ -184,13 +184,13 @@ namespace Emby.Server.Implementations.Syncplay } /// - public List ListGroups(SessionInfo user) + public List ListGroups(SessionInfo session) { // Filter by playing item if the user is viewing something already - if (user.NowPlayingItem != null) + if (session.NowPlayingItem != null) { return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(user.FullNowPlayingItem.Id) + group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) ).Select( group => group.GetInfo() ).ToList(); @@ -205,47 +205,47 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) { ISyncplayController group; - _userToGroupMap.TryGetValue(user.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out group); if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); var update = new SyncplayGroupUpdate(); update.Type = SyncplayGroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } - group.HandleRequest(user, request); + group.HandleRequest(session, request); } /// - public void MapUserToGroup(SessionInfo user, ISyncplayController group) + public void MapSessionToGroup(SessionInfo session, ISyncplayController group) { - if (IsUserInGroup(user)) + if (IsSessionInGroup(session)) { - throw new InvalidOperationException("User in other group already!"); + throw new InvalidOperationException("Session in other group already!"); } - _userToGroupMap[user.Id] = group; + _sessionToGroupMap[session.Id] = group; } /// - public void UnmapUserFromGroup(SessionInfo user, ISyncplayController group) + public void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group) { - if (!IsUserInGroup(user)) + if (!IsSessionInGroup(session)) { - throw new InvalidOperationException("User not in any group!"); + throw new InvalidOperationException("Session not in any group!"); } ISyncplayController tempGroup; - _userToGroupMap.Remove(user.Id, out tempGroup); + _sessionToGroupMap.Remove(session.Id, out tempGroup); if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) { - throw new InvalidOperationException("User was in wrong group!"); + throw new InvalidOperationException("Session was in wrong group!"); } } } diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs index d37e8563bc..42e85ef864 100644 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Syncplay public class GroupInfo { /// - /// Default ping value used for users. + /// Default ping value used for sessions. /// public readonly long DefaulPing = 500; /// @@ -53,85 +53,85 @@ namespace MediaBrowser.Controller.Syncplay new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// - /// Checks if a user is in this group. + /// Checks if a session is in this group. /// - /// true if the user is in this group; false otherwise. - public bool ContainsUser(string sessionId) + /// true if the session is in this group; false otherwise. + public bool ContainsSession(string sessionId) { return Partecipants.ContainsKey(sessionId); } /// - /// Adds the user to the group. + /// Adds the session to the group. /// - /// The session. - public void AddUser(SessionInfo user) + /// The session. + public void AddSession(SessionInfo session) { - if (ContainsUser(user.Id.ToString())) return; + if (ContainsSession(session.Id.ToString())) return; var member = new GroupMember(); - member.Session = user; + member.Session = session; member.Ping = DefaulPing; member.IsBuffering = false; - Partecipants[user.Id.ToString()] = member; + Partecipants[session.Id.ToString()] = member; } /// - /// Removes the user from the group. + /// Removes the session from the group. /// - /// The session. + /// The session. - public void RemoveUser(SessionInfo user) + public void RemoveSession(SessionInfo session) { - if (!ContainsUser(user.Id.ToString())) return; + if (!ContainsSession(session.Id.ToString())) return; GroupMember member; - Partecipants.Remove(user.Id.ToString(), out member); + Partecipants.Remove(session.Id.ToString(), out member); } /// - /// Updates the ping of a user. + /// Updates the ping of a session. /// - /// The session. + /// The session. /// The ping. - public void UpdatePing(SessionInfo user, long ping) + public void UpdatePing(SessionInfo session, long ping) { - if (!ContainsUser(user.Id.ToString())) return; - Partecipants[user.Id.ToString()].Ping = ping; + if (!ContainsSession(session.Id.ToString())) return; + Partecipants[session.Id.ToString()].Ping = ping; } /// /// Gets the highest ping in the group. /// - /// The highest ping in the group. + /// The highest ping in the group. public long GetHighestPing() { long max = Int64.MinValue; - foreach (var user in Partecipants.Values) + foreach (var session in Partecipants.Values) { - max = Math.Max(max, user.Ping); + max = Math.Max(max, session.Ping); } return max; } /// - /// Sets the user's buffering state. + /// Sets the session's buffering state. /// - /// The session. + /// The session. /// The state. - public void SetBuffering(SessionInfo user, bool isBuffering) + public void SetBuffering(SessionInfo session, bool isBuffering) { - if (!ContainsUser(user.Id.ToString())) return; - Partecipants[user.Id.ToString()].IsBuffering = isBuffering; + if (!ContainsSession(session.Id.ToString())) return; + Partecipants[session.Id.ToString()].IsBuffering = isBuffering; } /// /// Gets the group buffering state. /// - /// true if there is a user buffering in the group; false otherwise. + /// true if there is a session buffering in the group; false otherwise. public bool IsBuffering() { - foreach (var user in Partecipants.Values) + foreach (var session in Partecipants.Values) { - if (user.IsBuffering) return true; + if (session.IsBuffering) return true; } return false; } diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs index c9465b27a8..d35ae31019 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -28,29 +28,29 @@ namespace MediaBrowser.Controller.Syncplay bool IsGroupEmpty(); /// - /// Initializes the group with the user's info. + /// Initializes the group with the session's info. /// - /// The session. - void InitGroup(SessionInfo user); + /// The session. + void InitGroup(SessionInfo session); /// - /// Adds the user to the group. + /// Adds the session to the group. /// - /// The session. - void UserJoin(SessionInfo user); + /// The session. + void SessionJoin(SessionInfo session); /// - /// Removes the user from the group. + /// Removes the session from the group. /// - /// The session. - void UserLeave(SessionInfo user); + /// The session. + void SessionLeave(SessionInfo session); /// - /// Handles the requested action by the user. + /// Handles the requested action by the session. /// - /// The session. + /// The session. /// The requested action. - void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, SyncplayRequestInfo request); /// /// Gets the info about the group for the clients. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index ec91ea69d3..09920a19ff 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -13,50 +13,50 @@ namespace MediaBrowser.Controller.Syncplay /// /// Creates a new group. /// - /// The user that's creating the group. - void NewGroup(SessionInfo user); + /// The session that's creating the group. + void NewGroup(SessionInfo session); /// - /// Adds the user to a group. + /// Adds the session to a group. /// - /// The session. + /// The session. /// The group id. - void JoinGroup(SessionInfo user, string groupId); + void JoinGroup(SessionInfo session, string groupId); /// - /// Removes the user from a group. + /// Removes the session from a group. /// - /// The session. - void LeaveGroup(SessionInfo user); + /// The session. + void LeaveGroup(SessionInfo session); /// - /// Gets list of available groups for a user. + /// Gets list of available groups for a session. /// - /// The user. + /// The session. /// The list of available groups. - List ListGroups(SessionInfo user); + List ListGroups(SessionInfo session); /// - /// Handle a request by a user in a group. + /// Handle a request by a session in a group. /// - /// The session. + /// The session. /// The request. - void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, SyncplayRequestInfo request); /// - /// Maps a user to a group. + /// Maps a session to a group. /// - /// The user. + /// The session. /// The group. /// - void MapUserToGroup(SessionInfo user, ISyncplayController group); + void MapSessionToGroup(SessionInfo session, ISyncplayController group); /// - /// Unmaps a user from a group. + /// Unmaps a session from a group. /// - /// The user. + /// The session. /// The group. /// - void UnmapUserFromGroup(SessionInfo user, ISyncplayController group); + void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group); } } From 459297211ecb435886e8cdde8a6521671ca869f6 Mon Sep 17 00:00:00 2001 From: gion Date: Sat, 4 Apr 2020 17:59:16 +0200 Subject: [PATCH 08/72] Implement syncplay permissions for a user --- .../Syncplay/SyncplayManager.cs | 41 +++++++++++++++++++ .../Configuration/SyncplayAccess.cs | 23 +++++++++++ MediaBrowser.Model/Users/UserPolicy.cs | 7 ++++ 3 files changed, 71 insertions(+) create mode 100644 MediaBrowser.Model/Configuration/SyncplayAccess.cs diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index f76d243d58..f6311d098f 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Syncplay; namespace Emby.Server.Implementations.Syncplay @@ -21,6 +22,11 @@ namespace Emby.Server.Implementations.Syncplay /// private readonly ILogger _logger; + /// + /// The user manager. + /// + private readonly IUserManager _userManager; + /// /// The session manager. /// @@ -42,9 +48,11 @@ namespace Emby.Server.Implementations.Syncplay public SyncplayManager( ILogger logger, + IUserManager userManager, ISessionManager sessionManager) { _logger = logger; + _userManager = userManager; _sessionManager = sessionManager; _sessionManager.SessionEnded += _sessionManager_SessionEnded; @@ -125,8 +133,16 @@ namespace Emby.Server.Implementations.Syncplay /// public void NewGroup(SessionInfo session) { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { + // TODO: shall an error message be sent back to the client? + return; + } + if (IsSessionInGroup(session)) + { LeaveGroup(session); } @@ -139,6 +155,14 @@ namespace Emby.Server.Implementations.Syncplay /// public void JoinGroup(SessionInfo session, string groupId) { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncplayAccess == SyncplayAccess.None) + { + // TODO: shall an error message be sent back to the client? + return; + } + if (IsSessionInGroup(session)) { if (GetSessionGroup(session).Equals(groupId)) return; @@ -163,6 +187,8 @@ namespace Emby.Server.Implementations.Syncplay /// public void LeaveGroup(SessionInfo session) { + // TODO: what happens to users that are in a group and get their permissions revoked? + ISyncplayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); @@ -186,6 +212,13 @@ namespace Emby.Server.Implementations.Syncplay /// public List ListGroups(SessionInfo session) { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncplayAccess == SyncplayAccess.None) + { + return new List(); + } + // Filter by playing item if the user is viewing something already if (session.NowPlayingItem != null) { @@ -207,6 +240,14 @@ namespace Emby.Server.Implementations.Syncplay /// public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncplayAccess == SyncplayAccess.None) + { + // TODO: same as LeaveGroup + return; + } + ISyncplayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); diff --git a/MediaBrowser.Model/Configuration/SyncplayAccess.cs b/MediaBrowser.Model/Configuration/SyncplayAccess.cs new file mode 100644 index 0000000000..cddf68c42d --- /dev/null +++ b/MediaBrowser.Model/Configuration/SyncplayAccess.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Model.Configuration +{ + /// + /// Enum SyncplayAccess. + /// + public enum SyncplayAccess + { + /// + /// User can create groups and join them. + /// + CreateAndJoinGroups, + + /// + /// User can only join already existing groups. + /// + JoinGroups, + + /// + /// Syncplay is disabled for the user. + /// + None + } +} diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index ae2b3fd4e9..cf576c3582 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -80,6 +80,12 @@ namespace MediaBrowser.Model.Users public string AuthenticationProviderId { get; set; } public string PasswordResetProviderId { get; set; } + /// + /// Gets or sets a value indicating what Syncplay features the user can access. + /// + /// Access level to Syncplay features. + public SyncplayAccess SyncplayAccess { get; set; } + public UserPolicy() { IsHidden = true; @@ -125,6 +131,7 @@ namespace MediaBrowser.Model.Users EnableContentDownloading = true; EnablePublicSharing = true; EnableRemoteAccess = true; + SyncplayAccess = SyncplayAccess.CreateAndJoinGroups; } } } From e74832d13946b63d41341ac91bd4ef9964be2162 Mon Sep 17 00:00:00 2001 From: gion Date: Sun, 5 Apr 2020 00:50:57 +0200 Subject: [PATCH 09/72] Filter groups by library access --- .../Syncplay/SyncplayManager.cs | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index f6311d098f..7439338101 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using Microsoft.Extensions.Logging; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Syncplay; @@ -32,6 +34,11 @@ namespace Emby.Server.Implementations.Syncplay /// private readonly ISessionManager _sessionManager; + /// + /// The library manager. + /// + private readonly ILibraryManager _libraryManager; + /// /// The map between sessions and groups. /// @@ -49,11 +56,13 @@ namespace Emby.Server.Implementations.Syncplay public SyncplayManager( ILogger logger, IUserManager userManager, - ISessionManager sessionManager) + ISessionManager sessionManager, + ILibraryManager libraryManager) { _logger = logger; _userManager = userManager; _sessionManager = sessionManager; + _libraryManager = libraryManager; _sessionManager.SessionEnded += _sessionManager_SessionEnded; _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; @@ -116,6 +125,23 @@ namespace Emby.Server.Implementations.Syncplay return _sessionToGroupMap.ContainsKey(session.Id); } + private bool HasAccessToItem(User user, Guid itemId) + { + if (!user.Policy.EnableAllFolders) + { + var item = _libraryManager.GetItemById(itemId); + var collections = _libraryManager.GetCollectionFolders(item).Select( + folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) + ); + var intersect = collections.Intersect(user.Policy.EnabledFolders); + return intersect.Count() > 0; + } + else + { + return true; + } + } + private Guid? GetSessionGroup(SessionInfo session) { ISyncplayController group; @@ -181,6 +207,12 @@ namespace Emby.Server.Implementations.Syncplay _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } + + if (!HasAccessToItem(user, group.GetPlayingItemId())) + { + return; + } + group.SessionJoin(session); } @@ -223,6 +255,8 @@ namespace Emby.Server.Implementations.Syncplay if (session.NowPlayingItem != null) { return _groups.Values.Where( + group => HasAccessToItem(user, group.GetPlayingItemId()) + ).Where( group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) ).Select( group => group.GetInfo() @@ -231,7 +265,9 @@ namespace Emby.Server.Implementations.Syncplay // Otherwise show all available groups else { - return _groups.Values.Select( + return _groups.Values.Where( + group => HasAccessToItem(user, group.GetPlayingItemId()) + ).Select( group => group.GetInfo() ).ToList(); } From 73c19bd2811abf7daa2db3801388db488cab3a59 Mon Sep 17 00:00:00 2001 From: gion Date: Sun, 5 Apr 2020 09:28:55 +0200 Subject: [PATCH 10/72] Filter groups by parental rating --- Emby.Server.Implementations/Syncplay/SyncplayManager.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 7439338101..5c44326f5d 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -127,18 +127,20 @@ namespace Emby.Server.Implementations.Syncplay private bool HasAccessToItem(User user, Guid itemId) { + var item = _libraryManager.GetItemById(itemId); + var hasParentalRatingAccess = user.Policy.MaxParentalRating.HasValue ? item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating : true; + if (!user.Policy.EnableAllFolders) { - var item = _libraryManager.GetItemById(itemId); var collections = _libraryManager.GetCollectionFolders(item).Select( folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) ); var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Count() > 0; + return intersect.Count() > 0 && hasParentalRatingAccess; } else { - return true; + return hasParentalRatingAccess; } } From 84d92ba9cea4fdd97a8d1580e67706dc4577871a Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 15 Apr 2020 18:03:58 +0200 Subject: [PATCH 11/72] Check that client is playing the right item Send date when playback command is emitted Rename some classes --- .../Session/SessionManager.cs | 4 +- .../Syncplay/SyncplayController.cs | 80 ++++++++++--------- .../Syncplay/SyncplayManager.cs | 28 +++---- MediaBrowser.Api/Syncplay/SyncplayService.cs | 43 +++++++--- .../Session/ISessionManager.cs | 4 +- .../Syncplay/ISyncplayController.cs | 5 +- .../Syncplay/ISyncplayManager.cs | 5 +- ...{SyncplayGroupUpdate.cs => GroupUpdate.cs} | 6 +- ...yGroupUpdateType.cs => GroupUpdateType.cs} | 4 +- .../Syncplay/JoinGroupRequest.cs | 22 +++++ ...cplayRequestInfo.cs => PlaybackRequest.cs} | 6 +- ...yRequestType.cs => PlaybackRequestType.cs} | 4 +- .../{SyncplayCommand.cs => SendCommand.cs} | 12 ++- ...cplayCommandType.cs => SendCommandType.cs} | 4 +- 14 files changed, 141 insertions(+), 86 deletions(-) rename MediaBrowser.Model/Syncplay/{SyncplayGroupUpdate.cs => GroupUpdate.cs} (80%) rename MediaBrowser.Model/Syncplay/{SyncplayGroupUpdateType.cs => GroupUpdateType.cs} (94%) create mode 100644 MediaBrowser.Model/Syncplay/JoinGroupRequest.cs rename MediaBrowser.Model/Syncplay/{SyncplayRequestInfo.cs => PlaybackRequest.cs} (87%) rename MediaBrowser.Model/Syncplay/{SyncplayRequestType.cs => PlaybackRequestType.cs} (92%) rename MediaBrowser.Model/Syncplay/{SyncplayCommand.cs => SendCommand.cs} (69%) rename MediaBrowser.Model/Syncplay/{SyncplayCommandType.cs => SendCommandType.cs} (87%) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1519b5729..6a64209c1a 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1156,7 +1156,7 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken) + public async Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); @@ -1164,7 +1164,7 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken) + public async Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index b156e5a879..fb37b2fb6f 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -129,7 +129,7 @@ namespace Emby.Server.Implementations.Syncplay } } - private Task SendGroupUpdate(SessionInfo from, BroadcastType type, SyncplayGroupUpdate message) + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message) { IEnumerable GetTasks() { @@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } - private Task SendCommand(SessionInfo from, BroadcastType type, SyncplayCommand message) + private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message) { IEnumerable GetTasks() { @@ -157,18 +157,20 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } - private SyncplayCommand NewSyncplayCommand(SyncplayCommandType type) { - var command = new SyncplayCommand(); + private SendCommand NewSyncplayCommand(SendCommandType type) + { + var command = new SendCommand(); command.GroupId = _group.GroupId.ToString(); command.Command = type; command.PositionTicks = _group.PositionTicks; command.When = _group.LastActivity.ToUniversalTime().ToString("o"); + command.EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o"); return command; } - private SyncplayGroupUpdate NewSyncplayGroupUpdate(SyncplayGroupUpdateType type, T data) + private GroupUpdate NewSyncplayGroupUpdate(GroupUpdateType type, T data) { - var command = new SyncplayGroupUpdate(); + var command = new GroupUpdate(); command.GroupId = _group.GroupId.ToString(); command.Type = type; command.Data = data; @@ -186,35 +188,37 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks = session.PlayState.PositionTicks ??= 0; _group.LastActivity = DateTime.UtcNow; - var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, pauseCommand); } /// - public void SessionJoin(SessionInfo session) + public void SessionJoin(SessionInfo session, JoinGroupRequest request) { - if (session.NowPlayingItem != null && session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) + if (session.NowPlayingItem != null && + session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id) && + request.PlayingItemId.Equals(_group.PlayingItem.Id)) { _group.AddSession(session); _syncplayManager.MapSessionToGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserJoined, session.UserName); + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); // Client join and play, syncing will happen client side if (!_group.IsPaused) { - var playCommand = NewSyncplayCommand(SyncplayCommandType.Play); + var playCommand = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.SingleSession, playCommand); } else { - var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, pauseCommand); } } @@ -223,7 +227,7 @@ namespace Emby.Server.Implementations.Syncplay var playRequest = new PlayRequest(); playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; - var update = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.PrepareSession, playRequest); + var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); SendGroupUpdate(session, BroadcastType.SingleSession, update); } } @@ -234,17 +238,17 @@ namespace Emby.Server.Implementations.Syncplay _group.RemoveSession(session); _syncplayManager.UnmapSessionFromGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, session.UserName); + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } /// - public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, PlaybackRequest request) { - if (request.Type.Equals(SyncplayRequestType.Play)) + if (request.Type.Equals(PlaybackRequestType.Play)) { if (_group.IsPaused) { @@ -256,17 +260,17 @@ namespace Emby.Server.Implementations.Syncplay delay ); - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command); } else { // Client got lost - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(SyncplayRequestType.Pause)) + else if (request.Type.Equals(PlaybackRequestType.Pause)) { if (!_group.IsPaused) { @@ -276,16 +280,16 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = currentTime; _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.AllGroup, command); } else { - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(SyncplayRequestType.Seek)) + else if (request.Type.Equals(PlaybackRequestType.Seek)) { // Sanitize PositionTicks var ticks = request.PositionTicks ??= 0; @@ -300,11 +304,11 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks = ticks; _group.LastActivity = DateTime.UtcNow; - var command = NewSyncplayCommand(SyncplayCommandType.Seek); + var command = NewSyncplayCommand(SendCommandType.Seek); SendCommand(session, BroadcastType.AllGroup, command); } // TODO: client does not implement this yet - else if (request.Type.Equals(SyncplayRequestType.Buffering)) + else if (request.Type.Equals(PlaybackRequestType.Buffering)) { if (!_group.IsPaused) { @@ -317,20 +321,20 @@ namespace Emby.Server.Implementations.Syncplay _group.SetBuffering(session, true); // Send pause command to all non-buffering sessions - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.AllReady, command); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, session.UserName); + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } else { - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, command); } } // TODO: client does not implement this yet - else if (request.Type.Equals(SyncplayRequestType.BufferingComplete)) + else if (request.Type.Equals(PlaybackRequestType.BufferingComplete)) { if (_group.IsPaused) { @@ -344,7 +348,7 @@ namespace Emby.Server.Implementations.Syncplay var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; var delay = _group.PositionTicks - clientPosition.Ticks; - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); command.When = currentTime.AddMilliseconds( delay ).ToUniversalTime().ToString("o"); @@ -367,7 +371,7 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = currentTime.AddMilliseconds( delay ); - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllExceptSession, command); } else @@ -380,7 +384,7 @@ namespace Emby.Server.Implementations.Syncplay delay ); - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command); } } @@ -388,17 +392,17 @@ namespace Emby.Server.Implementations.Syncplay else { // Make sure client has latest group state - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(SyncplayRequestType.KeepAlive)) + else if (request.Type.Equals(PlaybackRequestType.KeepAlive)) { _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); - var keepAlive = new SyncplayGroupUpdate(); + var keepAlive = new GroupUpdate(); keepAlive.GroupId = _group.GroupId.ToString(); - keepAlive.Type = SyncplayGroupUpdateType.KeepAlive; + keepAlive.Type = GroupUpdateType.KeepAlive; SendGroupUpdate(session, BroadcastType.SingleSession, keepAlive); } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 5c44326f5d..60d70e5fde 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -166,7 +166,7 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { // TODO: shall an error message be sent back to the client? - return; + throw new ArgumentException("User does not have permission to create groups"); } if (IsSessionInGroup(session)) @@ -181,14 +181,14 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void JoinGroup(SessionInfo session, string groupId) + public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { // TODO: shall an error message be sent back to the client? - return; + throw new ArgumentException("User does not have access to syncplay"); } if (IsSessionInGroup(session)) @@ -204,18 +204,18 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogError("Syncplaymanager JoinGroup: " + groupId + " does not exist."); - var update = new SyncplayGroupUpdate(); - update.Type = SyncplayGroupUpdateType.NotInGroup; + var update = new GroupUpdate(); + update.Type = GroupUpdateType.NotInGroup; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } if (!HasAccessToItem(user, group.GetPlayingItemId())) { - return; + throw new ArgumentException("User does not have access to playing item"); } - group.SessionJoin(session); + group.SessionJoin(session, request); } /// @@ -230,8 +230,8 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); - var update = new SyncplayGroupUpdate(); - update.Type = SyncplayGroupUpdateType.NotInGroup; + var update = new GroupUpdate(); + update.Type = GroupUpdateType.NotInGroup; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } @@ -276,14 +276,14 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, PlaybackRequest request) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { // TODO: same as LeaveGroup - return; + throw new ArgumentException("User does not have access to syncplay"); } ISyncplayController group; @@ -293,14 +293,14 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); - var update = new SyncplayGroupUpdate(); - update.Type = SyncplayGroupUpdateType.NotInGroup; + var update = new GroupUpdate(); + update.Type = GroupUpdateType.NotInGroup; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } group.HandleRequest(session, request); } - + /// public void MapSessionToGroup(SessionInfo session, ISyncplayController group) { diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index f17cca9ee7..0f9d1b7339 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -31,6 +31,13 @@ namespace MediaBrowser.Api.Syncplay /// The Group id to join. [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string PlayingItemId { get; set; } } [Route("/Syncplay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined Syncplay group")] @@ -160,7 +167,21 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayJoinGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.JoinGroup(currentSession, request.GroupId); + var joinRequest = new JoinGroupRequest(); + joinRequest.GroupId = Guid.Parse(request.GroupId); + try + { + joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); + } + catch (ArgumentNullException) + { + // Do nothing + } + catch (FormatException) + { + // Do nothing + } + _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest); } /// @@ -191,8 +212,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPlayRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.Play; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.Play; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -203,8 +224,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPauseRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.Pause; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.Pause; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -215,8 +236,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplaySeekRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.Seek; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.Seek; syncplayRequest.PositionTicks = request.PositionTicks; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -228,8 +249,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayBufferingRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = request.Resume ? SyncplayRequestType.BufferingComplete : SyncplayRequestType.Buffering; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = request.Resume ? PlaybackRequestType.BufferingComplete : PlaybackRequestType.Buffering; syncplayRequest.When = DateTime.Parse(request.When); syncplayRequest.PositionTicks = request.PositionTicks; _syncplayManager.HandleRequest(currentSession, syncplayRequest); @@ -242,8 +263,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayKeepAlive request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.KeepAlive; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.KeepAlive; syncplayRequest.Ping = Convert.ToInt64(request.Ping); _syncplayManager.HandleRequest(currentSession, syncplayRequest); } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 4bfc0c73f3..39c065b895 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Controller.Session /// The command. /// The cancellation token. /// Task. - Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken); + Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); /// /// Sends the SyncplayGroupUpdate. @@ -157,7 +157,7 @@ namespace MediaBrowser.Controller.Session /// The group update. /// The cancellation token. /// Task. - Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken); + Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); /// /// Sends the browse command. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs index d35ae31019..5b08eac0a4 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -37,7 +37,8 @@ namespace MediaBrowser.Controller.Syncplay /// Adds the session to the group. /// /// The session. - void SessionJoin(SessionInfo session); + /// The request. + void SessionJoin(SessionInfo session, JoinGroupRequest request); /// /// Removes the session from the group. @@ -50,7 +51,7 @@ namespace MediaBrowser.Controller.Syncplay /// /// The session. /// The requested action. - void HandleRequest(SessionInfo session, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, PlaybackRequest request); /// /// Gets the info about the group for the clients. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index 09920a19ff..d0cf8fa9c8 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -21,7 +21,8 @@ namespace MediaBrowser.Controller.Syncplay /// /// The session. /// The group id. - void JoinGroup(SessionInfo session, string groupId); + /// The request. + void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request); /// /// Removes the session from a group. @@ -41,7 +42,7 @@ namespace MediaBrowser.Controller.Syncplay /// /// The session. /// The request. - void HandleRequest(SessionInfo session, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, PlaybackRequest request); /// /// Maps a session to a group. diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs b/MediaBrowser.Model/Syncplay/GroupUpdate.cs similarity index 80% rename from MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs rename to MediaBrowser.Model/Syncplay/GroupUpdate.cs index c5c2f35404..cc49e92a9c 100644 --- a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs +++ b/MediaBrowser.Model/Syncplay/GroupUpdate.cs @@ -1,9 +1,9 @@ namespace MediaBrowser.Model.Syncplay { /// - /// Class SyncplayGroupUpdate. + /// Class GroupUpdate. /// - public class SyncplayGroupUpdate + public class GroupUpdate { /// /// Gets or sets the group identifier. @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Syncplay /// Gets or sets the update type. /// /// The update type. - public SyncplayGroupUpdateType Type { get; set; } + public GroupUpdateType Type { get; set; } /// /// Gets or sets the data. diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs similarity index 94% rename from MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs rename to MediaBrowser.Model/Syncplay/GroupUpdateType.cs index c7c5f534dc..ceb778b36f 100644 --- a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -1,9 +1,9 @@ namespace MediaBrowser.Model.Syncplay { /// - /// Enum SyncplayGroupUpdateType + /// Enum GroupUpdateType /// - public enum SyncplayGroupUpdateType + public enum GroupUpdateType { /// /// The user-joined update. Tells members of a group about a new user. diff --git a/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs b/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs new file mode 100644 index 0000000000..8d8a2646ac --- /dev/null +++ b/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs @@ -0,0 +1,22 @@ +using System; + +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class JoinGroupRequest. + /// + public class JoinGroupRequest + { + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + public Guid GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + public Guid PlayingItemId { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs similarity index 87% rename from MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs rename to MediaBrowser.Model/Syncplay/PlaybackRequest.cs index 7dba74ae94..cae769db0d 100644 --- a/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs @@ -3,15 +3,15 @@ using System; namespace MediaBrowser.Model.Syncplay { /// - /// Class SyncplayRequestInfo. + /// Class PlaybackRequest. /// - public class SyncplayRequestInfo + public class PlaybackRequest { /// /// Gets or sets the request type. /// /// The request type. - public SyncplayRequestType Type; + public PlaybackRequestType Type; /// /// Gets or sets when the request has been made by the client. diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs similarity index 92% rename from MediaBrowser.Model/Syncplay/SyncplayRequestType.cs rename to MediaBrowser.Model/Syncplay/PlaybackRequestType.cs index 44d7a0af26..da770736c7 100644 --- a/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs @@ -1,9 +1,9 @@ namespace MediaBrowser.Model.Syncplay { /// - /// Enum SyncplayRequestType + /// Enum PlaybackRequestType /// - public enum SyncplayRequestType + public enum PlaybackRequestType { /// /// A user is requesting a play command for the group. diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommand.cs b/MediaBrowser.Model/Syncplay/SendCommand.cs similarity index 69% rename from MediaBrowser.Model/Syncplay/SyncplayCommand.cs rename to MediaBrowser.Model/Syncplay/SendCommand.cs index 769316e805..d9f3914030 100644 --- a/MediaBrowser.Model/Syncplay/SyncplayCommand.cs +++ b/MediaBrowser.Model/Syncplay/SendCommand.cs @@ -1,9 +1,9 @@ namespace MediaBrowser.Model.Syncplay { /// - /// Class SyncplayCommand. + /// Class SendCommand. /// - public class SyncplayCommand + public class SendCommand { /// /// Gets or sets the group identifier. @@ -27,6 +27,12 @@ namespace MediaBrowser.Model.Syncplay /// Gets or sets the command. /// /// The command. - public SyncplayCommandType Command { get; set; } + public SendCommandType Command { get; set; } + + /// + /// Gets or sets the UTC time when this command has been emitted. + /// + /// The UTC time when this command has been emitted. + public string EmittedAt { get; set; } } } diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs b/MediaBrowser.Model/Syncplay/SendCommandType.cs similarity index 87% rename from MediaBrowser.Model/Syncplay/SyncplayCommandType.cs rename to MediaBrowser.Model/Syncplay/SendCommandType.cs index 87b9ad66d6..02e4774d0d 100644 --- a/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs +++ b/MediaBrowser.Model/Syncplay/SendCommandType.cs @@ -1,9 +1,9 @@ namespace MediaBrowser.Model.Syncplay { /// - /// Enum SyncplayCommandType. + /// Enum SendCommandType. /// - public enum SyncplayCommandType + public enum SendCommandType { /// /// The play command. Instructs users to start playback. From 6519eebabb1df44535e0681e3bf798e7823d4c05 Mon Sep 17 00:00:00 2001 From: gion Date: Thu, 16 Apr 2020 16:02:52 +0200 Subject: [PATCH 12/72] Implement NTP like time sync --- MediaBrowser.Api/Syncplay/SyncplayService.cs | 21 +----- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 70 +++++++++++++++++++ .../Syncplay/UtcTimeResponse.cs | 20 ++++++ 3 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 MediaBrowser.Api/Syncplay/TimeSyncService.cs create mode 100644 MediaBrowser.Model/Syncplay/UtcTimeResponse.cs diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index 0f9d1b7339..c273e6c38c 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -111,14 +111,6 @@ namespace MediaBrowser.Api.Syncplay public double Ping { get; set; } } - [Route("/Syncplay/{SessionId}/GetUtcTime", "POST", Summary = "Get UtcTime")] - [Authenticated] - public class SyncplayGetUtcTime : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - /// /// Class SyncplayService. /// @@ -129,6 +121,9 @@ namespace MediaBrowser.Api.Syncplay /// private readonly ISessionManager _sessionManager; + /// + /// The session context. + /// private readonly ISessionContext _sessionContext; /// @@ -268,15 +263,5 @@ namespace MediaBrowser.Api.Syncplay syncplayRequest.Ping = Convert.ToInt64(request.Ping); _syncplayManager.HandleRequest(currentSession, syncplayRequest); } - - /// - /// Handles the specified request. - /// - /// The request. - /// The current UTC time. - public string Post(SyncplayGetUtcTime request) - { - return DateTime.UtcNow.ToUniversalTime().ToString("o"); - } } } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs new file mode 100644 index 0000000000..049684d94c --- /dev/null +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.Syncplay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Api.Syncplay +{ + [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] + [Authenticated] + public class GetUtcTime : IReturnVoid + { + // Nothing + } + + /// + /// Class TimeSyncService. + /// + public class TimeSyncService : BaseApiService + { + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The session context. + /// + private readonly ISessionContext _sessionContext; + + public TimeSyncService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + ISessionManager sessionManager, + ISessionContext sessionContext) + : base(logger, serverConfigurationManager, httpResultFactory) + { + _sessionManager = sessionManager; + _sessionContext = sessionContext; + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The current UTC time response. + public UtcTimeResponse Get(GetUtcTime request) + { + // Important to keep the following line at the beginning + var requestReceptionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); + + var response = new UtcTimeResponse(); + response.RequestReceptionTime = requestReceptionTime; + var currentSession = GetSession(_sessionContext); + + // Important to keep the following two lines at the end + var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); + response.ResponseTransmissionTime = responseTransmissionTime; + + // Implementing NTP on such a high level results in this useless + // information being sent. On the other hand it enables future additions. + return response; + } + } +} diff --git a/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs b/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs new file mode 100644 index 0000000000..f7887dc332 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs @@ -0,0 +1,20 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class UtcTimeResponse. + /// + public class UtcTimeResponse + { + /// + /// Gets or sets the UTC time when request has been received. + /// + /// The UTC time when request has been received. + public string RequestReceptionTime { get; set; } + + /// + /// Gets or sets the UTC time when response has been sent. + /// + /// The UTC time when response has been sent. + public string ResponseTransmissionTime { get; set; } + } +} From 40889702d05c7a6f3dc30090e9443e94cb29fbd9 Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 17 Apr 2020 12:57:36 +0200 Subject: [PATCH 13/72] Update session ping --- .../Syncplay/SyncplayController.cs | 7 +------ MediaBrowser.Api/Syncplay/SyncplayService.cs | 8 ++++---- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 2 -- MediaBrowser.Model/Syncplay/GroupUpdateType.cs | 4 ---- MediaBrowser.Model/Syncplay/PlaybackRequestType.cs | 2 +- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index fb37b2fb6f..83b4779447 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -396,14 +396,9 @@ namespace Emby.Server.Implementations.Syncplay SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(PlaybackRequestType.KeepAlive)) + else if (request.Type.Equals(PlaybackRequestType.UpdatePing)) { _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); - - var keepAlive = new GroupUpdate(); - keepAlive.GroupId = _group.GroupId.ToString(); - keepAlive.Type = GroupUpdateType.KeepAlive; - SendGroupUpdate(session, BroadcastType.SingleSession, keepAlive); } } diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index c273e6c38c..af220ed81d 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -100,9 +100,9 @@ namespace MediaBrowser.Api.Syncplay public bool Resume { get; set; } } - [Route("/Syncplay/{SessionId}/KeepAlive", "POST", Summary = "Keep session alive")] + [Route("/Syncplay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] [Authenticated] - public class SyncplayKeepAlive : IReturnVoid + public class SyncplayUpdatePing : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } @@ -255,11 +255,11 @@ namespace MediaBrowser.Api.Syncplay /// Handles the specified request. /// /// The request. - public void Post(SyncplayKeepAlive request) + public void Post(SyncplayUpdatePing request) { var currentSession = GetSession(_sessionContext); var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.KeepAlive; + syncplayRequest.Type = PlaybackRequestType.UpdatePing; syncplayRequest.Ping = Convert.ToInt64(request.Ping); _syncplayManager.HandleRequest(currentSession, syncplayRequest); } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index 049684d94c..a69e0e293a 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; using MediaBrowser.Model.Services; using MediaBrowser.Model.Syncplay; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs index ceb778b36f..0ef8b27853 100644 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -30,10 +30,6 @@ namespace MediaBrowser.Model.Syncplay /// PrepareSession = 5, /// - /// The keep-alive update. An update to keep alive the socket. - /// - KeepAlive = 6, - /// /// The not-in-group update. Tells a user that no group has been joined. /// NotInGroup = 7 diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs index da770736c7..3d99b2718e 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs @@ -28,6 +28,6 @@ namespace MediaBrowser.Model.Syncplay /// /// A user is reporting its ping. /// - KeepAlive = 5 + UpdatePing = 5 } } From aad5058d25b3c295e9ea5b4330dde219034ba8c8 Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 17 Apr 2020 13:47:00 +0200 Subject: [PATCH 14/72] Implement KeepAlive for WebSockets --- .../HttpServer/WebSocketConnection.cs | 27 ++- .../Session/SessionWebSocketListener.cs | 156 ++++++++++++++++++ .../Net/IWebSocketConnection.cs | 6 + 3 files changed, 183 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 2292d86a4a..171047e65f 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -94,6 +94,9 @@ namespace Emby.Server.Implementations.HttpServer /// The last activity date. public DateTime LastActivityDate { get; private set; } + /// + public DateTime LastKeepAliveDate { get; set; } + /// /// Gets the id. /// @@ -158,11 +161,6 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (OnReceive == null) - { - return; - } - try { var stub = (WebSocketMessage)_jsonSerializer.DeserializeFromString(message, typeof(WebSocketMessage)); @@ -174,7 +172,15 @@ namespace Emby.Server.Implementations.HttpServer Connection = this }; - OnReceive(info); + if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) + { + SendKeepAliveResponse(); + } + + if (OnReceive != null) + { + OnReceive(info); + } } catch (Exception ex) { @@ -233,6 +239,15 @@ namespace Emby.Server.Implementations.HttpServer return _socket.SendAsync(text, true, cancellationToken); } + private Task SendKeepAliveResponse() + { + LastKeepAliveDate = DateTime.UtcNow; + return SendAsync(new WebSocketMessage + { + MessageType = "KeepAlive" + }, CancellationToken.None); + } + /// public void Dispose() { diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 930f2d35d3..d8e02ef395 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,8 +1,13 @@ using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Net.WebSockets; +using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -14,6 +19,21 @@ namespace Emby.Server.Implementations.Session /// public class SessionWebSocketListener : IWebSocketListener, IDisposable { + /// + /// The timeout in seconds after which a WebSocket is considered to be lost. + /// + public readonly int WebSocketLostTimeout = 60; + + /// + /// The timer factor; controls the frequency of the timer. + /// + public readonly double TimerFactor = 0.2; + + /// + /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. + /// + public readonly double ForceKeepAliveFactor = 0.75; + /// /// The _session manager /// @@ -31,6 +51,15 @@ namespace Emby.Server.Implementations.Session private readonly IHttpServer _httpServer; + /// + /// The KeepAlive timer. + /// + private Timer _keepAliveTimer; + + /// + /// The WebSocket watchlist. + /// + private readonly ConcurrentDictionary _webSockets = new ConcurrentDictionary(); /// /// Initializes a new instance of the class. @@ -55,6 +84,7 @@ namespace Emby.Server.Implementations.Session if (session != null) { EnsureController(session, e.Argument); + KeepAliveWebSocket(e.Argument); } else { @@ -82,6 +112,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; + StopKeepAliveTimer(); } /// @@ -99,5 +130,130 @@ namespace Emby.Server.Implementations.Session var controller = (WebSocketController)controllerInfo.Item1; controller.AddWebSocket(connection); } + + /// + /// Called when a WebSocket is closed. + /// + /// The WebSocket. + /// The event arguments. + private void _webSocket_Closed(object sender, EventArgs e) + { + var webSocket = (IWebSocketConnection) sender; + webSocket.Closed -= _webSocket_Closed; + _webSockets.TryRemove(webSocket, out _); + } + + /// + /// Adds a WebSocket to the KeepAlive watchlist. + /// + /// The WebSocket to monitor. + private async void KeepAliveWebSocket(IWebSocketConnection webSocket) + { + _webSockets.TryAdd(webSocket, 0); + webSocket.Closed += _webSocket_Closed; + webSocket.LastKeepAliveDate = DateTime.UtcNow; + + // Notify WebSocket about timeout + try + { + await SendForceKeepAlive(webSocket); + } + catch (WebSocketException exception) + { + _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + } + + StartKeepAliveTimer(); + } + + /// + /// Starts the KeepAlive timer. + /// + private void StartKeepAliveTimer() + { + if (_keepAliveTimer == null) + { + _keepAliveTimer = new Timer( + KeepAliveSockets, + null, + TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor), + TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor) + ); + } + } + + /// + /// Stops the KeepAlive timer. + /// + private void StopKeepAliveTimer() + { + if (_keepAliveTimer != null) + { + _keepAliveTimer.Dispose(); + _keepAliveTimer = null; + } + + foreach (var pair in _webSockets) + { + pair.Key.Closed -= _webSocket_Closed; + } + } + + /// + /// Checks status of KeepAlive of WebSockets. + /// + /// The state. + private async void KeepAliveSockets(object state) + { + var inactive = _webSockets.Keys.Where(i => + { + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }); + var lost = _webSockets.Keys.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + + if (inactive.Any()) + { + _logger.LogDebug("Sending ForceKeepAlive message to {0} WebSockets.", inactive.Count()); + } + + foreach (var webSocket in inactive) + { + try + { + await SendForceKeepAlive(webSocket); + } + catch (WebSocketException exception) + { + _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost.Append(webSocket); + } + } + + if (lost.Any()) + { + // TODO: handle lost webSockets + _logger.LogDebug("Lost {0} WebSockets.", lost.Count()); + } + + if (!_webSockets.Any()) + { + StopKeepAliveTimer(); + } + } + + /// + /// Sends a ForceKeepAlive message to a WebSocket. + /// + /// The WebSocket. + /// Task. + private Task SendForceKeepAlive(IWebSocketConnection webSocket) + { + return webSocket.SendAsync(new WebSocketMessage + { + MessageType = "ForceKeepAlive", + Data = WebSocketLostTimeout + }, CancellationToken.None); + } } } diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index 31eb7ccb75..fb766ab57f 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -26,6 +26,12 @@ namespace MediaBrowser.Controller.Net /// The last activity date. DateTime LastActivityDate { get; } + /// + /// Gets or sets the date of last Keeplive received. + /// + /// The date of last Keeplive received. + public DateTime LastKeepAliveDate { get; set; } + /// /// Gets or sets the URL. /// From 083d3272d09395e2b7d73d886377017573e63686 Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 21 Apr 2020 23:37:37 +0200 Subject: [PATCH 15/72] Refactor and other minor changes --- .../HttpServer/WebSocketConnection.cs | 5 +- .../Session/SessionWebSocketListener.cs | 42 +- .../Syncplay/SyncplayController.cs | 491 +++++++++++------- .../Syncplay/SyncplayManager.cs | 50 +- MediaBrowser.Api/Syncplay/SyncplayService.cs | 52 +- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 1 - MediaBrowser.Controller/Syncplay/GroupInfo.cs | 24 +- .../Syncplay/ISyncplayManager.cs | 4 +- .../{GroupInfoModel.cs => GroupInfoView.cs} | 8 +- .../Syncplay/PlaybackRequestType.cs | 2 +- 10 files changed, 410 insertions(+), 269 deletions(-) rename MediaBrowser.Model/Syncplay/{GroupInfoModel.cs => GroupInfoView.cs} (84%) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 171047e65f..c819c163a7 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -176,10 +176,9 @@ namespace Emby.Server.Implementations.HttpServer { SendKeepAliveResponse(); } - - if (OnReceive != null) + else { - OnReceive(info); + OnReceive?.Invoke(info); } } catch (Exception ex) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index d8e02ef395..b0c6d0aa08 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System; using System.Collections.Concurrent; using System.Linq; @@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Session public readonly int WebSocketLostTimeout = 60; /// - /// The timer factor; controls the frequency of the timer. + /// The keep-alive timer factor; controls how often the timer will check on the status of the WebSockets. /// public readonly double TimerFactor = 0.2; @@ -136,11 +137,10 @@ namespace Emby.Server.Implementations.Session /// /// The WebSocket. /// The event arguments. - private void _webSocket_Closed(object sender, EventArgs e) + private void OnWebSocketClosed(object sender, EventArgs e) { var webSocket = (IWebSocketConnection) sender; - webSocket.Closed -= _webSocket_Closed; - _webSockets.TryRemove(webSocket, out _); + RemoveWebSocket(webSocket); } /// @@ -149,8 +149,12 @@ namespace Emby.Server.Implementations.Session /// The WebSocket to monitor. private async void KeepAliveWebSocket(IWebSocketConnection webSocket) { - _webSockets.TryAdd(webSocket, 0); - webSocket.Closed += _webSocket_Closed; + if (!_webSockets.TryAdd(webSocket, 0)) + { + _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); + return; + } + webSocket.Closed += OnWebSocketClosed; webSocket.LastKeepAliveDate = DateTime.UtcNow; // Notify WebSocket about timeout @@ -160,12 +164,22 @@ namespace Emby.Server.Implementations.Session } catch (WebSocketException exception) { - _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket."); } StartKeepAliveTimer(); } + /// + /// Removes a WebSocket from the KeepAlive watchlist. + /// + /// The WebSocket to remove. + private void RemoveWebSocket(IWebSocketConnection webSocket) + { + webSocket.Closed -= OnWebSocketClosed; + _webSockets.TryRemove(webSocket, out _); + } + /// /// Starts the KeepAlive timer. /// @@ -195,7 +209,7 @@ namespace Emby.Server.Implementations.Session foreach (var pair in _webSockets) { - pair.Key.Closed -= _webSocket_Closed; + pair.Key.Closed -= OnWebSocketClosed; } } @@ -214,7 +228,7 @@ namespace Emby.Server.Implementations.Session if (inactive.Any()) { - _logger.LogDebug("Sending ForceKeepAlive message to {0} WebSockets.", inactive.Count()); + _logger.LogDebug("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); } foreach (var webSocket in inactive) @@ -225,15 +239,19 @@ namespace Emby.Server.Implementations.Session } catch (WebSocketException exception) { - _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); lost.Append(webSocket); } } if (lost.Any()) { - // TODO: handle lost webSockets - _logger.LogDebug("Lost {0} WebSockets.", lost.Count()); + _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + foreach (var webSocket in lost) + { + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); + } } if (!_webSockets.Any()) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index 83b4779447..02cf08cd7c 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -16,11 +16,26 @@ namespace Emby.Server.Implementations.Syncplay /// public class SyncplayController : ISyncplayController, IDisposable { + /// + /// Used to filter the sessions of a group. + /// private enum BroadcastType { + /// + /// All sessions will receive the message. + /// AllGroup = 0, - SingleSession = 1, - AllExceptSession = 2, + /// + /// Only the specified session will receive the message. + /// + CurrentSession = 1, + /// + /// All sessions, except the current one, will receive the message. + /// + AllExceptCurrentSession = 2, + /// + /// Only sessions that are not buffering will receive the message. + /// AllReady = 3 } @@ -95,40 +110,46 @@ namespace Emby.Server.Implementations.Syncplay } } + /// + /// Filters sessions of this group. + /// + /// The current session. + /// The filtering type. + /// The array of sessions matching the filter. private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) { - if (type == BroadcastType.SingleSession) + switch (type) { - return new SessionInfo[] { from }; - } - else if (type == BroadcastType.AllGroup) - { - return _group.Partecipants.Values.Select( - session => session.Session - ).ToArray(); - } - else if (type == BroadcastType.AllExceptSession) - { - return _group.Partecipants.Values.Select( - session => session.Session - ).Where( - session => !session.Id.Equals(from.Id) - ).ToArray(); - } - else if (type == BroadcastType.AllReady) - { - return _group.Partecipants.Values.Where( - session => !session.IsBuffering - ).Select( - session => session.Session - ).ToArray(); - } - else - { - return new SessionInfo[] {}; + case BroadcastType.CurrentSession: + return new SessionInfo[] { from }; + case BroadcastType.AllGroup: + return _group.Participants.Values.Select( + session => session.Session + ).ToArray(); + case BroadcastType.AllExceptCurrentSession: + return _group.Participants.Values.Select( + session => session.Session + ).Where( + session => !session.Id.Equals(from.Id) + ).ToArray(); + case BroadcastType.AllReady: + return _group.Participants.Values.Where( + session => !session.IsBuffering + ).Select( + session => session.Session + ).ToArray(); + default: + return new SessionInfo[] { }; } } + /// + /// Sends a GroupUpdate message to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The task. private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message) { IEnumerable GetTasks() @@ -143,6 +164,13 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } + /// + /// Sends a playback command to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The task. private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message) { IEnumerable GetTasks() @@ -157,31 +185,44 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } + /// + /// Builds a new playback command with some default values. + /// + /// The command type. + /// The SendCommand. private SendCommand NewSyncplayCommand(SendCommandType type) { - var command = new SendCommand(); - command.GroupId = _group.GroupId.ToString(); - command.Command = type; - command.PositionTicks = _group.PositionTicks; - command.When = _group.LastActivity.ToUniversalTime().ToString("o"); - command.EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o"); - return command; + return new SendCommand() + { + GroupId = _group.GroupId.ToString(), + Command = type, + PositionTicks = _group.PositionTicks, + When = _group.LastActivity.ToUniversalTime().ToString("o"), + EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o") + }; } + /// + /// Builds a new group update message. + /// + /// The update type. + /// The data to send. + /// The GroupUpdate. private GroupUpdate NewSyncplayGroupUpdate(GroupUpdateType type, T data) { - var command = new GroupUpdate(); - command.GroupId = _group.GroupId.ToString(); - command.Type = type; - command.Data = data; - return command; + return new GroupUpdate() + { + GroupId = _group.GroupId.ToString(), + Type = type, + Data = data + }; } /// public void InitGroup(SessionInfo session) { _group.AddSession(session); - _syncplayManager.MapSessionToGroup(session, this); + _syncplayManager.AddSessionToGroup(session, this); _group.PlayingItem = session.FullNowPlayingItem; _group.IsPaused = true; @@ -189,37 +230,35 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = DateTime.UtcNow; var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand); } /// public void SessionJoin(SessionInfo session, JoinGroupRequest request) { - if (session.NowPlayingItem != null && - session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id) && - request.PlayingItemId.Equals(_group.PlayingItem.Id)) + if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) { _group.AddSession(session); - _syncplayManager.MapSessionToGroup(session, this); + _syncplayManager.AddSessionToGroup(session, this); var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); // Client join and play, syncing will happen client side if (!_group.IsPaused) { var playCommand = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.SingleSession, playCommand); + SendCommand(session, BroadcastType.CurrentSession, playCommand); } else { var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand); } } else @@ -228,7 +267,7 @@ namespace Emby.Server.Implementations.Syncplay playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(session, BroadcastType.SingleSession, update); + SendGroupUpdate(session, BroadcastType.CurrentSession, update); } } @@ -236,182 +275,250 @@ namespace Emby.Server.Implementations.Syncplay public void SessionLeave(SessionInfo session) { _group.RemoveSession(session); - _syncplayManager.UnmapSessionFromGroup(session, this); + _syncplayManager.RemoveSessionFromGroup(session, this); var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); } /// public void HandleRequest(SessionInfo session, PlaybackRequest request) { - if (request.Type.Equals(PlaybackRequestType.Play)) + // The server's job is to mantain a consistent state to which clients refer to, + // as also to notify clients of state changes. + // The actual syncing of media playback happens client side. + // Clients are aware of the server's time and use it to sync. + switch (request.Type) { - if (_group.IsPaused) - { - var delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - _group.IsPaused = false; - _group.LastActivity = DateTime.UtcNow.AddMilliseconds( - delay - ); - - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); - } - else - { - // Client got lost - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.SingleSession, command); - } + case PlaybackRequestType.Play: + HandlePlayRequest(session, request); + break; + case PlaybackRequestType.Pause: + HandlePauseRequest(session, request); + break; + case PlaybackRequestType.Seek: + HandleSeekRequest(session, request); + break; + case PlaybackRequestType.Buffering: + HandleBufferingRequest(session, request); + break; + case PlaybackRequestType.BufferingDone: + HandleBufferingDoneRequest(session, request); + break; + case PlaybackRequestType.UpdatePing: + HandlePingUpdateRequest(session, request); + break; } - else if (request.Type.Equals(PlaybackRequestType.Pause)) + } + + /// + /// Handles a play action requested by a session. + /// + /// The session. + /// The play action. + private void HandlePlayRequest(SessionInfo session, PlaybackRequest request) + { + if (_group.IsPaused) { - if (!_group.IsPaused) - { - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + // Pick a suitable time that accounts for latency + var delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllGroup, command); - } - else - { - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, command); - } - } - else if (request.Type.Equals(PlaybackRequestType.Seek)) - { - // Sanitize PositionTicks - var ticks = request.PositionTicks ??= 0; - ticks = ticks >= 0 ? ticks : 0; - if (_group.PlayingItem.RunTimeTicks != null) - { - var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; - ticks = ticks > runTimeTicks ? runTimeTicks : ticks; - } + // Unpause group and set starting point in future + // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position) + // The added delay does not guarantee, of course, that the command will be received in time + // Playback synchronization will mainly happen client side + _group.IsPaused = false; + _group.LastActivity = DateTime.UtcNow.AddMilliseconds( + delay + ); - _group.IsPaused = true; - _group.PositionTicks = ticks; - _group.LastActivity = DateTime.UtcNow; - - var command = NewSyncplayCommand(SendCommandType.Seek); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command); } - // TODO: client does not implement this yet - else if (request.Type.Equals(PlaybackRequestType.Buffering)) + else { - if (!_group.IsPaused) + // Client got lost, sending current state + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command); + } + } + + /// + /// Handles a pause action requested by a session. + /// + /// The session. + /// The pause action. + private void HandlePauseRequest(SessionInfo session, PlaybackRequest request) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + // Seek only if playback actually started + // (a pause request may be issued during the delay added to account for latency) + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllGroup, command); + } + else + { + // Client got lost, sending current state + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command); + } + } + + /// + /// Handles a seek action requested by a session. + /// + /// The session. + /// The seek action. + private void HandleSeekRequest(SessionInfo session, PlaybackRequest request) + { + // Sanitize PositionTicks + var ticks = request.PositionTicks ??= 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem.RunTimeTicks != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; + } + + // Pause and seek + _group.IsPaused = true; + _group.PositionTicks = ticks; + _group.LastActivity = DateTime.UtcNow; + + var command = NewSyncplayCommand(SendCommandType.Seek); + SendCommand(session, BroadcastType.AllGroup, command); + } + + /// + /// Handles a buffering action requested by a session. + /// + /// The session. + /// The buffering action. + private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + _group.SetBuffering(session, true); + + // Send pause command to all non-buffering sessions + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllReady, command); + + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + } + else + { + // Client got lost, sending current state + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command); + } + } + + /// + /// Handles a buffering-done action requested by a session. + /// + /// The session. + /// The buffering-done action. + private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request) + { + if (_group.IsPaused) + { + _group.SetBuffering(session, false); + + var when = request.When ??= DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; + + if (_group.IsBuffering()) { - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - - _group.SetBuffering(session, true); - - // Send pause command to all non-buffering sessions + // Others are buffering, tell this client to pause when ready var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllReady, command); - - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); + command.When = currentTime.AddMilliseconds( + delay + ).ToUniversalTime().ToString("o"); + SendCommand(session, BroadcastType.CurrentSession, command); } else { - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, command); - } - } - // TODO: client does not implement this yet - else if (request.Type.Equals(PlaybackRequestType.BufferingComplete)) - { - if (_group.IsPaused) - { - _group.SetBuffering(session, false); + // Let other clients resume as soon as the buffering client catches up + _group.IsPaused = false; - if (_group.IsBuffering()) { - // Others are buffering, tell this client to pause when ready - var when = request.When ??= DateTime.UtcNow; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; - var delay = _group.PositionTicks - clientPosition.Ticks; - - var command = NewSyncplayCommand(SendCommandType.Pause); - command.When = currentTime.AddMilliseconds( + if (delay > _group.GetHighestPing() * 2) + { + // Client that was buffering is recovering, notifying others to resume + _group.LastActivity = currentTime.AddMilliseconds( delay - ).ToUniversalTime().ToString("o"); - SendCommand(session, BroadcastType.SingleSession, command); + ); + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllExceptCurrentSession, command); } else { - // Let other clients resume as soon as the buffering client catches up - var when = request.When ??= DateTime.UtcNow; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; - var delay = _group.PositionTicks - clientPosition.Ticks; + // Client, that was buffering, resumed playback but did not update others in time + delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - _group.IsPaused = false; + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); - if (delay > _group.GetHighestPing() * 2) - { - // Client that was buffering is recovering, notifying others to resume - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllExceptSession, command); - } - else - { - // Client, that was buffering, resumed playback but did not update others in time - delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); - } - } - } - else - { - // Make sure client has latest group state - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.SingleSession, command); + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllGroup, command); + } } } - else if (request.Type.Equals(PlaybackRequestType.UpdatePing)) + else { - _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); + // Group was not waiting, make sure client has latest state + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command); } } + /// + /// Updates ping of a session. + /// + /// The session. + /// The update. + private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) + { + // Collected pings are used to account for network latency when unpausing playback + _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); + } + /// public GroupInfoView GetInfo() { - var info = new GroupInfoView(); - info.GroupId = GetGroupId().ToString(); - info.PlayingItemName = _group.PlayingItem.Name; - info.PlayingItemId = _group.PlayingItem.Id.ToString(); - info.PositionTicks = _group.PositionTicks; - info.Partecipants = _group.Partecipants.Values.Select(session => session.Session.UserName).ToArray(); - return info; + return new GroupInfoView() + { + GroupId = GetGroupId().ToString(), + PlayingItemName = _group.PlayingItem.Name, + PlayingItemId = _group.PlayingItem.Id.ToString(), + PositionTicks = _group.PositionTicks, + Participants = _group.Participants.Values.Select(session => session.Session.UserName).ToArray() + }; } } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 60d70e5fde..e7df8925e8 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Syncplay /// The groups. /// private readonly ConcurrentDictionary _groups = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private bool _disposed = false; @@ -64,8 +64,8 @@ namespace Emby.Server.Implementations.Syncplay _sessionManager = sessionManager; _libraryManager = libraryManager; - _sessionManager.SessionEnded += _sessionManager_SessionEnded; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + _sessionManager.SessionEnded += OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; } /// @@ -92,8 +92,8 @@ namespace Emby.Server.Implementations.Syncplay return; } - _sessionManager.SessionEnded -= _sessionManager_SessionEnded; - _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; _disposed = true; } @@ -106,14 +106,14 @@ namespace Emby.Server.Implementations.Syncplay } } - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; if (!IsSessionInGroup(session)) return; LeaveGroup(session); } - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { var session = e.Session; if (!IsSessionInGroup(session)) return; @@ -130,13 +130,13 @@ namespace Emby.Server.Implementations.Syncplay var item = _libraryManager.GetItemById(itemId); var hasParentalRatingAccess = user.Policy.MaxParentalRating.HasValue ? item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating : true; - if (!user.Policy.EnableAllFolders) + if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) { var collections = _libraryManager.GetCollectionFolders(item).Select( folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) ); var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Count() > 0 && hasParentalRatingAccess; + return intersect.Count() > 0; } else { @@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { - // TODO: shall an error message be sent back to the client? + // TODO: report the error to the client throw new ArgumentException("User does not have permission to create groups"); } @@ -187,22 +187,16 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - // TODO: shall an error message be sent back to the client? + // TODO: report the error to the client throw new ArgumentException("User does not have access to syncplay"); } - if (IsSessionInGroup(session)) - { - if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session); - } - ISyncplayController group; _groups.TryGetValue(groupId, out group); if (group == null) { - _logger.LogError("Syncplaymanager JoinGroup: " + groupId + " does not exist."); + _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not exist.", groupId); var update = new GroupUpdate(); update.Type = GroupUpdateType.NotInGroup; @@ -215,20 +209,26 @@ namespace Emby.Server.Implementations.Syncplay throw new ArgumentException("User does not have access to playing item"); } + if (IsSessionInGroup(session)) + { + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session); + } + group.SessionJoin(session, request); } /// public void LeaveGroup(SessionInfo session) { - // TODO: what happens to users that are in a group and get their permissions revoked? + // TODO: determine what happens to users that are in a group and get their permissions revoked ISyncplayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); + _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); var update = new GroupUpdate(); update.Type = GroupUpdateType.NotInGroup; @@ -257,9 +257,7 @@ namespace Emby.Server.Implementations.Syncplay if (session.NowPlayingItem != null) { return _groups.Values.Where( - group => HasAccessToItem(user, group.GetPlayingItemId()) - ).Where( - group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) + group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) && HasAccessToItem(user, group.GetPlayingItemId()) ).Select( group => group.GetInfo() ).ToList(); @@ -291,7 +289,7 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); + _logger.LogWarning("Syncplaymanager HandleRequest: {0} not in a group.", session.Id); var update = new GroupUpdate(); update.Type = GroupUpdateType.NotInGroup; @@ -302,7 +300,7 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void MapSessionToGroup(SessionInfo session, ISyncplayController group) + public void AddSessionToGroup(SessionInfo session, ISyncplayController group) { if (IsSessionInGroup(session)) { @@ -312,7 +310,7 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group) + public void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group) { if (!IsSessionInGroup(session)) { diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index af220ed81d..2eaf9ce834 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -90,12 +90,20 @@ namespace MediaBrowser.Api.Syncplay [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } + /// + /// Gets or sets the date used to pin PositionTicks in time. + /// + /// The date related to PositionTicks. [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string When { get; set; } [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] public long PositionTicks { get; set; } + /// + /// Gets or sets whether this is a buffering or a buffering-done request. + /// + /// true if buffering is complete; false otherwise. [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool Resume { get; set; } } @@ -162,8 +170,10 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayJoinGroup request) { var currentSession = GetSession(_sessionContext); - var joinRequest = new JoinGroupRequest(); - joinRequest.GroupId = Guid.Parse(request.GroupId); + var joinRequest = new JoinGroupRequest() + { + GroupId = Guid.Parse(request.GroupId) + }; try { joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); @@ -207,8 +217,10 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPlayRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.Play; + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Play + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -219,8 +231,10 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPauseRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.Pause; + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Pause + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -231,9 +245,11 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplaySeekRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.Seek; - syncplayRequest.PositionTicks = request.PositionTicks; + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Seek, + PositionTicks = request.PositionTicks + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -244,10 +260,12 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayBufferingRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = request.Resume ? PlaybackRequestType.BufferingComplete : PlaybackRequestType.Buffering; - syncplayRequest.When = DateTime.Parse(request.When); - syncplayRequest.PositionTicks = request.PositionTicks; + var syncplayRequest = new PlaybackRequest() + { + Type = request.Resume ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, + When = DateTime.Parse(request.When), + PositionTicks = request.PositionTicks + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -258,9 +276,11 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayUpdatePing request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.UpdatePing; - syncplayRequest.Ping = Convert.ToInt64(request.Ping); + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.UpdatePing, + Ping = Convert.ToInt64(request.Ping) + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index a69e0e293a..8974130157 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -54,7 +54,6 @@ namespace MediaBrowser.Api.Syncplay var response = new UtcTimeResponse(); response.RequestReceptionTime = requestReceptionTime; - var currentSession = GetSession(_sessionContext); // Important to keep the following two lines at the end var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs index 42e85ef864..8e886a2cb2 100644 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -46,11 +46,11 @@ namespace MediaBrowser.Controller.Syncplay public DateTime LastActivity { get; set; } /// - /// Gets the partecipants. + /// Gets the participants. /// - /// The partecipants. - public readonly ConcurrentDictionary Partecipants = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// The participants, or members of the group. + public readonly ConcurrentDictionary Participants = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// Checks if a session is in this group. @@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Syncplay /// true if the session is in this group; false otherwise. public bool ContainsSession(string sessionId) { - return Partecipants.ContainsKey(sessionId); + return Participants.ContainsKey(sessionId); } /// @@ -72,7 +72,7 @@ namespace MediaBrowser.Controller.Syncplay member.Session = session; member.Ping = DefaulPing; member.IsBuffering = false; - Partecipants[session.Id.ToString()] = member; + Participants[session.Id.ToString()] = member; } /// @@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Syncplay { if (!ContainsSession(session.Id.ToString())) return; GroupMember member; - Partecipants.Remove(session.Id.ToString(), out member); + Participants.Remove(session.Id.ToString(), out member); } /// @@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.Syncplay public void UpdatePing(SessionInfo session, long ping) { if (!ContainsSession(session.Id.ToString())) return; - Partecipants[session.Id.ToString()].Ping = ping; + Participants[session.Id.ToString()].Ping = ping; } /// @@ -105,7 +105,7 @@ namespace MediaBrowser.Controller.Syncplay public long GetHighestPing() { long max = Int64.MinValue; - foreach (var session in Partecipants.Values) + foreach (var session in Participants.Values) { max = Math.Max(max, session.Ping); } @@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.Syncplay public void SetBuffering(SessionInfo session, bool isBuffering) { if (!ContainsSession(session.Id.ToString())) return; - Partecipants[session.Id.ToString()].IsBuffering = isBuffering; + Participants[session.Id.ToString()].IsBuffering = isBuffering; } /// @@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Syncplay /// true if there is a session buffering in the group; false otherwise. public bool IsBuffering() { - foreach (var session in Partecipants.Values) + foreach (var session in Participants.Values) { if (session.IsBuffering) return true; } @@ -142,7 +142,7 @@ namespace MediaBrowser.Controller.Syncplay /// true if the group is empty; false otherwise. public bool IsEmpty() { - return Partecipants.Count == 0; + return Participants.Count == 0; } } } diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index d0cf8fa9c8..433d6d8bc1 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group. /// - void MapSessionToGroup(SessionInfo session, ISyncplayController group); + void AddSessionToGroup(SessionInfo session, ISyncplayController group); /// /// Unmaps a session from a group. @@ -58,6 +58,6 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group. /// - void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group); + void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group); } } diff --git a/MediaBrowser.Model/Syncplay/GroupInfoModel.cs b/MediaBrowser.Model/Syncplay/GroupInfoView.cs similarity index 84% rename from MediaBrowser.Model/Syncplay/GroupInfoModel.cs rename to MediaBrowser.Model/Syncplay/GroupInfoView.cs index 599c0dbfc9..50ad70630f 100644 --- a/MediaBrowser.Model/Syncplay/GroupInfoModel.cs +++ b/MediaBrowser.Model/Syncplay/GroupInfoView.cs @@ -1,7 +1,7 @@ namespace MediaBrowser.Model.Syncplay { /// - /// Class GroupInfoModel. + /// Class GroupInfoView. /// public class GroupInfoView { @@ -30,9 +30,9 @@ namespace MediaBrowser.Model.Syncplay public long PositionTicks { get; set; } /// - /// Gets or sets the partecipants. + /// Gets or sets the participants. /// - /// The partecipants. - public string[] Partecipants { get; set; } + /// The participants. + public string[] Participants { get; set; } } } diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs index 3d99b2718e..b3d49d09ef 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Syncplay /// /// A user is signaling that playback resumed. /// - BufferingComplete = 4, + BufferingDone = 4, /// /// A user is reporting its ping. /// From 73fcbe90c04d9b3de0fc0591565d9a3548a0fa70 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 22 Apr 2020 22:05:53 +0200 Subject: [PATCH 16/72] Send error messages to clients --- .../Syncplay/SyncplayManager.cs | 68 ++++++++++++++----- .../Syncplay/GroupUpdateType.cs | 34 +++++++--- 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index e7df8925e8..5aefd1fd98 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -165,8 +165,14 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { - // TODO: report the error to the client - throw new ArgumentException("User does not have permission to create groups"); + _logger.LogWarning("Syncplaymanager NewGroup: {0} does not have permission to create groups.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.CreateGroupDenied + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; } if (IsSessionInGroup(session)) @@ -187,8 +193,14 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - // TODO: report the error to the client - throw new ArgumentException("User does not have access to syncplay"); + _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to Syncplay.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.JoinGroupDenied + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; } ISyncplayController group; @@ -196,17 +208,27 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not exist.", groupId); + _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); - var update = new GroupUpdate(); - update.Type = GroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); + var error = new GroupUpdate() + { + Type = GroupUpdateType.GroupNotJoined + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } if (!HasAccessToItem(user, group.GetPlayingItemId())) { - throw new ArgumentException("User does not have access to playing item"); + _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + + var error = new GroupUpdate() + { + GroupId = group.GetGroupId().ToString(), + Type = GroupUpdateType.LibraryAccessDenied + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; } if (IsSessionInGroup(session)) @@ -230,9 +252,11 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); - var update = new GroupUpdate(); - update.Type = GroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } group.SessionLeave(session); @@ -280,8 +304,14 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - // TODO: same as LeaveGroup - throw new ArgumentException("User does not have access to syncplay"); + _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not have access to Syncplay.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.JoinGroupDenied + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; } ISyncplayController group; @@ -289,11 +319,13 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} not in a group.", session.Id); + _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); - var update = new GroupUpdate(); - update.Type = GroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } group.HandleRequest(session, request); diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs index 0ef8b27853..20e76932d4 100644 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -1,37 +1,53 @@ namespace MediaBrowser.Model.Syncplay { /// - /// Enum GroupUpdateType + /// Enum GroupUpdateType. /// public enum GroupUpdateType { /// /// The user-joined update. Tells members of a group about a new user. /// - UserJoined = 0, + UserJoined, /// /// The user-left update. Tells members of a group that a user left. /// - UserLeft = 1, + UserLeft, /// /// The group-joined update. Tells a user that the group has been joined. /// - GroupJoined = 2, + GroupJoined, /// /// The group-left update. Tells a user that the group has been left. /// - GroupLeft = 3, + GroupLeft, /// /// The group-wait update. Tells members of the group that a user is buffering. /// - GroupWait = 4, + GroupWait, /// /// The prepare-session update. Tells a user to load some content. /// - PrepareSession = 5, + PrepareSession, /// - /// The not-in-group update. Tells a user that no group has been joined. + /// The not-in-group error. Tells a user that it doesn't belong to a group. /// - NotInGroup = 7 + NotInGroup, + /// + /// The group-not-joined error. Sent when a request to join a group fails. + /// + GroupNotJoined, + /// + /// The create-group-denied error. Sent when a user tries to create a group without required permissions. + /// + CreateGroupDenied, + /// + /// The join-group-denied error. Sent when a user tries to join a group without required permissions. + /// + JoinGroupDenied, + /// + /// The library-access-denied error. Sent when a user tries to join a group without required access to the library. + /// + LibraryAccessDenied } } From 0b974d09ca08f70d9cd61d4871698956026b7b3b Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 28 Apr 2020 14:12:06 +0200 Subject: [PATCH 17/72] Synchronize access to data --- .../Session/SessionWebSocketListener.cs | 206 +++++++++++------- .../Syncplay/SyncplayManager.cs | 149 +++++++------ MediaBrowser.Api/Syncplay/TimeSyncService.cs | 8 +- 3 files changed, 216 insertions(+), 147 deletions(-) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index b0c6d0aa08..7a316b070c 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,6 +1,5 @@ -using System.Collections.Generic; using System; -using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Net.WebSockets; using System.Threading; @@ -26,9 +25,9 @@ namespace Emby.Server.Implementations.Session public readonly int WebSocketLostTimeout = 60; /// - /// The keep-alive timer factor; controls how often the timer will check on the status of the WebSockets. + /// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets. /// - public readonly double TimerFactor = 0.2; + public readonly double IntervalFactor = 0.2; /// /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. @@ -53,14 +52,24 @@ namespace Emby.Server.Implementations.Session private readonly IHttpServer _httpServer; /// - /// The KeepAlive timer. + /// The KeepAlive cancellation token. /// - private Timer _keepAliveTimer; + private CancellationTokenSource _keepAliveCancellationToken; + + /// + /// Lock used for accesing the KeepAlive cancellation token. + /// + private readonly object _keepAliveLock = new object(); /// /// The WebSocket watchlist. /// - private readonly ConcurrentDictionary _webSockets = new ConcurrentDictionary(); + private readonly HashSet _webSockets = new HashSet(); + + /// + /// Lock used for accesing the WebSockets watchlist. + /// + private readonly object _webSocketsLock = new object(); /// /// Initializes a new instance of the class. @@ -113,7 +122,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; - StopKeepAliveTimer(); + StopKeepAlive(); } /// @@ -140,6 +149,7 @@ namespace Emby.Server.Implementations.Session private void OnWebSocketClosed(object sender, EventArgs e) { var webSocket = (IWebSocketConnection) sender; + _logger.LogDebug("WebSockets {0} closed.", webSocket); RemoveWebSocket(webSocket); } @@ -147,15 +157,20 @@ namespace Emby.Server.Implementations.Session /// Adds a WebSocket to the KeepAlive watchlist. /// /// The WebSocket to monitor. - private async void KeepAliveWebSocket(IWebSocketConnection webSocket) + private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) { - if (!_webSockets.TryAdd(webSocket, 0)) + lock (_webSocketsLock) { - _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); - return; + if (!_webSockets.Add(webSocket)) + { + _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); + return; + } + webSocket.Closed += OnWebSocketClosed; + webSocket.LastKeepAliveDate = DateTime.UtcNow; + + StartKeepAlive(); } - webSocket.Closed += OnWebSocketClosed; - webSocket.LastKeepAliveDate = DateTime.UtcNow; // Notify WebSocket about timeout try @@ -164,10 +179,8 @@ namespace Emby.Server.Implementations.Session } catch (WebSocketException exception) { - _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket."); + _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket {0}.", webSocket); } - - StartKeepAliveTimer(); } /// @@ -176,88 +189,131 @@ namespace Emby.Server.Implementations.Session /// The WebSocket to remove. private void RemoveWebSocket(IWebSocketConnection webSocket) { - webSocket.Closed -= OnWebSocketClosed; - _webSockets.TryRemove(webSocket, out _); - } - - /// - /// Starts the KeepAlive timer. - /// - private void StartKeepAliveTimer() - { - if (_keepAliveTimer == null) + lock (_webSocketsLock) { - _keepAliveTimer = new Timer( - KeepAliveSockets, - null, - TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor), - TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor) - ); + if (!_webSockets.Remove(webSocket)) + { + _logger.LogWarning("WebSocket {0} not on watchlist.", webSocket); + } + else + { + webSocket.Closed -= OnWebSocketClosed; + } } } /// - /// Stops the KeepAlive timer. + /// Starts the KeepAlive watcher. /// - private void StopKeepAliveTimer() + private void StartKeepAlive() { - if (_keepAliveTimer != null) + lock (_keepAliveLock) { - _keepAliveTimer.Dispose(); - _keepAliveTimer = null; - } - - foreach (var pair in _webSockets) - { - pair.Key.Closed -= OnWebSocketClosed; + if (_keepAliveCancellationToken == null) + { + _keepAliveCancellationToken = new CancellationTokenSource(); + // Start KeepAlive watcher + KeepAliveSockets( + TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor), + _keepAliveCancellationToken.Token); + } } } /// - /// Checks status of KeepAlive of WebSockets. + /// Stops the KeepAlive watcher. /// - /// The state. - private async void KeepAliveSockets(object state) + private void StopKeepAlive() { - var inactive = _webSockets.Keys.Where(i => + lock (_keepAliveLock) { - var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; - return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); - }); - var lost = _webSockets.Keys.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); - - if (inactive.Any()) - { - _logger.LogDebug("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + if (_keepAliveCancellationToken != null) + { + _keepAliveCancellationToken.Cancel(); + _keepAliveCancellationToken = null; + } } - foreach (var webSocket in inactive) + lock (_webSocketsLock) { + foreach (var webSocket in _webSockets) + { + webSocket.Closed -= OnWebSocketClosed; + } + _webSockets.Clear(); + } + } + + /// + /// Checks status of KeepAlive of WebSockets once every the specified interval time. + /// + /// The interval. + /// The cancellation token. + private async Task KeepAliveSockets(TimeSpan interval, CancellationToken cancellationToken) + { + while (true) + { + _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count()); + + IEnumerable inactive; + IEnumerable lost; + lock (_webSocketsLock) + { + inactive = _webSockets.Where(i => + { + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + } + + if (inactive.Any()) + { + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + } + + foreach (var webSocket in inactive) + { + try + { + await SendForceKeepAlive(webSocket); + } + catch (WebSocketException exception) + { + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost = lost.Append(webSocket); + } + } + + lock (_webSocketsLock) + { + if (lost.Any()) + { + _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + foreach (var webSocket in lost.ToList()) + { + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); + } + } + + if (!_webSockets.Any()) + { + StopKeepAlive(); + } + } + + // Wait for next interval + Task task = Task.Delay(interval, cancellationToken); try { - await SendForceKeepAlive(webSocket); + await task; } - catch (WebSocketException exception) + catch (TaskCanceledException) { - _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); - lost.Append(webSocket); + return; } } - - if (lost.Any()) - { - _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); - foreach (var webSocket in lost) - { - // TODO: handle session relative to the lost webSocket - RemoveWebSocket(webSocket); - } - } - - if (!_webSockets.Any()) - { - StopKeepAliveTimer(); - } } /// diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 5aefd1fd98..eb61da7f32 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -42,14 +41,19 @@ namespace Emby.Server.Implementations.Syncplay /// /// The map between sessions and groups. /// - private readonly ConcurrentDictionary _sessionToGroupMap = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _sessionToGroupMap = + new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// The groups. /// - private readonly ConcurrentDictionary _groups = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _groups = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Lock used for accesing any group. + /// + private readonly object _groupsLock = new object(); private bool _disposed = false; @@ -175,15 +179,18 @@ namespace Emby.Server.Implementations.Syncplay return; } - if (IsSessionInGroup(session)) + lock (_groupsLock) { - LeaveGroup(session); + if (IsSessionInGroup(session)) + { + LeaveGroup(session); + } + + var group = new SyncplayController(_logger, _sessionManager, this); + _groups[group.GetGroupId().ToString()] = group; + + group.InitGroup(session); } - - var group = new SyncplayController(_logger, _sessionManager, this); - _groups[group.GetGroupId().ToString()] = group; - - group.InitGroup(session); } /// @@ -203,67 +210,73 @@ namespace Emby.Server.Implementations.Syncplay return; } - ISyncplayController group; - _groups.TryGetValue(groupId, out group); - - if (group == null) + lock (_groupsLock) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); + ISyncplayController group; + _groups.TryGetValue(groupId, out group); - var error = new GroupUpdate() + if (group == null) { - Type = GroupUpdateType.GroupNotJoined - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } + _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); - if (!HasAccessToItem(user, group.GetPlayingItemId())) - { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + var error = new GroupUpdate() + { + Type = GroupUpdateType.GroupNotJoined + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } - var error = new GroupUpdate() + if (!HasAccessToItem(user, group.GetPlayingItemId())) { - GroupId = group.GetGroupId().ToString(), - Type = GroupUpdateType.LibraryAccessDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } + _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); - if (IsSessionInGroup(session)) - { - if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session); - } + var error = new GroupUpdate() + { + GroupId = group.GetGroupId().ToString(), + Type = GroupUpdateType.LibraryAccessDenied + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } - group.SessionJoin(session, request); + if (IsSessionInGroup(session)) + { + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session); + } + + group.SessionJoin(session, request); + } } /// public void LeaveGroup(SessionInfo session) { // TODO: determine what happens to users that are in a group and get their permissions revoked - - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) + lock (_groupsLock) { - _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); + ISyncplayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); - var error = new GroupUpdate() + if (group == null) { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - group.SessionLeave(session); + _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); - if (group.IsGroupEmpty()) - { - _groups.Remove(group.GetGroupId().ToString(), out _); + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.SessionLeave(session); + + if (group.IsGroupEmpty()) + { + _groups.Remove(group.GetGroupId().ToString(), out _); + } } } @@ -314,21 +327,25 @@ namespace Emby.Server.Implementations.Syncplay return; } - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) + lock (_groupsLock) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); + ISyncplayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); - var error = new GroupUpdate() + if (group == null) { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; + _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.HandleRequest(session, request); } - group.HandleRequest(session, request); } /// diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index 8974130157..930968d9fc 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Syncplay { [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] - [Authenticated] public class GetUtcTime : IReturnVoid { // Nothing @@ -33,13 +32,10 @@ namespace MediaBrowser.Api.Syncplay public TimeSyncService( ILogger logger, IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISessionManager sessionManager, - ISessionContext sessionContext) + IHttpResultFactory httpResultFactory) : base(logger, serverConfigurationManager, httpResultFactory) { - _sessionManager = sessionManager; - _sessionContext = sessionContext; + // Do nothing } /// From b737301c709ba4c2575b2a38ddbba6de96477413 Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Sat, 2 May 2020 17:56:09 +0100 Subject: [PATCH 18/72] Auto discover published URL override --- .../EntryPoints/UdpServerEntryPoint.cs | 9 ++++--- .../IStartupOptions.cs | 5 ++++ Emby.Server.Implementations/Udp/UdpServer.cs | 24 ++++++++++++++++--- Jellyfin.Server/StartupOptions.cs | 11 +++++++++ 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 50ba0f8fac..6929c81f92 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Udp; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -22,6 +23,7 @@ namespace Emby.Server.Implementations.EntryPoints /// private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; + private readonly IConfiguration _config; /// /// The UDP server. @@ -35,18 +37,19 @@ namespace Emby.Server.Implementations.EntryPoints /// public UdpServerEntryPoint( ILogger logger, - IServerApplicationHost appHost) + IServerApplicationHost appHost, + IConfiguration configuration) { _logger = logger; _appHost = appHost; - + _config = configuration; } /// public async Task RunAsync() { - _udpServer = new UdpServer(_logger, _appHost); + _udpServer = new UdpServer(_logger, _appHost, _config); _udpServer.Start(PortNumber, _cancellationTokenSource.Token); } diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 16b68170be..a3a047057d 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -36,5 +36,10 @@ namespace Emby.Server.Implementations /// Gets the value of the --plugin-manifest-url command line option. /// string PluginManifestUrl { get; } + + /// + /// Gets the value of the --auto-discover-publish-url command line option. + /// + string AutoDiscoverPublishUrl { get; } } } diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index c91d137a72..57228d208d 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller; using MediaBrowser.Model.ApiClient; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Udp @@ -21,6 +22,12 @@ namespace Emby.Server.Implementations.Udp /// private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; + private readonly IConfiguration _config; + + /// + /// Address Override Configuration Key + /// + public const string AddressOverrideConfigKey = "AutoDiscoverAddressOverride"; private Socket _udpSocket; private IPEndPoint _endpoint; @@ -31,15 +38,26 @@ namespace Emby.Server.Implementations.Udp /// /// Initializes a new instance of the class. /// - public UdpServer(ILogger logger, IServerApplicationHost appHost) + public UdpServer(ILogger logger, IServerApplicationHost appHost, IConfiguration configuration) { _logger = logger; _appHost = appHost; + _config = configuration; } private async Task RespondToV2Message(string messageText, EndPoint endpoint, CancellationToken cancellationToken) { - var localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + string localUrl; + + if (!string.IsNullOrEmpty(_config[AddressOverrideConfigKey])) + { + localUrl = _config[AddressOverrideConfigKey]; + } + else + { + localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + } + if (!string.IsNullOrEmpty(localUrl)) { @@ -105,7 +123,7 @@ namespace Emby.Server.Implementations.Udp } catch (SocketException ex) { - _logger.LogError(ex, "Failed to receive data drom socket"); + _logger.LogError(ex, "Failed to receive data from socket"); } catch (OperationCanceledException) { diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 6e15d058fc..135ba9d7f8 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using CommandLine; using Emby.Server.Implementations; +using Emby.Server.Implementations.EntryPoints; +using Emby.Server.Implementations.Udp; using Emby.Server.Implementations.Updates; using MediaBrowser.Controller.Extensions; @@ -80,6 +82,10 @@ namespace Jellyfin.Server [Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")] public string? PluginManifestUrl { get; set; } + /// + [Option("auto-discover-publish-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] + public string? AutoDiscoverPublishUrl { get; set; } + /// /// Gets the command line options as a dictionary that can be used in the .NET configuration system. /// @@ -98,6 +104,11 @@ namespace Jellyfin.Server config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); } + if (AutoDiscoverPublishUrl != null) + { + config.Add(UdpServer.AddressOverrideConfigKey, AutoDiscoverPublishUrl); + } + return config; } } From 7d3eaea3fa550f601218cc6531bbdb0a614dda95 Mon Sep 17 00:00:00 2001 From: Christoph Potas Date: Sun, 3 May 2020 18:17:55 +0200 Subject: [PATCH 19/72] + add bd tag to clean string regex Signed-off-by: Christoph Potas --- Emby.Naming/Common/NamingOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index a2d75d0b8d..1b343790e8 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -142,7 +142,7 @@ namespace Emby.Naming.Common CleanStrings = new[] { - @"[ _\,\.\(\)\[\]\-](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|\[.*\])([ _\,\.\(\)\[\]\-]|$)", + @"[ _\,\.\(\)\[\]\-](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|bd|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|\[.*\])([ _\,\.\(\)\[\]\-]|$)", @"(\[.*\])" }; From 6e22e9222b68ad117550c02a8cbce2d65878f50b Mon Sep 17 00:00:00 2001 From: gion Date: Mon, 4 May 2020 19:46:02 +0200 Subject: [PATCH 20/72] Fix code issues --- .../HttpServer/WebSocketConnection.cs | 4 +- .../Session/SessionWebSocketListener.cs | 139 ++++++++------- .../Syncplay/SyncplayController.cs | 158 ++++++++++-------- .../Syncplay/SyncplayManager.cs | 61 ++++--- MediaBrowser.Api/Syncplay/SyncplayService.cs | 85 ++++++---- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 13 +- MediaBrowser.Controller/Syncplay/GroupInfo.cs | 8 +- .../Syncplay/ISyncplayController.cs | 13 +- .../Syncplay/ISyncplayManager.cs | 16 +- .../Syncplay/GroupUpdateType.cs | 6 +- .../Syncplay/PlaybackRequest.cs | 2 +- 11 files changed, 287 insertions(+), 218 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index c819c163a7..4c33ff71b0 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -238,10 +238,10 @@ namespace Emby.Server.Implementations.HttpServer return _socket.SendAsync(text, true, cancellationToken); } - private Task SendKeepAliveResponse() + private void SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; - return SendAsync(new WebSocketMessage + SendAsync(new WebSocketMessage { MessageType = "KeepAlive" }, CancellationToken.None); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 7a316b070c..d1ee22ea86 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -84,10 +84,10 @@ namespace Emby.Server.Implementations.Session _logger = loggerFactory.CreateLogger(GetType().Name); _json = json; _httpServer = httpServer; - httpServer.WebSocketConnected += _serverManager_WebSocketConnected; + httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; } - void _serverManager_WebSocketConnected(object sender, GenericEventArgs e) + void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) { var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint); @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { - _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; + _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected; StopKeepAlive(); } @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Session private void OnWebSocketClosed(object sender, EventArgs e) { var webSocket = (IWebSocketConnection) sender; - _logger.LogDebug("WebSockets {0} closed.", webSocket); + _logger.LogDebug("WebSocket {0} is closed.", webSocket); RemoveWebSocket(webSocket); } @@ -157,7 +157,7 @@ namespace Emby.Server.Implementations.Session /// Adds a WebSocket to the KeepAlive watchlist. /// /// The WebSocket to monitor. - private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) + private void KeepAliveWebSocket(IWebSocketConnection webSocket) { lock (_webSocketsLock) { @@ -175,11 +175,11 @@ namespace Emby.Server.Implementations.Session // Notify WebSocket about timeout try { - await SendForceKeepAlive(webSocket); + SendForceKeepAlive(webSocket).Wait(); } catch (WebSocketException exception) { - _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket {0}.", webSocket); + _logger.LogWarning(exception, "Cannot send ForceKeepAlive message to WebSocket {0}.", webSocket); } } @@ -213,7 +213,8 @@ namespace Emby.Server.Implementations.Session { _keepAliveCancellationToken = new CancellationTokenSource(); // Start KeepAlive watcher - KeepAliveSockets( + var task = RepeatAsyncCallbackEvery( + KeepAliveSockets, TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor), _keepAliveCancellationToken.Token); } @@ -245,73 +246,58 @@ namespace Emby.Server.Implementations.Session } /// - /// Checks status of KeepAlive of WebSockets once every the specified interval time. + /// Checks status of KeepAlive of WebSockets. /// - /// The interval. - /// The cancellation token. - private async Task KeepAliveSockets(TimeSpan interval, CancellationToken cancellationToken) + private async Task KeepAliveSockets() { - while (true) + IEnumerable inactive; + IEnumerable lost; + + lock (_webSocketsLock) { - _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count()); + _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count); - IEnumerable inactive; - IEnumerable lost; - lock (_webSocketsLock) + inactive = _webSockets.Where(i => { - inactive = _webSockets.Where(i => - { - var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; - return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); - }); - lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); - } + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + } - if (inactive.Any()) - { - _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); - } + if (inactive.Any()) + { + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + } - foreach (var webSocket in inactive) - { - try - { - await SendForceKeepAlive(webSocket); - } - catch (WebSocketException exception) - { - _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); - lost = lost.Append(webSocket); - } - } - - lock (_webSocketsLock) - { - if (lost.Any()) - { - _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); - foreach (var webSocket in lost.ToList()) - { - // TODO: handle session relative to the lost webSocket - RemoveWebSocket(webSocket); - } - } - - if (!_webSockets.Any()) - { - StopKeepAlive(); - } - } - - // Wait for next interval - Task task = Task.Delay(interval, cancellationToken); + foreach (var webSocket in inactive) + { try { - await task; + await SendForceKeepAlive(webSocket); } - catch (TaskCanceledException) + catch (WebSocketException exception) { - return; + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost = lost.Append(webSocket); + } + } + + lock (_webSocketsLock) + { + if (lost.Any()) + { + _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + foreach (var webSocket in lost.ToList()) + { + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); + } + } + + if (!_webSockets.Any()) + { + StopKeepAlive(); } } } @@ -329,5 +315,30 @@ namespace Emby.Server.Implementations.Session Data = WebSocketLostTimeout }, CancellationToken.None); } + + /// + /// Runs a given async callback once every specified interval time, until cancelled. + /// + /// The async callback. + /// The interval time. + /// The cancellation token. + /// Task. + private async Task RepeatAsyncCallbackEvery(Func callback, TimeSpan interval, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + await callback(); + Task task = Task.Delay(interval, cancellationToken); + + try + { + await task; + } + catch (TaskCanceledException) + { + return; + } + } + } } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index 02cf08cd7c..8cc3d1fac3 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -7,13 +7,15 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Syncplay; using MediaBrowser.Model.Session; using MediaBrowser.Model.Syncplay; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Syncplay { /// /// Class SyncplayController. /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// public class SyncplayController : ISyncplayController, IDisposable { /// @@ -39,11 +41,6 @@ namespace Emby.Server.Implementations.Syncplay AllReady = 3 } - /// - /// The logger. - /// - private readonly ILogger _logger; - /// /// The session manager. /// @@ -71,11 +68,9 @@ namespace Emby.Server.Implementations.Syncplay private bool _disposed = false; public SyncplayController( - ILogger logger, ISessionManager sessionManager, ISyncplayManager syncplayManager) { - _logger = logger; _sessionManager = sessionManager; _syncplayManager = syncplayManager; } @@ -110,6 +105,16 @@ namespace Emby.Server.Implementations.Syncplay } } + /// + /// Converts DateTime to UTC string. + /// + /// The date to convert. + /// The UTC string. + private string DateToUTCString(DateTime date) + { + return date.ToUniversalTime().ToString("o"); + } + /// /// Filters sessions of this group. /// @@ -149,15 +154,16 @@ namespace Emby.Server.Implementations.Syncplay /// The current session. /// The filtering type. /// The message to send. + /// The cancellation token. /// The task. - private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message) + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message, CancellationToken cancellationToken) { IEnumerable GetTasks() { SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, cancellationToken); } } @@ -170,15 +176,16 @@ namespace Emby.Server.Implementations.Syncplay /// The current session. /// The filtering type. /// The message to send. + /// The cancellation token. /// The task. - private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message) + private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken) { IEnumerable GetTasks() { SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, cancellationToken); } } @@ -197,8 +204,8 @@ namespace Emby.Server.Implementations.Syncplay GroupId = _group.GroupId.ToString(), Command = type, PositionTicks = _group.PositionTicks, - When = _group.LastActivity.ToUniversalTime().ToString("o"), - EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o") + When = DateToUTCString(_group.LastActivity), + EmittedAt = DateToUTCString(DateTime.UtcNow) }; } @@ -219,46 +226,46 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void InitGroup(SessionInfo session) + public void InitGroup(SessionInfo session, CancellationToken cancellationToken) { _group.AddSession(session); _syncplayManager.AddSessionToGroup(session, this); _group.PlayingItem = session.FullNowPlayingItem; _group.IsPaused = true; - _group.PositionTicks = session.PlayState.PositionTicks ??= 0; + _group.PositionTicks = session.PlayState.PositionTicks ?? 0; _group.LastActivity = DateTime.UtcNow; - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); } /// - public void SessionJoin(SessionInfo session, JoinGroupRequest request) + public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) { if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) { _group.AddSession(session); _syncplayManager.AddSessionToGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); // Client join and play, syncing will happen client side if (!_group.IsPaused) { var playCommand = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, playCommand); + SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken); } else { var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); } } else @@ -267,25 +274,25 @@ namespace Emby.Server.Implementations.Syncplay playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(session, BroadcastType.CurrentSession, update); + SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); } } /// - public void SessionLeave(SessionInfo session) + public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) { _group.RemoveSession(session); _syncplayManager.RemoveSessionFromGroup(session, this); var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); } /// - public void HandleRequest(SessionInfo session, PlaybackRequest request) + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { // The server's job is to mantain a consistent state to which clients refer to, // as also to notify clients of state changes. @@ -294,19 +301,19 @@ namespace Emby.Server.Implementations.Syncplay switch (request.Type) { case PlaybackRequestType.Play: - HandlePlayRequest(session, request); + HandlePlayRequest(session, request, cancellationToken); break; case PlaybackRequestType.Pause: - HandlePauseRequest(session, request); + HandlePauseRequest(session, request, cancellationToken); break; case PlaybackRequestType.Seek: - HandleSeekRequest(session, request); + HandleSeekRequest(session, request, cancellationToken); break; case PlaybackRequestType.Buffering: - HandleBufferingRequest(session, request); + HandleBufferingRequest(session, request, cancellationToken); break; case PlaybackRequestType.BufferingDone: - HandleBufferingDoneRequest(session, request); + HandleBufferingDoneRequest(session, request, cancellationToken); break; case PlaybackRequestType.UpdatePing: HandlePingUpdateRequest(session, request); @@ -319,7 +326,8 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The play action. - private void HandlePlayRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (_group.IsPaused) { @@ -337,13 +345,13 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } else { // Client got lost, sending current state var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -352,7 +360,8 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The pause action. - private void HandlePauseRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (!_group.IsPaused) { @@ -366,13 +375,13 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } else { // Client got lost, sending current state var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -381,16 +390,11 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The seek action. - private void HandleSeekRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { // Sanitize PositionTicks - var ticks = request.PositionTicks ??= 0; - ticks = ticks >= 0 ? ticks : 0; - if (_group.PlayingItem.RunTimeTicks != null) - { - var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; - ticks = ticks > runTimeTicks ? runTimeTicks : ticks; - } + var ticks = SanitizePositionTicks(request.PositionTicks); // Pause and seek _group.IsPaused = true; @@ -398,7 +402,7 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = DateTime.UtcNow; var command = NewSyncplayCommand(SendCommandType.Seek); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } /// @@ -406,7 +410,8 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The buffering action. - private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (!_group.IsPaused) { @@ -421,16 +426,16 @@ namespace Emby.Server.Implementations.Syncplay // Send pause command to all non-buffering sessions var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllReady, command); + SendCommand(session, BroadcastType.AllReady, command, cancellationToken); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); } else { // Client got lost, sending current state var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -439,26 +444,28 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The buffering-done action. - private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (_group.IsPaused) { _group.SetBuffering(session, false); - var when = request.When ??= DateTime.UtcNow; + var requestTicks = SanitizePositionTicks(request.PositionTicks); + + var when = request.When ?? DateTime.UtcNow; var currentTime = DateTime.UtcNow; var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime; var delay = _group.PositionTicks - clientPosition.Ticks; if (_group.IsBuffering()) { - // Others are buffering, tell this client to pause when ready + // Others are still buffering, tell this client to pause when ready var command = NewSyncplayCommand(SendCommandType.Pause); - command.When = currentTime.AddMilliseconds( - delay - ).ToUniversalTime().ToString("o"); - SendCommand(session, BroadcastType.CurrentSession, command); + var pauseAtTime = currentTime.AddMilliseconds(delay); + command.When = DateToUTCString(pauseAtTime); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } else { @@ -472,7 +479,7 @@ namespace Emby.Server.Implementations.Syncplay delay ); var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllExceptCurrentSession, command); + SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); } else { @@ -485,7 +492,7 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } } } @@ -493,10 +500,27 @@ namespace Emby.Server.Implementations.Syncplay { // Group was not waiting, make sure client has latest state var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } + /// + /// Sanitizes the PositionTicks, considers the current playing item when available. + /// + /// The PositionTicks. + /// The sanitized PositionTicks. + private long SanitizePositionTicks(long? positionTicks) + { + var ticks = positionTicks ?? 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; + } + return ticks; + } + /// /// Updates ping of a session. /// @@ -505,7 +529,7 @@ namespace Emby.Server.Implementations.Syncplay private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) { // Collected pings are used to account for network latency when unpausing playback - _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); + _group.UpdatePing(session, request.Ping ?? _group.DefaulPing); } /// @@ -517,7 +541,7 @@ namespace Emby.Server.Implementations.Syncplay PlayingItemName = _group.PlayingItem.Name, PlayingItemId = _group.PlayingItem.Id.ToString(), PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).ToArray() + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() }; } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index eb61da7f32..7074e2225e 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -114,14 +114,14 @@ namespace Emby.Server.Implementations.Syncplay { var session = e.SessionInfo; if (!IsSessionInGroup(session)) return; - LeaveGroup(session); + LeaveGroup(session, CancellationToken.None); } private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { var session = e.Session; if (!IsSessionInGroup(session)) return; - LeaveGroup(session); + LeaveGroup(session, CancellationToken.None); } private bool IsSessionInGroup(SessionInfo session) @@ -132,7 +132,13 @@ namespace Emby.Server.Implementations.Syncplay private bool HasAccessToItem(User user, Guid itemId) { var item = _libraryManager.GetItemById(itemId); - var hasParentalRatingAccess = user.Policy.MaxParentalRating.HasValue ? item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating : true; + + // Check ParentalRating access + var hasParentalRatingAccess = true; + if (user.Policy.MaxParentalRating.HasValue) + { + hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; + } if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) { @@ -140,7 +146,7 @@ namespace Emby.Server.Implementations.Syncplay folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) ); var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Count() > 0; + return intersect.Any(); } else { @@ -163,13 +169,13 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void NewGroup(SessionInfo session) + public void NewGroup(SessionInfo session, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { - _logger.LogWarning("Syncplaymanager NewGroup: {0} does not have permission to create groups.", session.Id); + _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); var error = new GroupUpdate() { @@ -183,24 +189,24 @@ namespace Emby.Server.Implementations.Syncplay { if (IsSessionInGroup(session)) { - LeaveGroup(session); + LeaveGroup(session, cancellationToken); } - var group = new SyncplayController(_logger, _sessionManager, this); + var group = new SyncplayController(_sessionManager, this); _groups[group.GetGroupId().ToString()] = group; - group.InitGroup(session); + group.InitGroup(session, cancellationToken); } } /// - public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request) + public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to Syncplay.", session.Id); + _logger.LogWarning("JoinGroup: {0} does not have access to Syncplay.", session.Id); var error = new GroupUpdate() { @@ -217,11 +223,11 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); + _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); var error = new GroupUpdate() { - Type = GroupUpdateType.GroupNotJoined + Type = GroupUpdateType.GroupDoesNotExist }; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; @@ -229,7 +235,7 @@ namespace Emby.Server.Implementations.Syncplay if (!HasAccessToItem(user, group.GetPlayingItemId())) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); var error = new GroupUpdate() { @@ -243,15 +249,15 @@ namespace Emby.Server.Implementations.Syncplay if (IsSessionInGroup(session)) { if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session); + LeaveGroup(session, cancellationToken); } - group.SessionJoin(session, request); + group.SessionJoin(session, request, cancellationToken); } } /// - public void LeaveGroup(SessionInfo session) + public void LeaveGroup(SessionInfo session, CancellationToken cancellationToken) { // TODO: determine what happens to users that are in a group and get their permissions revoked lock (_groupsLock) @@ -261,7 +267,7 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); + _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id); var error = new GroupUpdate() { @@ -271,17 +277,18 @@ namespace Emby.Server.Implementations.Syncplay return; } - group.SessionLeave(session); + group.SessionLeave(session, cancellationToken); if (group.IsGroupEmpty()) { + _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); _groups.Remove(group.GetGroupId().ToString(), out _); } } } /// - public List ListGroups(SessionInfo session) + public List ListGroups(SessionInfo session, Guid filterItemId) { var user = _userManager.GetUserById(session.UserId); @@ -290,11 +297,11 @@ namespace Emby.Server.Implementations.Syncplay return new List(); } - // Filter by playing item if the user is viewing something already - if (session.NowPlayingItem != null) + // Filter by item if requested + if (!filterItemId.Equals(Guid.Empty)) { return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) && HasAccessToItem(user, group.GetPlayingItemId()) + group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId()) ).Select( group => group.GetInfo() ).ToList(); @@ -311,13 +318,13 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void HandleRequest(SessionInfo session, PlaybackRequest request) + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not have access to Syncplay.", session.Id); + _logger.LogWarning("HandleRequest: {0} does not have access to Syncplay.", session.Id); var error = new GroupUpdate() { @@ -334,7 +341,7 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); + _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id); var error = new GroupUpdate() { @@ -344,7 +351,7 @@ namespace Emby.Server.Implementations.Syncplay return; } - group.HandleRequest(session, request); + group.HandleRequest(session, request, cancellationToken); } } diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index 2eaf9ce834..4b6e16762a 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System; using System.Collections.Generic; using MediaBrowser.Controller.Configuration; @@ -48,12 +49,19 @@ namespace MediaBrowser.Api.Syncplay public string SessionId { get; set; } } - [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups playing same item")] + [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups")] [Authenticated] public class SyncplayListGroups : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } + + /// + /// Gets or sets the filter item id. + /// + /// The filter item id. + [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string FilterItemId { get; set; } } [Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")] @@ -104,8 +112,8 @@ namespace MediaBrowser.Api.Syncplay /// Gets or sets whether this is a buffering or a buffering-done request. /// /// true if buffering is complete; false otherwise. - [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Resume { get; set; } + [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool BufferingDone { get; set; } } [Route("/Syncplay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] @@ -124,11 +132,6 @@ namespace MediaBrowser.Api.Syncplay /// public class SyncplayService : BaseApiService { - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - /// /// The session context. /// @@ -143,12 +146,10 @@ namespace MediaBrowser.Api.Syncplay ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, - ISessionManager sessionManager, ISessionContext sessionContext, ISyncplayManager syncplayManager) : base(logger, serverConfigurationManager, httpResultFactory) { - _sessionManager = sessionManager; _sessionContext = sessionContext; _syncplayManager = syncplayManager; } @@ -160,7 +161,7 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayNewGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.NewGroup(currentSession); + _syncplayManager.NewGroup(currentSession, CancellationToken.None); } /// @@ -174,19 +175,27 @@ namespace MediaBrowser.Api.Syncplay { GroupId = Guid.Parse(request.GroupId) }; - try + + // Both null and empty strings mean that client isn't playing anything + if (!String.IsNullOrEmpty(request.PlayingItemId)) { - joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); + try + { + joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); + } + catch (ArgumentNullException) + { + // Should never happen, but just in case + Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); + return; + } + catch (FormatException) + { + Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); + return; + } } - catch (ArgumentNullException) - { - // Do nothing - } - catch (FormatException) - { - // Do nothing - } - _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest); + _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); } /// @@ -196,7 +205,7 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayLeaveGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.LeaveGroup(currentSession); + _syncplayManager.LeaveGroup(currentSession, CancellationToken.None); } /// @@ -207,7 +216,23 @@ namespace MediaBrowser.Api.Syncplay public List Post(SyncplayListGroups request) { var currentSession = GetSession(_sessionContext); - return _syncplayManager.ListGroups(currentSession); + var filterItemId = Guid.Empty; + if (!String.IsNullOrEmpty(request.FilterItemId)) + { + try + { + filterItemId = Guid.Parse(request.FilterItemId); + } + catch (ArgumentNullException) + { + Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); + } + catch (FormatException) + { + Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); + } + } + return _syncplayManager.ListGroups(currentSession, filterItemId); } /// @@ -221,7 +246,7 @@ namespace MediaBrowser.Api.Syncplay { Type = PlaybackRequestType.Play }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -235,7 +260,7 @@ namespace MediaBrowser.Api.Syncplay { Type = PlaybackRequestType.Pause }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -250,7 +275,7 @@ namespace MediaBrowser.Api.Syncplay Type = PlaybackRequestType.Seek, PositionTicks = request.PositionTicks }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -262,11 +287,11 @@ namespace MediaBrowser.Api.Syncplay var currentSession = GetSession(_sessionContext); var syncplayRequest = new PlaybackRequest() { - Type = request.Resume ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, + Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, When = DateTime.Parse(request.When), PositionTicks = request.PositionTicks }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -281,7 +306,7 @@ namespace MediaBrowser.Api.Syncplay Type = PlaybackRequestType.UpdatePing, Ping = Convert.ToInt64(request.Ping) }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } } } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index 930968d9fc..9a26ffd996 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -1,7 +1,6 @@ using System; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Services; using MediaBrowser.Model.Syncplay; using Microsoft.Extensions.Logging; @@ -19,16 +18,6 @@ namespace MediaBrowser.Api.Syncplay /// public class TimeSyncService : BaseApiService { - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - - /// - /// The session context. - /// - private readonly ISessionContext _sessionContext; - public TimeSyncService( ILogger logger, IServerConfigurationManager serverConfigurationManager, @@ -55,7 +44,7 @@ namespace MediaBrowser.Api.Syncplay var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); response.ResponseTransmissionTime = responseTransmissionTime; - // Implementing NTP on such a high level results in this useless + // Implementing NTP on such a high level results in this useless // information being sent. On the other hand it enables future additions. return response; } diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs index 8e886a2cb2..c01fead836 100644 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Session; @@ -9,6 +8,9 @@ namespace MediaBrowser.Controller.Syncplay /// /// Class GroupInfo. /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// public class GroupInfo { /// @@ -49,8 +51,8 @@ namespace MediaBrowser.Controller.Syncplay /// Gets the participants. /// /// The participants, or members of the group. - public readonly ConcurrentDictionary Participants = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + public readonly Dictionary Participants = + new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Checks if a session is in this group. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs index 5b08eac0a4..34eae40624 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Syncplay; @@ -31,27 +32,31 @@ namespace MediaBrowser.Controller.Syncplay /// Initializes the group with the session's info. /// /// The session. - void InitGroup(SessionInfo session); + /// The cancellation token. + void InitGroup(SessionInfo session, CancellationToken cancellationToken); /// /// Adds the session to the group. /// /// The session. /// The request. - void SessionJoin(SessionInfo session, JoinGroupRequest request); + /// The cancellation token. + void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); /// /// Removes the session from the group. /// /// The session. - void SessionLeave(SessionInfo session); + /// The cancellation token. + void SessionLeave(SessionInfo session, CancellationToken cancellationToken); /// /// Handles the requested action by the session. /// /// The session. /// The requested action. - void HandleRequest(SessionInfo session, PlaybackRequest request); + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); /// /// Gets the info about the group for the clients. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index 433d6d8bc1..fbc208d27d 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Syncplay; @@ -14,7 +15,8 @@ namespace MediaBrowser.Controller.Syncplay /// Creates a new group. /// /// The session that's creating the group. - void NewGroup(SessionInfo session); + /// The cancellation token. + void NewGroup(SessionInfo session, CancellationToken cancellationToken); /// /// Adds the session to a group. @@ -22,27 +24,31 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group id. /// The request. - void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request); + /// The cancellation token. + void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); /// /// Removes the session from a group. /// /// The session. - void LeaveGroup(SessionInfo session); + /// The cancellation token. + void LeaveGroup(SessionInfo session, CancellationToken cancellationToken); /// /// Gets list of available groups for a session. /// /// The session. + /// The item id to filter by. /// The list of available groups. - List ListGroups(SessionInfo session); + List ListGroups(SessionInfo session, Guid filterItemId); /// /// Handle a request by a session in a group. /// /// The session. /// The request. - void HandleRequest(SessionInfo session, PlaybackRequest request); + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); /// /// Maps a session to a group. diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs index 20e76932d4..9f40f9577f 100644 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -30,13 +30,13 @@ namespace MediaBrowser.Model.Syncplay /// PrepareSession, /// - /// The not-in-group error. Tells a user that it doesn't belong to a group. + /// The not-in-group error. Tells a user that they don't belong to a group. /// NotInGroup, /// - /// The group-not-joined error. Sent when a request to join a group fails. + /// The group-does-not-exist error. Sent when trying to join a non-existing group. /// - GroupNotJoined, + GroupDoesNotExist, /// /// The create-group-denied error. Sent when a user tries to create a group without required permissions. /// diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs index cae769db0d..ba97641f63 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Syncplay /// Gets or sets the request type. /// /// The request type. - public PlaybackRequestType Type; + public PlaybackRequestType Type { get; set; } /// /// Gets or sets when the request has been made by the client. From 8a6ec2fb713cb77e91d2fceea8b4fce8e7d17395 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 6 May 2020 23:42:53 +0200 Subject: [PATCH 21/72] Rename Syncplay to SyncPlay --- .../ApplicationHost.cs | 6 +- .../Session/SessionManager.cs | 10 +- .../SyncPlayController.cs} | 76 ++++++------- .../SyncPlayManager.cs} | 66 +++++------ .../SyncPlayService.cs} | 104 +++++++++--------- .../{Syncplay => SyncPlay}/TimeSyncService.cs | 4 +- .../Session/ISessionManager.cs | 10 +- .../{Syncplay => SyncPlay}/GroupInfo.cs | 2 +- .../{Syncplay => SyncPlay}/GroupMember.cs | 2 +- .../ISyncPlayController.cs} | 8 +- .../ISyncPlayManager.cs} | 12 +- .../Configuration/SyncplayAccess.cs | 6 +- .../{Syncplay => SyncPlay}/GroupInfoView.cs | 2 +- .../{Syncplay => SyncPlay}/GroupUpdate.cs | 2 +- .../{Syncplay => SyncPlay}/GroupUpdateType.cs | 2 +- .../JoinGroupRequest.cs | 2 +- .../{Syncplay => SyncPlay}/PlaybackRequest.cs | 2 +- .../PlaybackRequestType.cs | 2 +- .../{Syncplay => SyncPlay}/SendCommand.cs | 2 +- .../{Syncplay => SyncPlay}/SendCommandType.cs | 2 +- .../{Syncplay => SyncPlay}/UtcTimeResponse.cs | 2 +- MediaBrowser.Model/Users/UserPolicy.cs | 8 +- 22 files changed, 166 insertions(+), 166 deletions(-) rename Emby.Server.Implementations/{Syncplay/SyncplayController.cs => SyncPlay/SyncPlayController.cs} (90%) rename Emby.Server.Implementations/{Syncplay/SyncplayManager.cs => SyncPlay/SyncPlayManager.cs} (87%) rename MediaBrowser.Api/{Syncplay/SyncplayService.cs => SyncPlay/SyncPlayService.cs} (75%) rename MediaBrowser.Api/{Syncplay => SyncPlay}/TimeSyncService.cs (96%) rename MediaBrowser.Controller/{Syncplay => SyncPlay}/GroupInfo.cs (99%) rename MediaBrowser.Controller/{Syncplay => SyncPlay}/GroupMember.cs (94%) rename MediaBrowser.Controller/{Syncplay/ISyncplayController.cs => SyncPlay/ISyncPlayController.cs} (93%) rename MediaBrowser.Controller/{Syncplay/ISyncplayManager.cs => SyncPlay/ISyncPlayManager.cs} (90%) rename MediaBrowser.Model/{Syncplay => SyncPlay}/GroupInfoView.cs (96%) rename MediaBrowser.Model/{Syncplay => SyncPlay}/GroupUpdate.cs (94%) rename MediaBrowser.Model/{Syncplay => SyncPlay}/GroupUpdateType.cs (97%) rename MediaBrowser.Model/{Syncplay => SyncPlay}/JoinGroupRequest.cs (93%) rename MediaBrowser.Model/{Syncplay => SyncPlay}/PlaybackRequest.cs (95%) rename MediaBrowser.Model/{Syncplay => SyncPlay}/PlaybackRequestType.cs (95%) rename MediaBrowser.Model/{Syncplay => SyncPlay}/SendCommand.cs (96%) rename MediaBrowser.Model/{Syncplay => SyncPlay}/SendCommandType.cs (93%) rename MediaBrowser.Model/{Syncplay => SyncPlay}/UtcTimeResponse.cs (94%) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8419014c2c..730323c224 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,7 +47,7 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; -using Emby.Server.Implementations.Syncplay; +using Emby.Server.Implementations.SyncPlay; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -81,7 +81,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.TV; -using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Controller.SyncPlay; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.Model.Activity; @@ -645,7 +645,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6a64209c1a..aab745de4f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -25,7 +25,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -1156,19 +1156,19 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) + public async Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncplayCommand", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "SyncPlayCommand", command, cancellationToken).ConfigureAwait(false); } /// - public async Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) + public async Task SendSyncPlayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncplayGroupUpdate", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "SyncPlayGroupUpdate", command, cancellationToken).ConfigureAwait(false); } private IEnumerable TranslateItemForPlayback(Guid id, User user) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs similarity index 90% rename from Emby.Server.Implementations/Syncplay/SyncplayController.cs rename to Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index 8cc3d1fac3..9c9758de1c 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -4,19 +4,19 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; -namespace Emby.Server.Implementations.Syncplay +namespace Emby.Server.Implementations.SyncPlay { /// - /// Class SyncplayController. + /// Class SyncPlayController. /// /// /// Class is not thread-safe, external locking is required when accessing methods. /// - public class SyncplayController : ISyncplayController, IDisposable + public class SyncPlayController : ISyncPlayController, IDisposable { /// /// Used to filter the sessions of a group. @@ -47,9 +47,9 @@ namespace Emby.Server.Implementations.Syncplay private readonly ISessionManager _sessionManager; /// - /// The syncplay manager. + /// The SyncPlay manager. /// - private readonly ISyncplayManager _syncplayManager; + private readonly ISyncPlayManager _syncPlayManager; /// /// The group to manage. @@ -67,12 +67,12 @@ namespace Emby.Server.Implementations.Syncplay private bool _disposed = false; - public SyncplayController( + public SyncPlayController( ISessionManager sessionManager, - ISyncplayManager syncplayManager) + ISyncPlayManager syncPlayManager) { _sessionManager = sessionManager; - _syncplayManager = syncplayManager; + _syncPlayManager = syncPlayManager; } /// @@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.Syncplay SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, cancellationToken); + yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), message, cancellationToken); } } @@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.Syncplay SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, cancellationToken); + yield return _sessionManager.SendSyncPlayCommand(session.Id.ToString(), message, cancellationToken); } } @@ -197,7 +197,7 @@ namespace Emby.Server.Implementations.Syncplay /// /// The command type. /// The SendCommand. - private SendCommand NewSyncplayCommand(SendCommandType type) + private SendCommand NewSyncPlayCommand(SendCommandType type) { return new SendCommand() { @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.Syncplay /// The update type. /// The data to send. /// The GroupUpdate. - private GroupUpdate NewSyncplayGroupUpdate(GroupUpdateType type, T data) + private GroupUpdate NewSyncPlayGroupUpdate(GroupUpdateType type, T data) { return new GroupUpdate() { @@ -229,16 +229,16 @@ namespace Emby.Server.Implementations.Syncplay public void InitGroup(SessionInfo session, CancellationToken cancellationToken) { _group.AddSession(session); - _syncplayManager.AddSessionToGroup(session, this); + _syncPlayManager.AddSessionToGroup(session, this); _group.PlayingItem = session.FullNowPlayingItem; _group.IsPaused = true; _group.PositionTicks = session.PlayState.PositionTicks ?? 0; _group.LastActivity = DateTime.UtcNow; - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); - var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); + var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); } @@ -248,23 +248,23 @@ namespace Emby.Server.Implementations.Syncplay if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) { _group.AddSession(session); - _syncplayManager.AddSessionToGroup(session, this); + _syncPlayManager.AddSessionToGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); // Client join and play, syncing will happen client side if (!_group.IsPaused) { - var playCommand = NewSyncplayCommand(SendCommandType.Play); + var playCommand = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken); } else { - var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); + var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); } } @@ -273,7 +273,7 @@ namespace Emby.Server.Implementations.Syncplay var playRequest = new PlayRequest(); playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; - var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); + var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); } } @@ -282,12 +282,12 @@ namespace Emby.Server.Implementations.Syncplay public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) { _group.RemoveSession(session); - _syncplayManager.RemoveSessionFromGroup(session, this); + _syncPlayManager.RemoveSessionFromGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); } @@ -344,13 +344,13 @@ namespace Emby.Server.Implementations.Syncplay delay ); - var command = NewSyncplayCommand(SendCommandType.Play); + var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } else { // Client got lost, sending current state - var command = NewSyncplayCommand(SendCommandType.Play); + var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -374,13 +374,13 @@ namespace Emby.Server.Implementations.Syncplay // (a pause request may be issued during the delay added to account for latency) _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - var command = NewSyncplayCommand(SendCommandType.Pause); + var command = NewSyncPlayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } else { // Client got lost, sending current state - var command = NewSyncplayCommand(SendCommandType.Pause); + var command = NewSyncPlayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -401,7 +401,7 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks = ticks; _group.LastActivity = DateTime.UtcNow; - var command = NewSyncplayCommand(SendCommandType.Seek); + var command = NewSyncPlayCommand(SendCommandType.Seek); SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } @@ -425,16 +425,16 @@ namespace Emby.Server.Implementations.Syncplay _group.SetBuffering(session, true); // Send pause command to all non-buffering sessions - var command = NewSyncplayCommand(SendCommandType.Pause); + var command = NewSyncPlayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.AllReady, command, cancellationToken); - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); } else { // Client got lost, sending current state - var command = NewSyncplayCommand(SendCommandType.Pause); + var command = NewSyncPlayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -462,7 +462,7 @@ namespace Emby.Server.Implementations.Syncplay if (_group.IsBuffering()) { // Others are still buffering, tell this client to pause when ready - var command = NewSyncplayCommand(SendCommandType.Pause); + var command = NewSyncPlayCommand(SendCommandType.Pause); var pauseAtTime = currentTime.AddMilliseconds(delay); command.When = DateToUTCString(pauseAtTime); SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); @@ -478,7 +478,7 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = currentTime.AddMilliseconds( delay ); - var command = NewSyncplayCommand(SendCommandType.Play); + var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); } else @@ -491,7 +491,7 @@ namespace Emby.Server.Implementations.Syncplay delay ); - var command = NewSyncplayCommand(SendCommandType.Play); + var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } } @@ -499,7 +499,7 @@ namespace Emby.Server.Implementations.Syncplay else { // Group was not waiting, make sure client has latest state - var command = NewSyncplayCommand(SendCommandType.Play); + var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs similarity index 87% rename from Emby.Server.Implementations/Syncplay/SyncplayManager.cs rename to Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 7074e2225e..d3197d97b8 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -7,16 +7,16 @@ using Microsoft.Extensions.Logging; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; -namespace Emby.Server.Implementations.Syncplay +namespace Emby.Server.Implementations.SyncPlay { /// - /// Class SyncplayManager. + /// Class SyncPlayManager. /// - public class SyncplayManager : ISyncplayManager, IDisposable + public class SyncPlayManager : ISyncPlayManager, IDisposable { /// /// The logger. @@ -41,14 +41,14 @@ namespace Emby.Server.Implementations.Syncplay /// /// The map between sessions and groups. /// - private readonly Dictionary _sessionToGroupMap = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _sessionToGroupMap = + new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// The groups. /// - private readonly Dictionary _groups = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _groups = + new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Lock used for accesing any group. @@ -57,8 +57,8 @@ namespace Emby.Server.Implementations.Syncplay private bool _disposed = false; - public SyncplayManager( - ILogger logger, + public SyncPlayManager( + ILogger logger, IUserManager userManager, ISessionManager sessionManager, ILibraryManager libraryManager) @@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Syncplay /// Gets all groups. /// /// All groups. - public IEnumerable Groups => _groups.Values; + public IEnumerable Groups => _groups.Values; /// public void Dispose() @@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.Syncplay private Guid? GetSessionGroup(SessionInfo session) { - ISyncplayController group; + ISyncPlayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); if (group != null) { @@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.Syncplay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) + if (user.Policy.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) { _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); @@ -181,7 +181,7 @@ namespace Emby.Server.Implementations.Syncplay { Type = GroupUpdateType.CreateGroupDenied }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } @@ -192,7 +192,7 @@ namespace Emby.Server.Implementations.Syncplay LeaveGroup(session, cancellationToken); } - var group = new SyncplayController(_sessionManager, this); + var group = new SyncPlayController(_sessionManager, this); _groups[group.GetGroupId().ToString()] = group; group.InitGroup(session, cancellationToken); @@ -204,21 +204,21 @@ namespace Emby.Server.Implementations.Syncplay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncplayAccess == SyncplayAccess.None) + if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) { - _logger.LogWarning("JoinGroup: {0} does not have access to Syncplay.", session.Id); + _logger.LogWarning("JoinGroup: {0} does not have access to SyncPlay.", session.Id); var error = new GroupUpdate() { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } lock (_groupsLock) { - ISyncplayController group; + ISyncPlayController group; _groups.TryGetValue(groupId, out group); if (group == null) @@ -229,7 +229,7 @@ namespace Emby.Server.Implementations.Syncplay { Type = GroupUpdateType.GroupDoesNotExist }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } @@ -242,7 +242,7 @@ namespace Emby.Server.Implementations.Syncplay GroupId = group.GetGroupId().ToString(), Type = GroupUpdateType.LibraryAccessDenied }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } @@ -262,7 +262,7 @@ namespace Emby.Server.Implementations.Syncplay // TODO: determine what happens to users that are in a group and get their permissions revoked lock (_groupsLock) { - ISyncplayController group; + ISyncPlayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); if (group == null) @@ -273,7 +273,7 @@ namespace Emby.Server.Implementations.Syncplay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } @@ -292,7 +292,7 @@ namespace Emby.Server.Implementations.Syncplay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncplayAccess == SyncplayAccess.None) + if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) { return new List(); } @@ -322,21 +322,21 @@ namespace Emby.Server.Implementations.Syncplay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncplayAccess == SyncplayAccess.None) + if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) { - _logger.LogWarning("HandleRequest: {0} does not have access to Syncplay.", session.Id); + _logger.LogWarning("HandleRequest: {0} does not have access to SyncPlay.", session.Id); var error = new GroupUpdate() { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } lock (_groupsLock) { - ISyncplayController group; + ISyncPlayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); if (group == null) @@ -347,7 +347,7 @@ namespace Emby.Server.Implementations.Syncplay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } @@ -356,7 +356,7 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void AddSessionToGroup(SessionInfo session, ISyncplayController group) + public void AddSessionToGroup(SessionInfo session, ISyncPlayController group) { if (IsSessionInGroup(session)) { @@ -366,14 +366,14 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group) + public void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group) { if (!IsSessionInGroup(session)) { throw new InvalidOperationException("Session not in any group!"); } - ISyncplayController tempGroup; + ISyncPlayController tempGroup; _sessionToGroupMap.Remove(session.Id, out tempGroup); if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs similarity index 75% rename from MediaBrowser.Api/Syncplay/SyncplayService.cs rename to MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 4b6e16762a..bcdc833e40 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -4,24 +4,24 @@ using System.Collections.Generic; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Model.Services; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Api.Syncplay +namespace MediaBrowser.Api.SyncPlay { - [Route("/Syncplay/{SessionId}/NewGroup", "POST", Summary = "Create a new Syncplay group")] + [Route("/SyncPlay/{SessionId}/NewGroup", "POST", Summary = "Create a new SyncPlay group")] [Authenticated] - public class SyncplayNewGroup : IReturnVoid + public class SyncPlayNewGroup : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } } - [Route("/Syncplay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing Syncplay group")] + [Route("/SyncPlay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing SyncPlay group")] [Authenticated] - public class SyncplayJoinGroup : IReturnVoid + public class SyncPlayJoinGroup : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } @@ -41,17 +41,17 @@ namespace MediaBrowser.Api.Syncplay public string PlayingItemId { get; set; } } - [Route("/Syncplay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined Syncplay group")] + [Route("/SyncPlay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined SyncPlay group")] [Authenticated] - public class SyncplayLeaveGroup : IReturnVoid + public class SyncPlayLeaveGroup : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } } - [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups")] + [Route("/SyncPlay/{SessionId}/ListGroups", "POST", Summary = "List SyncPlay groups")] [Authenticated] - public class SyncplayListGroups : IReturnVoid + public class SyncPlayListGroups : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } @@ -64,25 +64,25 @@ namespace MediaBrowser.Api.Syncplay public string FilterItemId { get; set; } } - [Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")] + [Route("/SyncPlay/{SessionId}/PlayRequest", "POST", Summary = "Request play in SyncPlay group")] [Authenticated] - public class SyncplayPlayRequest : IReturnVoid + public class SyncPlayPlayRequest : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } } - [Route("/Syncplay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in Syncplay group")] + [Route("/SyncPlay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in SyncPlay group")] [Authenticated] - public class SyncplayPauseRequest : IReturnVoid + public class SyncPlayPauseRequest : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } } - [Route("/Syncplay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in Syncplay group")] + [Route("/SyncPlay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in SyncPlay group")] [Authenticated] - public class SyncplaySeekRequest : IReturnVoid + public class SyncPlaySeekRequest : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } @@ -91,9 +91,9 @@ namespace MediaBrowser.Api.Syncplay public long PositionTicks { get; set; } } - [Route("/Syncplay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in Syncplay group while buffering")] + [Route("/SyncPlay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in SyncPlay group while buffering")] [Authenticated] - public class SyncplayBufferingRequest : IReturnVoid + public class SyncPlayBufferingRequest : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } @@ -116,9 +116,9 @@ namespace MediaBrowser.Api.Syncplay public bool BufferingDone { get; set; } } - [Route("/Syncplay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] + [Route("/SyncPlay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] [Authenticated] - public class SyncplayUpdatePing : IReturnVoid + public class SyncPlayUpdatePing : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } @@ -128,9 +128,9 @@ namespace MediaBrowser.Api.Syncplay } /// - /// Class SyncplayService. + /// Class SyncPlayService. /// - public class SyncplayService : BaseApiService + public class SyncPlayService : BaseApiService { /// /// The session context. @@ -138,37 +138,37 @@ namespace MediaBrowser.Api.Syncplay private readonly ISessionContext _sessionContext; /// - /// The Syncplay manager. + /// The SyncPlay manager. /// - private readonly ISyncplayManager _syncplayManager; + private readonly ISyncPlayManager _syncPlayManager; - public SyncplayService( - ILogger logger, + public SyncPlayService( + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, ISessionContext sessionContext, - ISyncplayManager syncplayManager) + ISyncPlayManager syncPlayManager) : base(logger, serverConfigurationManager, httpResultFactory) { _sessionContext = sessionContext; - _syncplayManager = syncplayManager; + _syncPlayManager = syncPlayManager; } /// /// Handles the specified request. /// /// The request. - public void Post(SyncplayNewGroup request) + public void Post(SyncPlayNewGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.NewGroup(currentSession, CancellationToken.None); + _syncPlayManager.NewGroup(currentSession, CancellationToken.None); } /// /// Handles the specified request. /// /// The request. - public void Post(SyncplayJoinGroup request) + public void Post(SyncPlayJoinGroup request) { var currentSession = GetSession(_sessionContext); var joinRequest = new JoinGroupRequest() @@ -195,17 +195,17 @@ namespace MediaBrowser.Api.Syncplay return; } } - _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); + _syncPlayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); } /// /// Handles the specified request. /// /// The request. - public void Post(SyncplayLeaveGroup request) + public void Post(SyncPlayLeaveGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.LeaveGroup(currentSession, CancellationToken.None); + _syncPlayManager.LeaveGroup(currentSession, CancellationToken.None); } /// @@ -213,7 +213,7 @@ namespace MediaBrowser.Api.Syncplay /// /// The request. /// The requested list of groups. - public List Post(SyncplayListGroups request) + public List Post(SyncPlayListGroups request) { var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; @@ -232,81 +232,81 @@ namespace MediaBrowser.Api.Syncplay Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } } - return _syncplayManager.ListGroups(currentSession, filterItemId); + return _syncPlayManager.ListGroups(currentSession, filterItemId); } /// /// Handles the specified request. /// /// The request. - public void Post(SyncplayPlayRequest request) + public void Post(SyncPlayPlayRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() + var syncPlayRequest = new PlaybackRequest() { Type = PlaybackRequestType.Play }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); } /// /// Handles the specified request. /// /// The request. - public void Post(SyncplayPauseRequest request) + public void Post(SyncPlayPauseRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() + var syncPlayRequest = new PlaybackRequest() { Type = PlaybackRequestType.Pause }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); } /// /// Handles the specified request. /// /// The request. - public void Post(SyncplaySeekRequest request) + public void Post(SyncPlaySeekRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() + var syncPlayRequest = new PlaybackRequest() { Type = PlaybackRequestType.Seek, PositionTicks = request.PositionTicks }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); } /// /// Handles the specified request. /// /// The request. - public void Post(SyncplayBufferingRequest request) + public void Post(SyncPlayBufferingRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() + var syncPlayRequest = new PlaybackRequest() { Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, When = DateTime.Parse(request.When), PositionTicks = request.PositionTicks }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); } /// /// Handles the specified request. /// /// The request. - public void Post(SyncplayUpdatePing request) + public void Post(SyncPlayUpdatePing request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() + var syncPlayRequest = new PlaybackRequest() { Type = PlaybackRequestType.UpdatePing, Ping = Convert.ToInt64(request.Ping) }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); } } } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/SyncPlay/TimeSyncService.cs similarity index 96% rename from MediaBrowser.Api/Syncplay/TimeSyncService.cs rename to MediaBrowser.Api/SyncPlay/TimeSyncService.cs index 9a26ffd996..4a9307e62f 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/SyncPlay/TimeSyncService.cs @@ -2,10 +2,10 @@ using System; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Services; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Api.Syncplay +namespace MediaBrowser.Api.SyncPlay { [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] public class GetUtcTime : IReturnVoid diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 39c065b895..4c2f834cb3 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -9,7 +9,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.Session { @@ -142,22 +142,22 @@ namespace MediaBrowser.Controller.Session Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken); /// - /// Sends the SyncplayCommand. + /// Sends the SyncPlayCommand. /// /// The session id. /// The command. /// The cancellation token. /// Task. - Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); + Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); /// - /// Sends the SyncplayGroupUpdate. + /// Sends the SyncPlayGroupUpdate. /// /// The session id. /// The group update. /// The cancellation token. /// Task. - Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); + Task SendSyncPlayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); /// /// Sends the browse command. diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs similarity index 99% rename from MediaBrowser.Controller/Syncplay/GroupInfo.cs rename to MediaBrowser.Controller/SyncPlay/GroupInfo.cs index c01fead836..087748de08 100644 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Session; -namespace MediaBrowser.Controller.Syncplay +namespace MediaBrowser.Controller.SyncPlay { /// /// Class GroupInfo. diff --git a/MediaBrowser.Controller/Syncplay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs similarity index 94% rename from MediaBrowser.Controller/Syncplay/GroupMember.cs rename to MediaBrowser.Controller/SyncPlay/GroupMember.cs index 7630428d76..a3975c334c 100644 --- a/MediaBrowser.Controller/Syncplay/GroupMember.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs @@ -1,6 +1,6 @@ using MediaBrowser.Controller.Session; -namespace MediaBrowser.Controller.Syncplay +namespace MediaBrowser.Controller.SyncPlay { /// /// Class GroupMember. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs similarity index 93% rename from MediaBrowser.Controller/Syncplay/ISyncplayController.cs rename to MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs index 34eae40624..de1fcd2591 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs @@ -1,14 +1,14 @@ using System; using System.Threading; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; -namespace MediaBrowser.Controller.Syncplay +namespace MediaBrowser.Controller.SyncPlay { /// - /// Interface ISyncplayController. + /// Interface ISyncPlayController. /// - public interface ISyncplayController + public interface ISyncPlayController { /// /// Gets the group id. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs similarity index 90% rename from MediaBrowser.Controller/Syncplay/ISyncplayManager.cs rename to MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index fbc208d27d..6c962ec854 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -2,14 +2,14 @@ using System; using System.Collections.Generic; using System.Threading; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; -namespace MediaBrowser.Controller.Syncplay +namespace MediaBrowser.Controller.SyncPlay { /// - /// Interface ISyncplayManager. + /// Interface ISyncPlayManager. /// - public interface ISyncplayManager + public interface ISyncPlayManager { /// /// Creates a new group. @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group. /// - void AddSessionToGroup(SessionInfo session, ISyncplayController group); + void AddSessionToGroup(SessionInfo session, ISyncPlayController group); /// /// Unmaps a session from a group. @@ -64,6 +64,6 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group. /// - void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group); + void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group); } } diff --git a/MediaBrowser.Model/Configuration/SyncplayAccess.cs b/MediaBrowser.Model/Configuration/SyncplayAccess.cs index cddf68c42d..d891a8167a 100644 --- a/MediaBrowser.Model/Configuration/SyncplayAccess.cs +++ b/MediaBrowser.Model/Configuration/SyncplayAccess.cs @@ -1,9 +1,9 @@ namespace MediaBrowser.Model.Configuration { /// - /// Enum SyncplayAccess. + /// Enum SyncPlayAccess. /// - public enum SyncplayAccess + public enum SyncPlayAccess { /// /// User can create groups and join them. @@ -16,7 +16,7 @@ namespace MediaBrowser.Model.Configuration JoinGroups, /// - /// Syncplay is disabled for the user. + /// SyncPlay is disabled for the user. /// None } diff --git a/MediaBrowser.Model/Syncplay/GroupInfoView.cs b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs similarity index 96% rename from MediaBrowser.Model/Syncplay/GroupInfoView.cs rename to MediaBrowser.Model/SyncPlay/GroupInfoView.cs index 50ad70630f..7b833506ba 100644 --- a/MediaBrowser.Model/Syncplay/GroupInfoView.cs +++ b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Model.Syncplay +namespace MediaBrowser.Model.SyncPlay { /// /// Class GroupInfoView. diff --git a/MediaBrowser.Model/Syncplay/GroupUpdate.cs b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs similarity index 94% rename from MediaBrowser.Model/Syncplay/GroupUpdate.cs rename to MediaBrowser.Model/SyncPlay/GroupUpdate.cs index cc49e92a9c..895702f3dd 100644 --- a/MediaBrowser.Model/Syncplay/GroupUpdate.cs +++ b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Model.Syncplay +namespace MediaBrowser.Model.SyncPlay { /// /// Class GroupUpdate. diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs similarity index 97% rename from MediaBrowser.Model/Syncplay/GroupUpdateType.cs rename to MediaBrowser.Model/SyncPlay/GroupUpdateType.cs index 9f40f9577f..89d2457872 100644 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Model.Syncplay +namespace MediaBrowser.Model.SyncPlay { /// /// Enum GroupUpdateType. diff --git a/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs similarity index 93% rename from MediaBrowser.Model/Syncplay/JoinGroupRequest.cs rename to MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs index 8d8a2646ac..d67b6bd555 100644 --- a/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs +++ b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs @@ -1,6 +1,6 @@ using System; -namespace MediaBrowser.Model.Syncplay +namespace MediaBrowser.Model.SyncPlay { /// /// Class JoinGroupRequest. diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequest.cs similarity index 95% rename from MediaBrowser.Model/Syncplay/PlaybackRequest.cs rename to MediaBrowser.Model/SyncPlay/PlaybackRequest.cs index ba97641f63..9de23194e3 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs +++ b/MediaBrowser.Model/SyncPlay/PlaybackRequest.cs @@ -1,6 +1,6 @@ using System; -namespace MediaBrowser.Model.Syncplay +namespace MediaBrowser.Model.SyncPlay { /// /// Class PlaybackRequest. diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs similarity index 95% rename from MediaBrowser.Model/Syncplay/PlaybackRequestType.cs rename to MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs index b3d49d09ef..f1e175fdec 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs +++ b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Model.Syncplay +namespace MediaBrowser.Model.SyncPlay { /// /// Enum PlaybackRequestType diff --git a/MediaBrowser.Model/Syncplay/SendCommand.cs b/MediaBrowser.Model/SyncPlay/SendCommand.cs similarity index 96% rename from MediaBrowser.Model/Syncplay/SendCommand.cs rename to MediaBrowser.Model/SyncPlay/SendCommand.cs index d9f3914030..0f06e381f1 100644 --- a/MediaBrowser.Model/Syncplay/SendCommand.cs +++ b/MediaBrowser.Model/SyncPlay/SendCommand.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Model.Syncplay +namespace MediaBrowser.Model.SyncPlay { /// /// Class SendCommand. diff --git a/MediaBrowser.Model/Syncplay/SendCommandType.cs b/MediaBrowser.Model/SyncPlay/SendCommandType.cs similarity index 93% rename from MediaBrowser.Model/Syncplay/SendCommandType.cs rename to MediaBrowser.Model/SyncPlay/SendCommandType.cs index 02e4774d0d..1137198715 100644 --- a/MediaBrowser.Model/Syncplay/SendCommandType.cs +++ b/MediaBrowser.Model/SyncPlay/SendCommandType.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Model.Syncplay +namespace MediaBrowser.Model.SyncPlay { /// /// Enum SendCommandType. diff --git a/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs b/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs similarity index 94% rename from MediaBrowser.Model/Syncplay/UtcTimeResponse.cs rename to MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs index f7887dc332..0a60361544 100644 --- a/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs +++ b/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Model.Syncplay +namespace MediaBrowser.Model.SyncPlay { /// /// Class UtcTimeResponse. diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index cf576c3582..3e027e8312 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -81,10 +81,10 @@ namespace MediaBrowser.Model.Users public string PasswordResetProviderId { get; set; } /// - /// Gets or sets a value indicating what Syncplay features the user can access. + /// Gets or sets a value indicating what SyncPlay features the user can access. /// - /// Access level to Syncplay features. - public SyncplayAccess SyncplayAccess { get; set; } + /// Access level to SyncPlay features. + public SyncPlayAccess SyncPlayAccess { get; set; } public UserPolicy() { @@ -131,7 +131,7 @@ namespace MediaBrowser.Model.Users EnableContentDownloading = true; EnablePublicSharing = true; EnableRemoteAccess = true; - SyncplayAccess = SyncplayAccess.CreateAndJoinGroups; + SyncPlayAccess = SyncPlayAccess.CreateAndJoinGroups; } } } From 5c8cbd4087261f13d003d7d4eab082cbf335b4d4 Mon Sep 17 00:00:00 2001 From: gion Date: Sat, 9 May 2020 14:34:07 +0200 Subject: [PATCH 22/72] Fix code issues --- .../Session/SessionWebSocketListener.cs | 21 +++++---- .../SyncPlay/SyncPlayController.cs | 4 +- .../SyncPlay/SyncPlayManager.cs | 28 +++++++---- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 47 +++++++++---------- .../Net/IWebSocketConnection.cs | 2 +- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 32 ++++++++++--- .../SyncPlay/ISyncPlayManager.cs | 2 +- MediaBrowser.Model/SyncPlay/GroupInfoView.cs | 4 +- 8 files changed, 86 insertions(+), 54 deletions(-) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index d1ee22ea86..3704445abe 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -22,17 +22,17 @@ namespace Emby.Server.Implementations.Session /// /// The timeout in seconds after which a WebSocket is considered to be lost. /// - public readonly int WebSocketLostTimeout = 60; + public const int WebSocketLostTimeout = 60; /// /// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets. /// - public readonly double IntervalFactor = 0.2; + public const float IntervalFactor = 0.2f; /// /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. /// - public readonly double ForceKeepAliveFactor = 0.75; + public const float ForceKeepAliveFactor = 0.75f; /// /// The _session manager @@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.Session { _keepAliveCancellationToken = new CancellationTokenSource(); // Start KeepAlive watcher - var task = RepeatAsyncCallbackEvery( + _ = RepeatAsyncCallbackEvery( KeepAliveSockets, TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor), _keepAliveCancellationToken.Token); @@ -241,6 +241,7 @@ namespace Emby.Server.Implementations.Session { webSocket.Closed -= OnWebSocketClosed; } + _webSockets.Clear(); } } @@ -250,8 +251,8 @@ namespace Emby.Server.Implementations.Session /// private async Task KeepAliveSockets() { - IEnumerable inactive; - IEnumerable lost; + List inactive; + List lost; lock (_webSocketsLock) { @@ -261,8 +262,8 @@ namespace Emby.Server.Implementations.Session { var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); - }); - lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + }).ToList(); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList(); } if (inactive.Any()) @@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.Session catch (WebSocketException exception) { _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); - lost = lost.Append(webSocket); + lost.Add(webSocket); } } @@ -288,7 +289,7 @@ namespace Emby.Server.Implementations.Session if (lost.Any()) { _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); - foreach (var webSocket in lost.ToList()) + foreach (var webSocket in lost) { // TODO: handle session relative to the lost webSocket RemoveWebSocket(webSocket); diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index 9c9758de1c..c7bd242a7c 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.SyncPlay session => session.Session ).ToArray(); default: - return new SessionInfo[] { }; + return Array.Empty(); } } @@ -541,7 +541,7 @@ namespace Emby.Server.Implementations.SyncPlay PlayingItemName = _group.PlayingItem.Name, PlayingItemId = _group.PlayingItem.Id.ToString(), PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList().AsReadOnly() }; } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index d3197d97b8..93cec1304c 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -47,8 +47,8 @@ namespace Emby.Server.Implementations.SyncPlay /// /// The groups. /// - private readonly Dictionary _groups = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _groups = + new Dictionary(); /// /// Lock used for accesing any group. @@ -113,14 +113,22 @@ namespace Emby.Server.Implementations.SyncPlay private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; - if (!IsSessionInGroup(session)) return; + if (!IsSessionInGroup(session)) + { + return; + } + LeaveGroup(session, CancellationToken.None); } private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { var session = e.Session; - if (!IsSessionInGroup(session)) return; + if (!IsSessionInGroup(session)) + { + return; + } + LeaveGroup(session, CancellationToken.None); } @@ -193,14 +201,14 @@ namespace Emby.Server.Implementations.SyncPlay } var group = new SyncPlayController(_sessionManager, this); - _groups[group.GetGroupId().ToString()] = group; + _groups[group.GetGroupId()] = group; group.InitGroup(session, cancellationToken); } } /// - public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) + public void JoinGroup(SessionInfo session, Guid groupId, JoinGroupRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); @@ -248,7 +256,11 @@ namespace Emby.Server.Implementations.SyncPlay if (IsSessionInGroup(session)) { - if (GetSessionGroup(session).Equals(groupId)) return; + if (GetSessionGroup(session).Equals(groupId)) + { + return; + } + LeaveGroup(session, cancellationToken); } @@ -282,7 +294,7 @@ namespace Emby.Server.Implementations.SyncPlay if (group.IsGroupEmpty()) { _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); - _groups.Remove(group.GetGroupId().ToString(), out _); + _groups.Remove(group.GetGroupId(), out _); } } } diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index bcdc833e40..9137faf9f4 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -171,31 +171,35 @@ namespace MediaBrowser.Api.SyncPlay public void Post(SyncPlayJoinGroup request) { var currentSession = GetSession(_sessionContext); - var joinRequest = new JoinGroupRequest() + + Guid groupId; + Guid playingItemId = Guid.Empty; + + var valid = Guid.TryParse(request.GroupId, out groupId); + if (!valid) { - GroupId = Guid.Parse(request.GroupId) - }; + Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId); + return; + } // Both null and empty strings mean that client isn't playing anything if (!String.IsNullOrEmpty(request.PlayingItemId)) { - try - { - joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); - } - catch (ArgumentNullException) - { - // Should never happen, but just in case - Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); - return; - } - catch (FormatException) + valid = Guid.TryParse(request.PlayingItemId, out playingItemId); + if (!valid) { Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); return; } } - _syncPlayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); + + var joinRequest = new JoinGroupRequest() + { + GroupId = groupId, + PlayingItemId = playingItemId + }; + + _syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None); } /// @@ -217,21 +221,16 @@ namespace MediaBrowser.Api.SyncPlay { var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; + if (!String.IsNullOrEmpty(request.FilterItemId)) { - try - { - filterItemId = Guid.Parse(request.FilterItemId); - } - catch (ArgumentNullException) - { - Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); - } - catch (FormatException) + var valid = Guid.TryParse(request.FilterItemId, out filterItemId); + if (!valid) { Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } } + return _syncPlayManager.ListGroups(currentSession, filterItemId); } diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index fb766ab57f..b371a59e97 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the date of last Keeplive received. /// /// The date of last Keeplive received. - public DateTime LastKeepAliveDate { get; set; } + DateTime LastKeepAliveDate { get; set; } /// /// Gets or sets the URL. diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs index 087748de08..bda49bd1b0 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -69,7 +69,11 @@ namespace MediaBrowser.Controller.SyncPlay /// The session. public void AddSession(SessionInfo session) { - if (ContainsSession(session.Id.ToString())) return; + if (ContainsSession(session.Id.ToString())) + { + return; + } + var member = new GroupMember(); member.Session = session; member.Ping = DefaulPing; @@ -84,9 +88,12 @@ namespace MediaBrowser.Controller.SyncPlay public void RemoveSession(SessionInfo session) { - if (!ContainsSession(session.Id.ToString())) return; - GroupMember member; - Participants.Remove(session.Id.ToString(), out member); + if (!ContainsSession(session.Id.ToString())) + { + return; + } + + Participants.Remove(session.Id.ToString(), out _); } /// @@ -96,7 +103,11 @@ namespace MediaBrowser.Controller.SyncPlay /// The ping. public void UpdatePing(SessionInfo session, long ping) { - if (!ContainsSession(session.Id.ToString())) return; + if (!ContainsSession(session.Id.ToString())) + { + return; + } + Participants[session.Id.ToString()].Ping = ping; } @@ -121,7 +132,11 @@ namespace MediaBrowser.Controller.SyncPlay /// The state. public void SetBuffering(SessionInfo session, bool isBuffering) { - if (!ContainsSession(session.Id.ToString())) return; + if (!ContainsSession(session.Id.ToString())) + { + return; + } + Participants[session.Id.ToString()].IsBuffering = isBuffering; } @@ -133,7 +148,10 @@ namespace MediaBrowser.Controller.SyncPlay { foreach (var session in Participants.Values) { - if (session.IsBuffering) return true; + if (session.IsBuffering) + { + return true; + } } return false; } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index 6c962ec854..006fb687b8 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.SyncPlay /// The group id. /// The request. /// The cancellation token. - void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); + void JoinGroup(SessionInfo session, Guid groupId, JoinGroupRequest request, CancellationToken cancellationToken); /// /// Removes the session from a group. diff --git a/MediaBrowser.Model/SyncPlay/GroupInfoView.cs b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs index 7b833506ba..f28ecf16df 100644 --- a/MediaBrowser.Model/SyncPlay/GroupInfoView.cs +++ b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace MediaBrowser.Model.SyncPlay { /// @@ -33,6 +35,6 @@ namespace MediaBrowser.Model.SyncPlay /// Gets or sets the participants. /// /// The participants. - public string[] Participants { get; set; } + public IReadOnlyList Participants { get; set; } } } From e8f45248aab753797ee9b2e3d8d58c2a243fc598 Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 15 May 2020 18:47:16 +0200 Subject: [PATCH 23/72] Fix code issues --- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 9137faf9f4..8f552ef89d 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -175,8 +175,7 @@ namespace MediaBrowser.Api.SyncPlay Guid groupId; Guid playingItemId = Guid.Empty; - var valid = Guid.TryParse(request.GroupId, out groupId); - if (!valid) + if (!Guid.TryParse(request.GroupId, out groupId)) { Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId); return; @@ -185,8 +184,7 @@ namespace MediaBrowser.Api.SyncPlay // Both null and empty strings mean that client isn't playing anything if (!String.IsNullOrEmpty(request.PlayingItemId)) { - valid = Guid.TryParse(request.PlayingItemId, out playingItemId); - if (!valid) + if (!Guid.TryParse(request.PlayingItemId, out playingItemId)) { Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); return; @@ -224,8 +222,7 @@ namespace MediaBrowser.Api.SyncPlay if (!String.IsNullOrEmpty(request.FilterItemId)) { - var valid = Guid.TryParse(request.FilterItemId, out filterItemId); - if (!valid) + if (!Guid.TryParse(request.FilterItemId, out filterItemId)) { Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } From 8c04049a595df054f491712ed317274566f2d71b Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 15 May 2020 20:06:41 +0200 Subject: [PATCH 24/72] Fix some code smells --- .../Session/SessionWebSocketListener.cs | 4 ++-- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 16 +++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a5293b41c2..3af18f6813 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.Session if (inactive.Any()) { - _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count); } foreach (var webSocket in inactive) @@ -289,7 +289,7 @@ namespace Emby.Server.Implementations.Session { if (lost.Any()) { - _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + _logger.LogInformation("Lost {0} WebSockets.", lost.Count); foreach (var webSocket in lost) { // TODO: handle session relative to the lost webSocket diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 8f552ef89d..8064ea7dc1 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -182,13 +182,10 @@ namespace MediaBrowser.Api.SyncPlay } // Both null and empty strings mean that client isn't playing anything - if (!String.IsNullOrEmpty(request.PlayingItemId)) + if (!String.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) { - if (!Guid.TryParse(request.PlayingItemId, out playingItemId)) - { - Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); - return; - } + Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); + return; } var joinRequest = new JoinGroupRequest() @@ -220,12 +217,9 @@ namespace MediaBrowser.Api.SyncPlay var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; - if (!String.IsNullOrEmpty(request.FilterItemId)) + if (!String.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) { - if (!Guid.TryParse(request.FilterItemId, out filterItemId)) - { - Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); - } + Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } return _syncPlayManager.ListGroups(currentSession, filterItemId); From b9fc0d26287e46017515e4ac3e569ca2c60f622f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Higueras?= Date: Mon, 23 Mar 2020 20:05:49 +0100 Subject: [PATCH 25/72] Add BlurHash support to backend --- Emby.Drawing/ImageProcessor.cs | 4 ++ Emby.Drawing/NullImageEncoder.cs | 6 +++ .../Data/SqliteItemRepository.cs | 17 ++++++- Emby.Server.Implementations/Dto/DtoService.cs | 7 +++ .../Library/LibraryManager.cs | 30 +++++++++++- .../Jellyfin.Drawing.Skia.csproj | 2 + Jellyfin.Drawing.Skia/SkiaEncoder.cs | 48 ++++++++++++++++++- MediaBrowser.Api/Images/ImageService.cs | 7 ++- .../Drawing/IImageEncoder.cs | 7 +++ .../Drawing/IImageProcessor.cs | 7 +++ MediaBrowser.Controller/Entities/BaseItem.cs | 1 + .../Entities/ItemImageInfo.cs | 6 +++ MediaBrowser.Model/Dto/BaseItemDto.cs | 6 +++ MediaBrowser.Model/Dto/ImageInfo.cs | 6 +++ 14 files changed, 149 insertions(+), 5 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 0b3bbe29ef..1237b603b6 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -313,6 +313,10 @@ namespace Emby.Drawing public ImageDimensions GetImageDimensions(string path) => _imageEncoder.GetImageSize(path); + /// + public string GetImageHash(string path) + => _imageEncoder.GetImageHash(path); + /// public string GetImageCacheTag(BaseItem item, ItemImageInfo image) => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index 5af7f16225..fa89b4c638 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -42,5 +42,11 @@ namespace Emby.Drawing { throw new NotImplementedException(); } + + /// + public string GetImageHash(string inputPath) + { + throw new NotImplementedException(); + } } } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index ca5cd6fdd5..5a43a138bd 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1144,12 +1144,18 @@ namespace Emby.Server.Implementations.Data var delimeter = "*"; var path = image.Path; + var hash = image.Hash; if (path == null) { path = string.Empty; } + if (hash == null) + { + hash = string.Empty; + } + return GetPathToSave(path) + delimeter + image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + @@ -1158,7 +1164,11 @@ namespace Emby.Server.Implementations.Data delimeter + image.Width.ToString(CultureInfo.InvariantCulture) + delimeter + - image.Height.ToString(CultureInfo.InvariantCulture); + image.Height.ToString(CultureInfo.InvariantCulture) + + delimeter + + // Replace delimiters with other characters. + // This can be removed when we migrate to a proper DB. + hash.Replace('*', '/').Replace('|', '\\'); } public ItemImageInfo ItemImageInfoFromValueString(string value) @@ -1192,6 +1202,11 @@ namespace Emby.Server.Implementations.Data image.Width = width; image.Height = height; } + + if (parts.Length >= 6) + { + image.Hash = parts[5].Replace('/', '*').Replace('\\', '|'); + } } return image; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index c4b65d2654..a34a3a192f 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -718,6 +718,7 @@ namespace Emby.Server.Implementations.Dto if (options.EnableImages) { dto.ImageTags = new Dictionary(); + dto.ImageHashes = new Dictionary(); // Prevent implicitly captured closure var currentItem = item; @@ -732,6 +733,12 @@ namespace Emby.Server.Implementations.Dto { dto.ImageTags[image.Type] = tag; } + + var hash = image.Hash; + if (hash != null && hash.Length > 0) + { + dto.ImageHashes[tag] = image.Hash; + } } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0b86b2db7e..bc35b04106 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -21,6 +21,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -35,6 +36,7 @@ using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -109,6 +111,18 @@ namespace Emby.Server.Implementations.Library /// The comparers. private IBaseItemComparer[] Comparers { get; set; } + /// + /// Gets or sets the active item repository + /// + /// The item repository. + public IItemRepository ItemRepository { get; set; } + + /// + /// Gets or sets the active image processor + /// + /// The image processor. + public IImageProcessor ImageProcessor { get; set; } + /// /// Occurs when [item added]. /// @@ -1817,7 +1831,19 @@ namespace Emby.Server.Implementations.Library public void UpdateImages(BaseItem item) { - _itemRepository.SaveImages(item); + item.ImageInfos + .Where(i => (i.Width == 0 || i.Height == 0)) + .ToList() + .ForEach(x => + { + string blurhash = ImageProcessor.GetImageHash(x.Path); + ImageDimensions size = ImageProcessor.GetImageDimensions(item, x, true); + x.Width = size.Width; + x.Height = size.Height; + x.Hash = blurhash; + }); + + ItemRepository.SaveImages(item); RegisterItem(item); } @@ -1839,7 +1865,7 @@ namespace Emby.Server.Implementations.Library item.DateLastSaved = DateTime.UtcNow; - RegisterItem(item); + UpdateImages(item); } _itemRepository.SaveItems(itemsList, cancellationToken); diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index a6e1f490ad..9f0e3a0042 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -21,6 +21,8 @@ + + diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 5c7462ee29..1d73078638 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Blurhash.Core; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Extensions; @@ -15,7 +19,7 @@ namespace Jellyfin.Drawing.Skia /// /// Image encoder that uses to manipulate images. /// - public class SkiaEncoder : IImageEncoder + public class SkiaEncoder : CoreEncoder, IImageEncoder { private static readonly HashSet _transparentImageTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; @@ -229,6 +233,48 @@ namespace Jellyfin.Drawing.Skia } } + /// + /// The path is null. + /// The path is not valid. + /// The file at the specified path could not be used to generate a codec. + [SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional")] + public string GetImageHash(string path) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + if (!File.Exists(path)) + { + throw new FileNotFoundException("File not found", path); + } + + using (var bitmap = GetBitmap(path, false, false, null)) + { + if (bitmap == null) + { + throw new ArgumentOutOfRangeException($"Skia unable to read image {path}"); + } + + var width = bitmap.Width; + var height = bitmap.Height; + var pixels = new Pixel[width, height]; + Parallel.ForEach(Enumerable.Range(0, height), y => + { + for (var x = 0; x < width; x++) + { + var color = bitmap.GetPixel(x, y); + pixels[x, y].Red = MathUtils.SRgbToLinear(color.Red); + pixels[x, y].Green = MathUtils.SRgbToLinear(color.Green); + pixels[x, y].Blue = MathUtils.SRgbToLinear(color.Blue); + } + }); + + return CoreEncode(pixels, 4, 4); + } + } + private static bool HasDiacritics(string text) => !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal); diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 2e9b3e6cb4..ecfe2ed763 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -323,6 +323,7 @@ namespace MediaBrowser.Api.Images { int? width = null; int? height = null; + string? blurhash = null; long length = 0; try @@ -332,7 +333,10 @@ namespace MediaBrowser.Api.Images var fileInfo = _fileSystem.GetFileInfo(info.Path); length = fileInfo.Length; - ImageDimensions size = _imageProcessor.GetImageDimensions(item, info); + blurhash = _imageProcessor.GetImageHash(info.Path); + info.Hash = blurhash; // TODO: this doesn't seem like the right thing to do + + ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true); _libraryManager.UpdateImages(item); width = size.Width; height = size.Height; @@ -358,6 +362,7 @@ namespace MediaBrowser.Api.Images ImageType = info.Type, ImageTag = _imageProcessor.GetImageCacheTag(item, info), Size = length, + Hash = blurhash, Width = width, Height = height }; diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 88e67b6486..1d3f0d3b44 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -43,6 +43,13 @@ namespace MediaBrowser.Controller.Drawing /// The image dimensions. ImageDimensions GetImageSize(string path); + /// + /// Get the blurhash of an image. + /// + /// The filepath of the image. + /// The blurhash. + string GetImageHash(string path); + /// /// Encode an image. /// diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 36c746624e..be5906cbc9 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -32,6 +32,13 @@ namespace MediaBrowser.Controller.Drawing /// ImageDimensions ImageDimensions GetImageDimensions(string path); + /// + /// Gets the blurhash of the image. + /// + /// Path to the image file. + /// BlurHash + String GetImageHash(string path); + /// /// Gets the dimensions of the image. /// diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 7ed8fa7671..e5f6ea09d6 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2222,6 +2222,7 @@ namespace MediaBrowser.Controller.Entities existingImage.DateModified = image.DateModified; existingImage.Width = image.Width; existingImage.Height = image.Height; + existingImage.Hash = image.Hash; } else { diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index fc46dec2ef..ba02971073 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -28,6 +28,12 @@ namespace MediaBrowser.Controller.Entities public int Height { get; set; } + /// + /// Gets or sets the blurhash. + /// + /// The blurhash. + public string Hash { get; set; } + [JsonIgnore] public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase); } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 607355d8d3..8c6c9683a4 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -510,6 +510,12 @@ namespace MediaBrowser.Model.Dto /// The series thumb image tag. public string SeriesThumbImageTag { get; set; } + /// + /// Gets or sets the blurhash for the image tags. + /// + /// The blurhashes. + public Dictionary ImageHashes { get; set; } + /// /// Gets or sets the series studio. /// diff --git a/MediaBrowser.Model/Dto/ImageInfo.cs b/MediaBrowser.Model/Dto/ImageInfo.cs index 57942ac23b..39bdc09ed8 100644 --- a/MediaBrowser.Model/Dto/ImageInfo.cs +++ b/MediaBrowser.Model/Dto/ImageInfo.cs @@ -30,6 +30,12 @@ namespace MediaBrowser.Model.Dto /// The path. public string Path { get; set; } + /// + /// Gets or sets the blurhash. + /// + /// The blurhash. + public string Hash { get; set; } + /// /// Gets or sets the height. /// From fe480caf5486a21d7ef152009e5c3e08364e0f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Higueras?= Date: Wed, 25 Mar 2020 17:26:53 +0100 Subject: [PATCH 26/72] Add endpoint to update all items in library --- .../Library/LibraryManager.cs | 28 +++++++++++++++++++ MediaBrowser.Api/ItemUpdateService.cs | 6 ++++ .../Library/ILibraryManager.cs | 1 + 3 files changed, 35 insertions(+) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index bc35b04106..0a97e007cd 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1911,6 +1911,34 @@ namespace Emby.Server.Implementations.Library UpdateItems(new[] { item }, parent, updateReason, cancellationToken); } + /// + /// Updates everything in the database. + /// + public void UpdateAll() + { + Task.Run(() => + { + var items = ItemRepository.GetItemList(new InternalItemsQuery { + Recursive = true + }); + foreach (var item in items) + { + _logger.LogDebug("Updating item {Name} ({ItemId})", + item.Name, + item.Id); + try + { + item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); + } + catch (Exception ex) + { + _logger.LogError(ex, "Updating item {ItemId} failed", item.Id); + } + } + _logger.LogDebug("All items have been updated"); + }); + } + /// /// Reports the item removed. /// diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 2db6d717aa..808627b41a 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -198,6 +198,12 @@ namespace MediaBrowser.Api public void Post(UpdateItem request) { + if (request.ItemId == "*") + { + // Special case: Refresh everything in database. Probably not a great idea to run often. + _libraryManager.UpdateAll(); + return; + } var item = _libraryManager.GetItemById(request.ItemId); var newLockData = request.LockData ?? false; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 2e1c97f674..559a5415fb 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -196,6 +196,7 @@ namespace MediaBrowser.Controller.Library /// void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); + void UpdateAll(); /// /// Retrieves the item. From 02da312f8aaf9975f31291fd65687f637e38530c Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 00:22:52 +0300 Subject: [PATCH 27/72] Fix compilation after rebase --- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- MediaBrowser.Api/Images/ImageService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0a97e007cd..c31fd5bf98 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1837,7 +1837,7 @@ namespace Emby.Server.Implementations.Library .ForEach(x => { string blurhash = ImageProcessor.GetImageHash(x.Path); - ImageDimensions size = ImageProcessor.GetImageDimensions(item, x, true); + ImageDimensions size = ImageProcessor.GetImageDimensions(item, x); x.Width = size.Width; x.Height = size.Height; x.Hash = blurhash; diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index ecfe2ed763..09b99781bf 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -336,7 +336,7 @@ namespace MediaBrowser.Api.Images blurhash = _imageProcessor.GetImageHash(info.Path); info.Hash = blurhash; // TODO: this doesn't seem like the right thing to do - ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true); + ImageDimensions size = _imageProcessor.GetImageDimensions(item, info); _libraryManager.UpdateImages(item); width = size.Width; height = size.Height; From bfb644d5f50fff9fd8ba5fe4504c73ec5be851af Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 13:45:39 +0300 Subject: [PATCH 28/72] Fix nullref exception --- .vscode/tasks.json | 13 ++++++++++++- .../Library/LibraryManager.cs | 10 ++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ac517e10c6..7475617c93 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,6 +10,17 @@ "${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj" ], "problemMatcher": "$msCompile" + }, + { + "label": "api tests", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "${workspaceFolder}/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj" + ], + "problemMatcher": "$msCompile" } + ] -} \ No newline at end of file +} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index c31fd5bf98..e1cb282ccb 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -111,12 +111,6 @@ namespace Emby.Server.Implementations.Library /// The comparers. private IBaseItemComparer[] Comparers { get; set; } - /// - /// Gets or sets the active item repository - /// - /// The item repository. - public IItemRepository ItemRepository { get; set; } - /// /// Gets or sets the active image processor /// @@ -1843,7 +1837,7 @@ namespace Emby.Server.Implementations.Library x.Hash = blurhash; }); - ItemRepository.SaveImages(item); + _itemRepository.SaveImages(item); RegisterItem(item); } @@ -1918,7 +1912,7 @@ namespace Emby.Server.Implementations.Library { Task.Run(() => { - var items = ItemRepository.GetItemList(new InternalItemsQuery { + var items = _itemRepository.GetItemList(new InternalItemsQuery { Recursive = true }); foreach (var item in items) From f18293bf762c86581153ab8d9b1b6267421178a9 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 13:46:00 +0300 Subject: [PATCH 29/72] Switch to BlurHashSharp lib which should be faster --- .../Jellyfin.Drawing.Skia.csproj | 4 +-- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 28 ++----------------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 9f0e3a0042..221346b688 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -18,11 +18,11 @@ + + - - diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 1d73078638..d2da0cf17e 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -5,7 +5,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; -using Blurhash.Core; +using BlurHashSharp.SkiaSharp; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Extensions; @@ -19,7 +19,7 @@ namespace Jellyfin.Drawing.Skia /// /// Image encoder that uses to manipulate images. /// - public class SkiaEncoder : CoreEncoder, IImageEncoder + public class SkiaEncoder : IImageEncoder { private static readonly HashSet _transparentImageTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; @@ -250,29 +250,7 @@ namespace Jellyfin.Drawing.Skia throw new FileNotFoundException("File not found", path); } - using (var bitmap = GetBitmap(path, false, false, null)) - { - if (bitmap == null) - { - throw new ArgumentOutOfRangeException($"Skia unable to read image {path}"); - } - - var width = bitmap.Width; - var height = bitmap.Height; - var pixels = new Pixel[width, height]; - Parallel.ForEach(Enumerable.Range(0, height), y => - { - for (var x = 0; x < width; x++) - { - var color = bitmap.GetPixel(x, y); - pixels[x, y].Red = MathUtils.SRgbToLinear(color.Red); - pixels[x, y].Green = MathUtils.SRgbToLinear(color.Green); - pixels[x, y].Blue = MathUtils.SRgbToLinear(color.Blue); - } - }); - - return CoreEncode(pixels, 4, 4); - } + return BlurHashSharp.SkiaSharp.BlurHashEncoder.Encode(4, 4, path); } private static bool HasDiacritics(string text) From a226a4ee03d974615a6fa26b936a93458a255b70 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 14:50:14 +0300 Subject: [PATCH 30/72] Compute hash only when one is not computed in DB, small optimizations here and there --- .../Data/SqliteItemRepository.cs | 16 +---- .../Library/LibraryManager.cs | 59 +++++++------------ MediaBrowser.Api/Images/ImageService.cs | 22 ++++--- MediaBrowser.Api/ItemUpdateService.cs | 6 -- .../Library/ILibraryManager.cs | 2 +- 5 files changed, 37 insertions(+), 68 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 5a43a138bd..10eb96b10c 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1141,20 +1141,10 @@ namespace Emby.Server.Implementations.Data public string ToValueString(ItemImageInfo image) { - var delimeter = "*"; + const string delimeter = "*"; - var path = image.Path; - var hash = image.Hash; - - if (path == null) - { - path = string.Empty; - } - - if (hash == null) - { - hash = string.Empty; - } + var path = image.Path ?? string.Empty; + var hash = image.Hash ?? string.Empty; return GetPathToSave(path) + delimeter + diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index e1cb282ccb..c48664a316 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1825,17 +1825,26 @@ namespace Emby.Server.Implementations.Library public void UpdateImages(BaseItem item) { - item.ImageInfos - .Where(i => (i.Width == 0 || i.Height == 0)) - .ToList() - .ForEach(x => - { - string blurhash = ImageProcessor.GetImageHash(x.Path); - ImageDimensions size = ImageProcessor.GetImageDimensions(item, x); - x.Width = size.Width; - x.Height = size.Height; - x.Hash = blurhash; - }); + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + var outdated = item.ImageInfos + .Where(i => (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.Hash))) + .ToList(); + if (outdated.Count == 0) + { + return; + } + + outdated.ForEach(img => + { + ImageDimensions size = ImageProcessor.GetImageDimensions(item, img); + img.Width = size.Width; + img.Height = size.Height; + img.Hash = ImageProcessor.GetImageHash(img.Path); + }); _itemRepository.SaveImages(item); @@ -1905,34 +1914,6 @@ namespace Emby.Server.Implementations.Library UpdateItems(new[] { item }, parent, updateReason, cancellationToken); } - /// - /// Updates everything in the database. - /// - public void UpdateAll() - { - Task.Run(() => - { - var items = _itemRepository.GetItemList(new InternalItemsQuery { - Recursive = true - }); - foreach (var item in items) - { - _logger.LogDebug("Updating item {Name} ({ItemId})", - item.Name, - item.Id); - try - { - item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - } - catch (Exception ex) - { - _logger.LogError(ex, "Updating item {ItemId} failed", item.Id); - } - } - _logger.LogDebug("All items have been updated"); - }); - } - /// /// Reports the item removed. /// diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 09b99781bf..559db550b8 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; @@ -280,9 +281,16 @@ namespace MediaBrowser.Api.Images public List GetItemImageInfos(BaseItem item) { var list = new List(); - var itemImages = item.ImageInfos; + if (itemImages.Length == 0) + { + // short-circuit + return list; + } + + _libraryManager.UpdateImages(item); // this makes sure dimensions and hashes are correct + foreach (var image in itemImages) { if (!item.AllowsMultipleImages(image.Type)) @@ -323,7 +331,7 @@ namespace MediaBrowser.Api.Images { int? width = null; int? height = null; - string? blurhash = null; + string blurhash = null; long length = 0; try @@ -333,13 +341,9 @@ namespace MediaBrowser.Api.Images var fileInfo = _fileSystem.GetFileInfo(info.Path); length = fileInfo.Length; - blurhash = _imageProcessor.GetImageHash(info.Path); - info.Hash = blurhash; // TODO: this doesn't seem like the right thing to do - - ImageDimensions size = _imageProcessor.GetImageDimensions(item, info); - _libraryManager.UpdateImages(item); - width = size.Width; - height = size.Height; + blurhash = info.Hash; + width = info.Width; + height = info.Height; if (width <= 0 || height <= 0) { diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 808627b41a..2db6d717aa 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -198,12 +198,6 @@ namespace MediaBrowser.Api public void Post(UpdateItem request) { - if (request.ItemId == "*") - { - // Special case: Refresh everything in database. Probably not a great idea to run often. - _libraryManager.UpdateAll(); - return; - } var item = _libraryManager.GetItemById(request.ItemId); var newLockData = request.LockData ?? false; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 559a5415fb..81160efece 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -195,8 +195,8 @@ namespace MediaBrowser.Controller.Library /// Updates the item. /// void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); + void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); - void UpdateAll(); /// /// Retrieves the item. From 186b7f303cd6f95ca64e020c2838dfe2028ea54c Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 14:56:52 +0300 Subject: [PATCH 31/72] More small optimizations --- Emby.Server.Implementations/Dto/DtoService.cs | 5 ++--- Emby.Server.Implementations/Library/LibraryManager.cs | 3 ++- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 6 +----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index a34a3a192f..07105786b9 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -722,8 +722,7 @@ namespace Emby.Server.Implementations.Dto // Prevent implicitly captured closure var currentItem = item; - foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)) - .ToList()) + foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type))) { if (options.GetImageLimit(image.Type) > 0) { @@ -735,7 +734,7 @@ namespace Emby.Server.Implementations.Dto } var hash = image.Hash; - if (hash != null && hash.Length > 0) + if (!string.IsNullOrEmpty(hash)) { dto.ImageHashes[tag] = image.Hash; } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index c48664a316..9f412b725e 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1831,10 +1831,11 @@ namespace Emby.Server.Implementations.Library } var outdated = item.ImageInfos - .Where(i => (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.Hash))) + .Where(i => (i.IsLocalFile && (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.Hash)))) .ToList(); if (outdated.Count == 0) { + RegisterItem(item); return; } diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index d2da0cf17e..99091ea57e 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Linq; -using System.Threading.Tasks; using BlurHashSharp.SkiaSharp; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; @@ -237,7 +234,6 @@ namespace Jellyfin.Drawing.Skia /// The path is null. /// The path is not valid. /// The file at the specified path could not be used to generate a codec. - [SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional")] public string GetImageHash(string path) { if (path == null) @@ -250,7 +246,7 @@ namespace Jellyfin.Drawing.Skia throw new FileNotFoundException("File not found", path); } - return BlurHashSharp.SkiaSharp.BlurHashEncoder.Encode(4, 4, path); + return BlurHashEncoder.Encode(4, 4, path); } private static bool HasDiacritics(string text) From 2b1ae7ac5834054866aa485b18f85f1793f7b8b4 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 15:42:50 +0300 Subject: [PATCH 32/72] Fix code smells --- Emby.Drawing/NullImageEncoder.cs | 2 +- .../Drawing/IImageProcessor.cs | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index fa89b4c638..54de7212a3 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -44,7 +44,7 @@ namespace Emby.Drawing } /// - public string GetImageHash(string inputPath) + public string GetImageHash(string path) { throw new NotImplementedException(); } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index be5906cbc9..e38eaf7606 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -32,13 +32,6 @@ namespace MediaBrowser.Controller.Drawing /// ImageDimensions ImageDimensions GetImageDimensions(string path); - /// - /// Gets the blurhash of the image. - /// - /// Path to the image file. - /// BlurHash - String GetImageHash(string path); - /// /// Gets the dimensions of the image. /// @@ -47,6 +40,13 @@ namespace MediaBrowser.Controller.Drawing /// ImageDimensions ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info); + /// + /// Gets the blurhash of the image. + /// + /// Path to the image file. + /// BlurHash + String GetImageHash(string path); + /// /// Gets the image cache tag. /// @@ -54,6 +54,7 @@ namespace MediaBrowser.Controller.Drawing /// The image. /// Guid. string GetImageCacheTag(BaseItem item, ItemImageInfo image); + string GetImageCacheTag(BaseItem item, ChapterInfo info); /// From d7b2c2a17626e75ebb202fc9fa1186a88f670eec Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Wed, 20 May 2020 09:05:51 +0100 Subject: [PATCH 33/72] Renaming variable and refactoring IF statement --- Emby.Server.Implementations/IStartupOptions.cs | 4 +++- Emby.Server.Implementations/Udp/UdpServer.cs | 16 ++++------------ Jellyfin.Server/StartupOptions.cs | 9 +++++---- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index a3a047057d..5d22bfddc9 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,3 +1,5 @@ +using System; + namespace Emby.Server.Implementations { public interface IStartupOptions @@ -40,6 +42,6 @@ namespace Emby.Server.Implementations /// /// Gets the value of the --auto-discover-publish-url command line option. /// - string AutoDiscoverPublishUrl { get; } + Uri PublishedServerUrl { get; } } } diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 57228d208d..1ae3888dc4 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Udp /// /// Address Override Configuration Key /// - public const string AddressOverrideConfigKey = "AutoDiscoverAddressOverride"; + public const string AddressOverrideConfigKey = "PublishedServerUrl"; private Socket _udpSocket; private IPEndPoint _endpoint; @@ -47,17 +47,9 @@ namespace Emby.Server.Implementations.Udp private async Task RespondToV2Message(string messageText, EndPoint endpoint, CancellationToken cancellationToken) { - string localUrl; - - if (!string.IsNullOrEmpty(_config[AddressOverrideConfigKey])) - { - localUrl = _config[AddressOverrideConfigKey]; - } - else - { - localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - } - + string localUrl = !string.IsNullOrEmpty(_config[AddressOverrideConfigKey]) + ? _config[AddressOverrideConfigKey] + : await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(localUrl)) { diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 135ba9d7f8..cc250b06e2 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using CommandLine; using Emby.Server.Implementations; @@ -83,8 +84,8 @@ namespace Jellyfin.Server public string? PluginManifestUrl { get; set; } /// - [Option("auto-discover-publish-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] - public string? AutoDiscoverPublishUrl { get; set; } + [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] + public Uri? PublishedServerUrl { get; set; } /// /// Gets the command line options as a dictionary that can be used in the .NET configuration system. @@ -104,9 +105,9 @@ namespace Jellyfin.Server config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); } - if (AutoDiscoverPublishUrl != null) + if (PublishedServerUrl != null) { - config.Add(UdpServer.AddressOverrideConfigKey, AutoDiscoverPublishUrl); + config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl.ToString()); } return config; From a646a91e158de8f26cc24e6b87d4a665942c28d9 Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Wed, 20 May 2020 14:29:18 +0100 Subject: [PATCH 34/72] Update Emby.Server.Implementations/IStartupOptions.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/IStartupOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 5d22bfddc9..acae702f30 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations string PluginManifestUrl { get; } /// - /// Gets the value of the --auto-discover-publish-url command line option. + /// Gets the value of the --published-server-url command line option. /// Uri PublishedServerUrl { get; } } From 3fb4c1356c22c8be03855a161140c21eb395086b Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 20 May 2020 23:50:17 +0300 Subject: [PATCH 35/72] Make blurhash be computed during regular scans if it was not already computed --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 2 ++ MediaBrowser.Controller/Entities/BaseItem.cs | 1 + MediaBrowser.Controller/Entities/Folder.cs | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 99091ea57e..7ab0a54dfd 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -246,6 +246,8 @@ namespace Jellyfin.Drawing.Skia throw new FileNotFoundException("File not found", path); } + // Use 4 vertical and 4 horizontal components of DCT of the image. + // See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components return BlurHashEncoder.Encode(4, 4, path); } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index e5f6ea09d6..035ab1dd94 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1374,6 +1374,7 @@ namespace MediaBrowser.Controller.Entities new List(); var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); + LibraryManager.UpdateImages(this); // ensure all image properties in DB are fresh if (ownedItemsChanged) { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index a468e0c35a..29040f92e6 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -341,6 +341,11 @@ namespace MediaBrowser.Controller.Entities { currentChild.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken); } + else + { + // metadata is up-to-date; make sure DB has correct images dimensions and hash + LibraryManager.UpdateImages(currentChild); + } continue; } From 8b517e9beffd5cf7b1e7ed2b82b0bdf63fe60f03 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 21 May 2020 00:03:22 +0300 Subject: [PATCH 36/72] Fix nullref for imageProcessor in LibraryManager --- .../Library/LibraryManager.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 9f412b725e..75350ac2a8 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -69,6 +69,8 @@ namespace Emby.Server.Implementations.Library private readonly IFileSystem _fileSystem; private readonly IItemRepository _itemRepository; private readonly ConcurrentDictionary _libraryItemsCache; + private readonly IImageProcessor _imageProcessor; + private NamingOptions _namingOptions; private string[] _videoFileExtensions; @@ -111,12 +113,6 @@ namespace Emby.Server.Implementations.Library /// The comparers. private IBaseItemComparer[] Comparers { get; set; } - /// - /// Gets or sets the active image processor - /// - /// The image processor. - public IImageProcessor ImageProcessor { get; set; } - /// /// Occurs when [item added]. /// @@ -155,7 +151,8 @@ namespace Emby.Server.Implementations.Library Lazy providerManagerFactory, Lazy userviewManagerFactory, IMediaEncoder mediaEncoder, - IItemRepository itemRepository) + IItemRepository itemRepository, + IImageProcessor imageProcessor) { _appHost = appHost; _logger = logger; @@ -169,6 +166,7 @@ namespace Emby.Server.Implementations.Library _userviewManagerFactory = userviewManagerFactory; _mediaEncoder = mediaEncoder; _itemRepository = itemRepository; + _imageProcessor = imageProcessor; _libraryItemsCache = new ConcurrentDictionary(); @@ -1841,10 +1839,10 @@ namespace Emby.Server.Implementations.Library outdated.ForEach(img => { - ImageDimensions size = ImageProcessor.GetImageDimensions(item, img); + ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); img.Width = size.Width; img.Height = size.Height; - img.Hash = ImageProcessor.GetImageHash(img.Path); + img.Hash = _imageProcessor.GetImageHash(img.Path); }); _itemRepository.SaveImages(item); From 1f83a212886bae879c47b4b4f5b1eb25a28e2ad3 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 21 May 2020 01:43:19 +0300 Subject: [PATCH 37/72] Rename Hash to BlurHash in all properties and methods for clarity --- Emby.Drawing/ImageProcessor.cs | 2 +- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 4 ++-- Emby.Server.Implementations/Dto/DtoService.cs | 6 +++--- Emby.Server.Implementations/Library/LibraryManager.cs | 4 ++-- MediaBrowser.Api/Images/ImageService.cs | 2 +- MediaBrowser.Controller/Drawing/IImageProcessor.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.Controller/Entities/ItemImageInfo.cs | 2 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 1237b603b6..35da6f6359 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -314,7 +314,7 @@ namespace Emby.Drawing => _imageEncoder.GetImageSize(path); /// - public string GetImageHash(string path) + public string GetImageBlurHash(string path) => _imageEncoder.GetImageHash(path); /// diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 10eb96b10c..dd60dd2228 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1144,7 +1144,7 @@ namespace Emby.Server.Implementations.Data const string delimeter = "*"; var path = image.Path ?? string.Empty; - var hash = image.Hash ?? string.Empty; + var hash = image.BlurHash ?? string.Empty; return GetPathToSave(path) + delimeter + @@ -1195,7 +1195,7 @@ namespace Emby.Server.Implementations.Data if (parts.Length >= 6) { - image.Hash = parts[5].Replace('/', '*').Replace('\\', '|'); + image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|'); } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 07105786b9..593e7be7db 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -718,7 +718,7 @@ namespace Emby.Server.Implementations.Dto if (options.EnableImages) { dto.ImageTags = new Dictionary(); - dto.ImageHashes = new Dictionary(); + dto.ImageBlurHashes = new Dictionary(); // Prevent implicitly captured closure var currentItem = item; @@ -733,10 +733,10 @@ namespace Emby.Server.Implementations.Dto dto.ImageTags[image.Type] = tag; } - var hash = image.Hash; + var hash = image.BlurHash; if (!string.IsNullOrEmpty(hash)) { - dto.ImageHashes[tag] = image.Hash; + dto.ImageBlurHashes[tag] = image.BlurHash; } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 75350ac2a8..579fb7eddf 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1829,7 +1829,7 @@ namespace Emby.Server.Implementations.Library } var outdated = item.ImageInfos - .Where(i => (i.IsLocalFile && (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.Hash)))) + .Where(i => (i.IsLocalFile && (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.BlurHash)))) .ToList(); if (outdated.Count == 0) { @@ -1842,7 +1842,7 @@ namespace Emby.Server.Implementations.Library ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); img.Width = size.Width; img.Height = size.Height; - img.Hash = _imageProcessor.GetImageHash(img.Path); + img.BlurHash = _imageProcessor.GetImageBlurHash(img.Path); }); _itemRepository.SaveImages(item); diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 559db550b8..d0846bfc3f 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -341,7 +341,7 @@ namespace MediaBrowser.Api.Images var fileInfo = _fileSystem.GetFileInfo(info.Path); length = fileInfo.Length; - blurhash = info.Hash; + blurhash = info.BlurHash; width = info.Width; height = info.Height; diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index e38eaf7606..8800fdf990 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Controller.Drawing /// /// Path to the image file. /// BlurHash - String GetImageHash(string path); + string GetImageBlurHash(string path); /// /// Gets the image cache tag. diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 035ab1dd94..07aeb69db9 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2223,7 +2223,7 @@ namespace MediaBrowser.Controller.Entities existingImage.DateModified = image.DateModified; existingImage.Width = image.Width; existingImage.Height = image.Height; - existingImage.Hash = image.Hash; + existingImage.BlurHash = image.BlurHash; } else { diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index ba02971073..12f5db2e0b 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the blurhash. /// /// The blurhash. - public string Hash { get; set; } + public string BlurHash { get; set; } [JsonIgnore] public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase); diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 8c6c9683a4..df84dcf123 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -514,7 +514,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the blurhash for the image tags. /// /// The blurhashes. - public Dictionary ImageHashes { get; set; } + public Dictionary ImageBlurHashes { get; set; } /// /// Gets or sets the series studio. From 09915363c2d5f2febed41e803476c7ec6049a06e Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Sun, 24 May 2020 09:22:13 +0100 Subject: [PATCH 38/72] Update Emby.Server.Implementations/Udp/UdpServer.cs Co-authored-by: Mark Monteiro --- Emby.Server.Implementations/Udp/UdpServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 1ae3888dc4..a26f714b12 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Udp private readonly IConfiguration _config; /// - /// Address Override Configuration Key + /// Address Override Configuration Key. /// public const string AddressOverrideConfigKey = "PublishedServerUrl"; From 35da965cd3c7a9224bea0b579744802a0bc5bdcd Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Mon, 25 May 2020 01:29:32 -0400 Subject: [PATCH 39/72] Add -k to keyserver curl command This command seems to inexplicably fail in Docker builds, despite working on the CLI, similar to what happened with the command directly above it in c257d6071c3a8dd141d1191062e892d912177d9a. Fix it in the same way by adding `-k`. --- Dockerfile.arm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.arm b/Dockerfile.arm index 39beaa4791..59b8a8c982 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -38,7 +38,7 @@ COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin RUN apt-get update \ && 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 - && \ + curl -ks 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 && \ From f75a09838e2fadf98544d39b7dbae6174c73cec6 Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 25 May 2020 18:25:45 +0900 Subject: [PATCH 40/72] remove uses of fnchecked from plugins --- .../Plugins/AudioDb/Configuration/config.html | 8 ++++---- .../Plugins/MusicBrainz/Configuration/config.html | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html index 34494644d4..fbf413f2b5 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html @@ -31,8 +31,8 @@ $('.configPage').on('pageshow', function () { Dashboard.showLoadingMsg(); ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { - $('#enable').checked(config.Enable); - $('#replaceAlbumName').checked(config.ReplaceAlbumName); + $('#enable').checked = config.Enable; + $('#replaceAlbumName').checked = config.ReplaceAlbumName; Dashboard.hideLoadingMsg(); }); @@ -43,8 +43,8 @@ var form = this; ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { - config.Enable = $('#enable', form).checked(); - config.ReplaceAlbumName = $('#replaceAlbumName', form).checked(); + config.Enable = $('#enable', form).checked; + config.ReplaceAlbumName = $('#replaceAlbumName', form).checked; ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); }); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html index 1f02461da2..90196b046b 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html @@ -41,8 +41,8 @@ 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); + $('#enable').checked = config.Enable; + $('#replaceArtistName').checked = config.ReplaceArtistName; Dashboard.hideLoadingMsg(); }); @@ -55,8 +55,8 @@ 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(); + config.Enable = $('#enable', form).checked; + config.ReplaceArtistName = $('#replaceArtistName', form).checked; ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); }); From 29443e36817e4866cc58e8397c1233b05624284a Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 00:50:29 +0300 Subject: [PATCH 41/72] Apply suggestions from code review Co-authored-by: dkanada --- .vscode/tasks.json | 1 - Emby.Server.Implementations/Library/LibraryManager.cs | 1 - MediaBrowser.Controller/Drawing/IImageEncoder.cs | 2 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7475617c93..2289fd9914 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -21,6 +21,5 @@ ], "problemMatcher": "$msCompile" } - ] } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 579fb7eddf..e63776bfff 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -71,7 +71,6 @@ namespace Emby.Server.Implementations.Library private readonly ConcurrentDictionary _libraryItemsCache; private readonly IImageProcessor _imageProcessor; - private NamingOptions _namingOptions; private string[] _videoFileExtensions; diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 1d3f0d3b44..4baec62044 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Drawing ImageDimensions GetImageSize(string path); /// - /// Get the blurhash of an image. + /// Gets the blurhash of an image. /// /// The filepath of the image. /// The blurhash. diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index df84dcf123..6213206ffc 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -511,7 +511,7 @@ namespace MediaBrowser.Model.Dto public string SeriesThumbImageTag { get; set; } /// - /// Gets or sets the blurhash for the image tags. + /// Gets or sets the blurhashes for the image tags. /// /// The blurhashes. public Dictionary ImageBlurHashes { get; set; } From 279f0da98031d60b81bb634ca11a2d548cb0f646 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 00:52:13 +0300 Subject: [PATCH 42/72] Rename ImageInfo.Hash to ImageInfo.BlurHash --- MediaBrowser.Api/Images/ImageService.cs | 2 +- MediaBrowser.Model/Dto/ImageInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index d0846bfc3f..89fe726350 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -366,7 +366,7 @@ namespace MediaBrowser.Api.Images ImageType = info.Type, ImageTag = _imageProcessor.GetImageCacheTag(item, info), Size = length, - Hash = blurhash, + BlurHash = blurhash, Width = width, Height = height }; diff --git a/MediaBrowser.Model/Dto/ImageInfo.cs b/MediaBrowser.Model/Dto/ImageInfo.cs index 39bdc09ed8..664ea332e8 100644 --- a/MediaBrowser.Model/Dto/ImageInfo.cs +++ b/MediaBrowser.Model/Dto/ImageInfo.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the blurhash. /// /// The blurhash. - public string Hash { get; set; } + public string BlurHash { get; set; } /// /// Gets or sets the height. From 10e381f66f957ffa2e8339a02b0c970086673739 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 25 May 2020 23:52:51 +0200 Subject: [PATCH 43/72] Fix some 'bugs' flagged by sonarcloud --- DvdLib/Ifo/Program.cs | 2 +- .../ContentDirectory/ContentDirectory.cs | 8 +--- Emby.Dlna/Main/DlnaEntryPoint.cs | 10 ++-- Emby.Dlna/PlayTo/Device.cs | 20 ++++---- Emby.Dlna/PlayTo/PlayToController.cs | 19 +++++--- Emby.Dlna/Ssdp/Extensions.cs | 14 ++---- .../EntryPoints/LibraryChangedNotifier.cs | 4 +- .../EntryPoints/RecordingNotifier.cs | 18 +++---- .../EntryPoints/ServerEventNotifier.cs | 48 +++++++++---------- .../HttpServer/HttpResultFactory.cs | 12 +++-- .../LiveTv/EmbyTV/EmbyTV.cs | 13 ++--- .../LiveTv/EmbyTV/EncodedRecorder.cs | 4 +- .../LiveTv/LiveTvManager.cs | 20 ++------ .../SocketSharp/WebSocketSharpRequest.cs | 5 +- MediaBrowser.Api/Images/ImageService.cs | 3 +- .../Sessions/SessionInfoWebSocketListener.cs | 28 +++++------ .../Net/BasePeriodicWebSocketListener.cs | 25 ++++++---- .../Subtitles/AssParser.cs | 6 ++- 18 files changed, 126 insertions(+), 133 deletions(-) diff --git a/DvdLib/Ifo/Program.cs b/DvdLib/Ifo/Program.cs index 9f62512706..3d94fa7dc1 100644 --- a/DvdLib/Ifo/Program.cs +++ b/DvdLib/Ifo/Program.cs @@ -6,7 +6,7 @@ namespace DvdLib.Ifo { public class Program { - public readonly List Cells; + public IReadOnlyList Cells { get; } public Program(List cells) { diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index 64cd308a2a..66805b7c88 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Linq; using System.Threading.Tasks; using Emby.Dlna.Service; using MediaBrowser.Common.Net; @@ -136,12 +137,7 @@ namespace Emby.Dlna.ContentDirectory } } - foreach (var user in _userManager.Users) - { - return user; - } - - return null; + return _userManager.Users.FirstOrDefault(); } } } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index c5d60b2a05..bcab4adbaf 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -133,20 +133,20 @@ namespace Emby.Dlna.Main { await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); - ReloadComponents(); + await ReloadComponents().ConfigureAwait(false); - _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; + _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated; } - void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) + private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) { if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) { - ReloadComponents(); + await ReloadComponents().ConfigureAwait(false); } } - private async void ReloadComponents() + private async Task ReloadComponents() { var options = _config.GetDlnaConfiguration(); diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 6abc3a82c3..c7431d1437 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -34,7 +34,7 @@ namespace Emby.Dlna.PlayTo { get { - RefreshVolumeIfNeeded(); + RefreshVolumeIfNeeded().GetAwaiter().GetResult(); return _volume; } set => _volume = value; @@ -76,24 +76,24 @@ namespace Emby.Dlna.PlayTo private DateTime _lastVolumeRefresh; private bool _volumeRefreshActive; - private void RefreshVolumeIfNeeded() + private Task RefreshVolumeIfNeeded() { - if (!_volumeRefreshActive) - { - return; - } - - if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5)) + if (_volumeRefreshActive + && DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5)) { _lastVolumeRefresh = DateTime.UtcNow; - RefreshVolume(CancellationToken.None); + return RefreshVolume(); } + + return Task.CompletedTask; } - private async void RefreshVolume(CancellationToken cancellationToken) + private async Task RefreshVolume(CancellationToken cancellationToken = default) { if (_disposed) + { return; + } try { diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 9d7c0d3659..7403a2a16a 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -146,11 +146,14 @@ namespace Emby.Dlna.PlayTo { var positionTicks = GetProgressPositionTicks(streamInfo); - ReportPlaybackStopped(streamInfo, positionTicks); + await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); } streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item == null) return; + if (streamInfo.Item == null) + { + return; + } var newItemProgress = GetProgressInfo(streamInfo); @@ -173,11 +176,14 @@ namespace Emby.Dlna.PlayTo { var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item == null) return; + if (streamInfo.Item == null) + { + return; + } var positionTicks = GetProgressPositionTicks(streamInfo); - ReportPlaybackStopped(streamInfo, positionTicks); + await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); @@ -185,7 +191,7 @@ namespace Emby.Dlna.PlayTo (_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) : mediaSource.RunTimeTicks; - var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0); + var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0; if (!playedToCompletion && duration.HasValue && positionTicks.HasValue) { @@ -210,7 +216,7 @@ namespace Emby.Dlna.PlayTo } } - private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) + private async Task ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) { try { @@ -220,7 +226,6 @@ namespace Emby.Dlna.PlayTo SessionId = _session.Id, PositionTicks = positionTicks, MediaSourceId = streamInfo.MediaSourceId - }).ConfigureAwait(false); } catch (Exception ex) diff --git a/Emby.Dlna/Ssdp/Extensions.cs b/Emby.Dlna/Ssdp/Extensions.cs index 10c1f321be..613d332b2d 100644 --- a/Emby.Dlna/Ssdp/Extensions.cs +++ b/Emby.Dlna/Ssdp/Extensions.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System.Linq; using System.Xml.Linq; namespace Emby.Dlna.Ssdp @@ -10,24 +11,17 @@ namespace Emby.Dlna.Ssdp { var node = container.Element(name); - return node == null ? null : node.Value; + return node?.Value; } public static string GetAttributeValue(this XElement container, XName name) { var node = container.Attribute(name); - return node == null ? null : node.Value; + return node?.Value; } public static string GetDescendantValue(this XElement container, XName name) - { - foreach (var node in container.Descendants(name)) - { - return node.Value; - } - - return null; - } + => container.Descendants(name).FirstOrDefault()?.Value; } } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 8e32364071..9bc2b62ec8 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -302,7 +302,7 @@ namespace Emby.Server.Implementations.EntryPoints .Select(x => x.First()) .ToList(); - SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None); + SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None).GetAwaiter().GetResult(); if (LibraryUpdateTimer != null) { @@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.EntryPoints /// The folders added to. /// The folders removed from. /// The cancellation token. - private async void SendChangeNotifications(List itemsAdded, List itemsUpdated, List itemsRemoved, List foldersAddedTo, List foldersRemovedFrom, CancellationToken cancellationToken) + private async Task SendChangeNotifications(List itemsAdded, List itemsUpdated, List itemsRemoved, List foldersAddedTo, List foldersRemovedFrom, CancellationToken cancellationToken) { var userIds = _sessionManager.Sessions .Select(i => i.UserId) diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 41c0c5115c..997571a91b 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -42,27 +42,27 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { - SendMessage("SeriesTimerCreated", e.Argument); + await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false); } - private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { - SendMessage("TimerCreated", e.Argument); + await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false); } - private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { - SendMessage("SeriesTimerCancelled", e.Argument); + await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false); } - private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { - SendMessage("TimerCancelled", e.Argument); + await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false); } - private async void SendMessage(string name, TimerEventInfo info) + private async Task SendMessage(string name, TimerEventInfo info) { var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList(); diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index e1dbb663bc..dea85d299a 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -85,29 +85,29 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - private void OnPackageInstalling(object sender, InstallationEventArgs e) + private async void OnPackageInstalling(object sender, InstallationEventArgs e) { - SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo); + await SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo).ConfigureAwait(false); } - private void OnPackageInstallationCancelled(object sender, InstallationEventArgs e) + private async void OnPackageInstallationCancelled(object sender, InstallationEventArgs e) { - SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo); + await SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo).ConfigureAwait(false); } - private void OnPackageInstallationCompleted(object sender, InstallationEventArgs e) + private async void OnPackageInstallationCompleted(object sender, InstallationEventArgs e) { - SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo); + await SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo).ConfigureAwait(false); } - private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) + private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) { - SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo); + await SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo).ConfigureAwait(false); } - private void OnTaskCompleted(object sender, TaskCompletionEventArgs e) + private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e) { - SendMessageToAdminSessions("ScheduledTaskEnded", e.Result); + await SendMessageToAdminSessions("ScheduledTaskEnded", e.Result).ConfigureAwait(false); } /// @@ -115,9 +115,9 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The sender. /// The e. - private void OnPluginUninstalled(object sender, GenericEventArgs e) + private async void OnPluginUninstalled(object sender, GenericEventArgs e) { - SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo()); + await SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo()).ConfigureAwait(false); } /// @@ -125,9 +125,9 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The source of the event. /// The instance containing the event data. - private void OnHasPendingRestartChanged(object sender, EventArgs e) + private async void OnHasPendingRestartChanged(object sender, EventArgs e) { - _sessionManager.SendRestartRequiredNotification(CancellationToken.None); + await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false); } /// @@ -135,11 +135,11 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The sender. /// The e. - private void OnUserUpdated(object sender, GenericEventArgs e) + private async void OnUserUpdated(object sender, GenericEventArgs e) { var dto = _userManager.GetUserDto(e.Argument); - SendMessageToUserSession(e.Argument, "UserUpdated", dto); + await SendMessageToUserSession(e.Argument, "UserUpdated", dto).ConfigureAwait(false); } /// @@ -147,26 +147,26 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The sender. /// The e. - private void OnUserDeleted(object sender, GenericEventArgs e) + private async void OnUserDeleted(object sender, GenericEventArgs e) { - SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)); + await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false); } - private void OnUserPolicyUpdated(object sender, GenericEventArgs e) + private async void OnUserPolicyUpdated(object sender, GenericEventArgs e) { var dto = _userManager.GetUserDto(e.Argument); - SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto); + await SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto).ConfigureAwait(false); } - private void OnUserConfigurationUpdated(object sender, GenericEventArgs e) + private async void OnUserConfigurationUpdated(object sender, GenericEventArgs e) { var dto = _userManager.GetUserDto(e.Argument); - SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto); + await SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto).ConfigureAwait(false); } - private async void SendMessageToAdminSessions(string name, T data) + private async Task SendMessageToAdminSessions(string name, T data) { try { @@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.EntryPoints } } - private async void SendMessageToUserSession(User user, string name, T data) + private async Task SendMessageToUserSession(User user, string name, T data) { try { diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 2e9ecc4ae6..cffae7b1c3 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -255,16 +255,20 @@ namespace Emby.Server.Implementations.HttpServer { var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString(); - if (string.IsNullOrEmpty(acceptEncoding)) + if (!string.IsNullOrEmpty(acceptEncoding)) { - //if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) + // if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) // return "br"; - if (acceptEncoding.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1) + if (acceptEncoding.Contains("deflate", StringComparison.OrdinalIgnoreCase)) + { return "deflate"; + } - if (acceptEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1) + if (acceptEncoding.Contains("gzip", StringComparison.OrdinalIgnoreCase)) + { return "gzip"; + } } return null; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 3efe1ee253..5a5dc33292 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -140,11 +140,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) + private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) { if (string.Equals(e.Key, "livetv", StringComparison.OrdinalIgnoreCase)) { - OnRecordingFoldersChanged(); + await CreateRecordingFolders().ConfigureAwait(false); } } @@ -155,11 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return CreateRecordingFolders(); } - private async void OnRecordingFoldersChanged() - { - await CreateRecordingFolders().ConfigureAwait(false); - } - internal async Task CreateRecordingFolders() { try @@ -1334,7 +1329,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV await CreateRecordingFolders().ConfigureAwait(false); TriggerRefresh(recordPath); - EnforceKeepUpTo(timer, seriesPath); + await EnforceKeepUpTo(timer, seriesPath).ConfigureAwait(false); }; await recorder.Record(directStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false); @@ -1494,7 +1489,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return item; } - private async void EnforceKeepUpTo(TimerInfo timer, string seriesPath) + private async Task EnforceKeepUpTo(TimerInfo timer, string seriesPath) { if (string.IsNullOrWhiteSpace(timer.SeriesTimerId)) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index bc86cc59a2..70dd8f321e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV 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); @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private async void StartStreamingLog(Stream source, Stream target) + private async Task StartStreamingLog(Stream source, Stream target) { try { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1b10f2d27c..a3dd45a53d 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -788,22 +788,12 @@ namespace Emby.Server.Implementations.LiveTv if (query.OrderBy.Count == 0) { - if (query.IsAiring ?? false) + + // Unless something else was specified, order by start date to take advantage of a specialized index + query.OrderBy = new[] { - // Unless something else was specified, order by start date to take advantage of a specialized index - query.OrderBy = new[] - { - (ItemSortBy.StartDate, SortOrder.Ascending) - }; - } - else - { - // Unless something else was specified, order by start date to take advantage of a specialized index - query.OrderBy = new[] - { - (ItemSortBy.StartDate, SortOrder.Ascending) - }; - } + (ItemSortBy.StartDate, SortOrder.Ascending) + }; } RemoveFields(options); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index ee5131c1ff..5554aa97f8 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -208,8 +208,9 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { - ReadOnlySpan format = httpReq.Query["format"].ToString(); - if (format == null) + string formatStr = httpReq.Query["format"].ToString(); + ReadOnlySpan format = formatStr; + if (formatStr == null) { const int FormatMaxLength = 4; ReadOnlySpan pi = httpReq.Path.ToString(); diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 2e9b3e6cb4..eaff22fffb 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -555,8 +555,7 @@ namespace MediaBrowser.Api.Images var imageInfo = GetImageInfo(request, item); if (imageInfo == null) { - var displayText = item == null ? itemId.ToString() : item.Name; - throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", displayText, request.Type)); + throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type)); } bool cropwhitespace; diff --git a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs index 0e74c92679..175984575d 100644 --- a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs @@ -40,39 +40,39 @@ namespace MediaBrowser.Api.Sessions _sessionManager.SessionActivity += OnSessionManagerSessionActivity; } - private void OnSessionManagerSessionActivity(object sender, SessionEventArgs e) + private async void OnSessionManagerSessionActivity(object sender, SessionEventArgs e) { - SendData(false); + await SendData(false).ConfigureAwait(false); } - private void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e) + private async void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } - private void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e) + private async void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e) { - SendData(!e.IsAutomated); + await SendData(!e.IsAutomated).ConfigureAwait(false); } - private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) + private async void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } - private void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e) + private async void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) + private async void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } - private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) + private async void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } /// diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 1162bff130..5be656bdbe 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -104,7 +104,7 @@ namespace MediaBrowser.Controller.Net } } - protected void SendData(bool force) + protected async Task SendData(bool force) { Tuple[] tuples; @@ -128,13 +128,18 @@ namespace MediaBrowser.Controller.Net .ToArray(); } - foreach (var tuple in tuples) + IEnumerable GetTasks() { - SendData(tuple); + foreach (var tuple in tuples) + { + yield return SendData(tuple); + } } + + await Task.WhenAll(GetTasks()).ConfigureAwait(false); } - private async void SendData(Tuple tuple) + private async Task SendData(Tuple tuple) { var connection = tuple.Item1; @@ -148,11 +153,13 @@ namespace MediaBrowser.Controller.Net if (data != null) { - await connection.SendAsync(new WebSocketMessage - { - MessageType = Name, - Data = data - }, cancellationToken).ConfigureAwait(false); + await connection.SendAsync( + new WebSocketMessage + { + MessageType = Name, + Data = data + }, + cancellationToken).ConfigureAwait(false); state.DateLastSendUtc = DateTime.UtcNow; } diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs index 293cf5ea5b..f44cf14523 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs @@ -33,10 +33,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles { continue; } + if (line.StartsWith("[")) + { break; - if (string.IsNullOrEmpty(line)) - continue; + } + var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) }; eventIndex++; var sections = line.Substring(10).Split(','); From 46420dfd68945fd7c7045b8492c401e3d8cd302d Mon Sep 17 00:00:00 2001 From: xumix Date: Tue, 26 May 2020 00:58:19 +0300 Subject: [PATCH 44/72] Refactor copy codec checks --- MediaBrowser.Api/ApiEntryPoint.cs | 4 +-- .../Playback/BaseStreamingService.cs | 10 +++---- .../Playback/Hls/DynamicHlsService.cs | 14 ++++----- .../Playback/Hls/VideoHlsService.cs | 2 +- MediaBrowser.Api/Playback/StreamState.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 20 ++++++++----- .../MediaEncoding/EncodingJobInfo.cs | 30 +++++++++---------- .../Configuration/EncodingOptions.cs | 12 ++++++++ 8 files changed, 56 insertions(+), 38 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 6691080bc8..c7485a2e96 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -284,8 +284,8 @@ namespace MediaBrowser.Api Width = state.OutputWidth, Height = state.OutputHeight, AudioChannels = state.OutputAudioChannels, - IsAudioDirect = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase), - IsVideoDirect = string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase), + IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec), + IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec), TranscodeReasons = state.TranscodeReasons }); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index f796aa486d..24297d5002 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -193,7 +193,7 @@ namespace MediaBrowser.Api.Playback await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); - if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (state.VideoRequest != null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { var auth = AuthorizationContext.GetAuthorizationInfo(Request); if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding) @@ -243,9 +243,9 @@ namespace MediaBrowser.Api.Playback var logFilePrefix = "ffmpeg-transcode"; if (state.VideoRequest != null - && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { - logFilePrefix = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase) + logFilePrefix = EncodingHelper.IsCopyCodec(state.OutputAudioCodec) ? "ffmpeg-remux" : "ffmpeg-directstream"; } @@ -328,7 +328,7 @@ namespace MediaBrowser.Api.Playback state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo && state.VideoType == VideoType.VideoFile && - !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase); + !EncodingHelper.IsCopyCodec(state.OutputVideoCodec); } return false; @@ -791,7 +791,7 @@ namespace MediaBrowser.Api.Playback EncodingHelper.TryStreamCopy(state); } - if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (state.OutputVideoBitrate.HasValue && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { var resolution = ResolutionNormalizer.Normalize( state.VideoStream?.BitRate, diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 061316cb86..50d34cca96 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -700,12 +700,12 @@ namespace MediaBrowser.Api.Playback.Hls return false; } - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { return false; } - if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(state.OutputAudioCodec)) { return false; } @@ -728,7 +728,7 @@ namespace MediaBrowser.Api.Playback.Hls private int? GetOutputVideoCodecLevel(StreamState state) { string levelString; - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.VideoStream.Level.HasValue) { levelString = state.VideoStream?.Level.ToString(); @@ -1008,7 +1008,7 @@ namespace MediaBrowser.Api.Playback.Hls if (!state.IsOutputVideo) { - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(audioCodec)) { return "-acodec copy"; } @@ -1036,11 +1036,11 @@ namespace MediaBrowser.Api.Playback.Hls return string.Join(" ", audioTranscodeParams.ToArray()); } - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(audioCodec)) { var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.EnableBreakOnNonKeyFrames(videoCodec)) + if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec)) { return "-codec:a:0 copy -copypriorss:a:0 0"; } @@ -1091,7 +1091,7 @@ namespace MediaBrowser.Api.Playback.Hls // } // See if we can save come cpu cycles by avoiding encoding - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(codec)) { if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index d1c53c1c11..aefb3f019b 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -72,7 +72,7 @@ namespace MediaBrowser.Api.Playback.Hls { var codec = EncodingHelper.GetAudioEncoder(state); - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(codec)) { return "-codec:a:0 copy"; } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index d5d2f58c03..c244b00334 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback return Request.SegmentLength.Value; } - if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) { var userAgent = UserAgent ?? string.Empty; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 61a3306756..2d2f731480 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1338,7 +1338,7 @@ namespace MediaBrowser.Controller.MediaEncoding transcoderChannelLimit = 6; } - var isTranscodingAudio = !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); + var isTranscodingAudio = !EncodingHelper.IsCopyCodec(codec); int? resultChannels = state.GetRequestedAudioChannels(codec); if (isTranscodingAudio) @@ -1734,7 +1734,8 @@ namespace MediaBrowser.Controller.MediaEncoding 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) + if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs)) && width.HasValue && height.HasValue) { @@ -1991,7 +1992,7 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwupload"); } - // When the input may or may not be hardware QSV decodable + // When the input may or may not be hardware QSV decodable else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { if (!hasTextSubs) @@ -2248,7 +2249,7 @@ namespace MediaBrowser.Controller.MediaEncoding flags.Add("+ignidx"); } - if (state.GenPtsInput || string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (state.GenPtsInput || EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { flags.Add("+genpts"); } @@ -2511,7 +2512,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) { - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { return null; } @@ -2799,7 +2800,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += " -mpegts_m2ts_mode 1"; } - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(videoCodec)) { if (state.VideoStream != null && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) @@ -2901,7 +2902,7 @@ namespace MediaBrowser.Controller.MediaEncoding var args = "-codec:a:0 " + codec; - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(codec)) { return args; } @@ -2973,5 +2974,10 @@ namespace MediaBrowser.Controller.MediaEncoding string.Empty, string.Empty).Trim(); } + + public static bool IsCopyCodec(string codec) + { + return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 1127a08ded..7cd7835959 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -302,7 +302,7 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - return BaseRequest.BreakOnNonKeyFrames && string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase); + return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec); } return false; @@ -367,7 +367,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputAudioCodec)) { if (AudioStream != null) { @@ -390,7 +390,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputAudioCodec)) { if (AudioStream != null) { @@ -409,7 +409,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.Level; } @@ -433,7 +433,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.BitDepth; } @@ -451,7 +451,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.RefFrames; } @@ -468,7 +468,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream == null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate); } @@ -499,7 +499,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.PacketLength; } @@ -515,7 +515,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.Profile; } @@ -535,7 +535,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.CodecTag; } @@ -549,7 +549,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.IsAnamorphic; } @@ -562,7 +562,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.Codec; } @@ -575,7 +575,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(OutputAudioCodec)) { return AudioStream?.Codec; } @@ -589,7 +589,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.IsInterlaced; } @@ -607,7 +607,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.IsAVC; } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 648568fd70..5880730fd2 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -5,10 +5,15 @@ namespace MediaBrowser.Model.Configuration public class EncodingOptions { public int EncodingThreadCount { get; set; } + public string TranscodingTempPath { get; set; } + public double DownMixAudioBoost { get; set; } + public bool EnableThrottling { get; set; } + public int ThrottleDelaySeconds { get; set; } + public string HardwareAccelerationType { get; set; } /// @@ -20,12 +25,19 @@ namespace MediaBrowser.Model.Configuration /// The current FFmpeg path being used by the system and displayed on the transcode page. /// public string EncoderAppPathDisplay { get; set; } + public string VaapiDevice { get; set; } + public int H264Crf { get; set; } + public int H265Crf { get; set; } + public string EncoderPreset { get; set; } + public string DeinterlaceMethod { get; set; } + public bool EnableHardwareEncoding { get; set; } + public bool EnableSubtitleExtraction { get; set; } public string[] HardwareDecodingCodecs { get; set; } From 0f32b0ffad03817f34b02a4fc7371e2a0a67e63a Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 01:04:40 +0300 Subject: [PATCH 45/72] Change image blurhash mapping to "image type to blurhash" --- Emby.Server.Implementations/Dto/DtoService.cs | 4 ++-- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 593e7be7db..77a734ebff 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -718,7 +718,7 @@ namespace Emby.Server.Implementations.Dto if (options.EnableImages) { dto.ImageTags = new Dictionary(); - dto.ImageBlurHashes = new Dictionary(); + dto.ImageBlurHashes = new Dictionary(); // Prevent implicitly captured closure var currentItem = item; @@ -736,7 +736,7 @@ namespace Emby.Server.Implementations.Dto var hash = image.BlurHash; if (!string.IsNullOrEmpty(hash)) { - dto.ImageBlurHashes[tag] = image.BlurHash; + dto.ImageBlurHashes[image.Type] = image.BlurHash; } } } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 6213206ffc..734bcaf81b 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -514,7 +514,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the blurhashes for the image tags. /// /// The blurhashes. - public Dictionary ImageBlurHashes { get; set; } + public Dictionary ImageBlurHashes { get; set; } /// /// Gets or sets the series studio. From f575415e0b611cbfb4139e9e9c816adcb000442e Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 02:31:40 +0300 Subject: [PATCH 46/72] Pick blurhash sizes depending on image aspect ratio --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 7ab0a54dfd..ccd501214e 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -241,14 +241,20 @@ namespace Jellyfin.Drawing.Skia throw new ArgumentNullException(nameof(path)); } - if (!File.Exists(path)) + var dims = GetImageSize(path); + if (dims.Width <= 0 || dims.Height <= 0) { - throw new FileNotFoundException("File not found", path); + // empty image does not have any blurhash + return string.Empty; } - // Use 4 vertical and 4 horizontal components of DCT of the image. + // We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance. + // One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width. // See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components - return BlurHashEncoder.Encode(4, 4, path); + float xComp = MathF.Sqrt(16.0f * dims.Width / dims.Height); + float yComp = xComp * dims.Height / dims.Width; + + return BlurHashEncoder.Encode(Math.Min((int)xComp + 1, 9), Math.Min((int)yComp + 1, 9), path); } private static bool HasDiacritics(string text) From e42bfc92f3e072a3d51dddce06bc90587e06791c Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 26 May 2020 11:37:52 +0200 Subject: [PATCH 47/72] Fix code issues --- .../HttpServer/WebSocketConnection.cs | 6 +-- .../Session/SessionWebSocketListener.cs | 10 ++--- .../SyncPlay/SyncPlayController.cs | 37 ++----------------- .../SyncPlay/SyncPlayManager.cs | 1 + MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 4 +- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 9 +++-- 6 files changed, 19 insertions(+), 48 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 1f5a7d1773..0680c5ffe7 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) { - SendKeepAliveResponse(); + await SendKeepAliveResponse(); } else { @@ -231,10 +231,10 @@ namespace Emby.Server.Implementations.HttpServer } } - private void SendKeepAliveResponse() + private Task SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; - SendAsync(new WebSocketMessage + return SendAsync(new WebSocketMessage { MessageType = "KeepAlive" }, CancellationToken.None); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 3af18f6813..e7b4b0ec35 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -87,13 +87,13 @@ namespace Emby.Server.Implementations.Session httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; } - private void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) + private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) { var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString()); if (session != null) { EnsureController(session, e.Argument); - KeepAliveWebSocket(e.Argument); + await KeepAliveWebSocket(e.Argument); } else { @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Session /// The event arguments. private void OnWebSocketClosed(object sender, EventArgs e) { - var webSocket = (IWebSocketConnection) sender; + var webSocket = (IWebSocketConnection)sender; _logger.LogDebug("WebSocket {0} is closed.", webSocket); RemoveWebSocket(webSocket); } @@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Session /// Adds a WebSocket to the KeepAlive watchlist. /// /// The WebSocket to monitor. - private void KeepAliveWebSocket(IWebSocketConnection webSocket) + private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) { lock (_webSocketsLock) { @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.Session // Notify WebSocket about timeout try { - SendForceKeepAlive(webSocket).Wait(); + await SendForceKeepAlive(webSocket); } catch (WebSocketException exception) { diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index c7bd242a7c..d430d4d162 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.SyncPlay /// /// Class is not thread-safe, external locking is required when accessing methods. /// - public class SyncPlayController : ISyncPlayController, IDisposable + public class SyncPlayController : ISyncPlayController { /// /// Used to filter the sessions of a group. @@ -65,8 +65,6 @@ namespace Emby.Server.Implementations.SyncPlay /// public bool IsGroupEmpty() => _group.IsEmpty(); - private bool _disposed = false; - public SyncPlayController( ISessionManager sessionManager, ISyncPlayManager syncPlayManager) @@ -75,36 +73,6 @@ namespace Emby.Server.Implementations.SyncPlay _syncPlayManager = syncPlayManager; } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _disposed = true; - } - - // TODO: use this somewhere - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - /// /// Converts DateTime to UTC string. /// @@ -518,6 +486,7 @@ namespace Emby.Server.Implementations.SyncPlay var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; ticks = ticks > runTimeTicks ? runTimeTicks : ticks; } + return ticks; } @@ -541,7 +510,7 @@ namespace Emby.Server.Implementations.SyncPlay PlayingItemName = _group.PlayingItem.Name, PlayingItemId = _group.PlayingItem.Id.ToString(), PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList().AsReadOnly() + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList() }; } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 93cec1304c..1f76dd4e36 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -374,6 +374,7 @@ namespace Emby.Server.Implementations.SyncPlay { throw new InvalidOperationException("Session in other group already!"); } + _sessionToGroupMap[session.Id] = group; } diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 8064ea7dc1..1e14ea552c 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -182,7 +182,7 @@ namespace MediaBrowser.Api.SyncPlay } // Both null and empty strings mean that client isn't playing anything - if (!String.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) + if (!string.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) { Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); return; @@ -217,7 +217,7 @@ namespace MediaBrowser.Api.SyncPlay var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; - if (!String.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) + if (!string.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) { Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs index bda49bd1b0..28a3ac505f 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -16,12 +16,13 @@ namespace MediaBrowser.Controller.SyncPlay /// /// Default ping value used for sessions. /// - public readonly long DefaulPing = 500; + public long DefaulPing { get; } = 500; + /// /// Gets or sets the group identifier. /// /// The group identifier. - public readonly Guid GroupId = Guid.NewGuid(); + public Guid GroupId { get; } = Guid.NewGuid(); /// /// Gets or sets the playing item. @@ -51,7 +52,7 @@ namespace MediaBrowser.Controller.SyncPlay /// Gets the participants. /// /// The participants, or members of the group. - public readonly Dictionary Participants = + public Dictionary Participants { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// @@ -85,7 +86,6 @@ namespace MediaBrowser.Controller.SyncPlay /// Removes the session from the group. /// /// The session. - public void RemoveSession(SessionInfo session) { if (!ContainsSession(session.Id.ToString())) @@ -153,6 +153,7 @@ namespace MediaBrowser.Controller.SyncPlay return true; } } + return false; } From 0be3dfe7c53d8c3bb43c28ea02c8a594bcb903b2 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Tue, 26 May 2020 12:14:40 -0400 Subject: [PATCH 48/72] Revert "Fix emby/user/public API leaking sensitive data" --- .../Library/UserManager.cs | 25 ---------- MediaBrowser.Api/UserService.cs | 38 +++++---------- .../Library/IUserManager.cs | 8 ---- MediaBrowser.Model/Dto/PublicUserDto.cs | 48 ------------------- 4 files changed, 11 insertions(+), 108 deletions(-) delete mode 100644 MediaBrowser.Model/Dto/PublicUserDto.cs diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index b8feb5535f..d63bc6bda8 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -608,31 +608,6 @@ namespace Emby.Server.Implementations.Library return dto; } - public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - IAuthenticationProvider authenticationProvider = GetAuthenticationProvider(user); - bool hasConfiguredPassword = authenticationProvider.HasPassword(user); - bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(authenticationProvider.GetEasyPasswordHash(user)); - - bool hasPassword = user.Configuration.EnableLocalPassword && - !string.IsNullOrEmpty(remoteEndPoint) && - _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword; - - PublicUserDto dto = new PublicUserDto - { - Name = user.Name, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword, - }; - - return dto; - } - public UserDto GetOfflineUserDto(User user) { var dto = GetUserDto(user); diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 7d4d5fcf97..78fc6c6941 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Api } [Route("/Users/Public", "GET", Summary = "Gets a list of publicly visible users for display on a login screen.")] - public class GetPublicUsers : IReturn + public class GetPublicUsers : IReturn { } @@ -266,38 +266,22 @@ namespace MediaBrowser.Api _authContext = authContext; } - /// - /// Gets the public available Users information - /// - /// The request. - /// System.Object. public object Get(GetPublicUsers request) { - var result = _userManager - .Users - .Where(item => !item.Policy.IsDisabled); - - if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) + // If the startup wizard hasn't been completed then just return all users + if (!ServerConfigurationManager.Configuration.IsStartupWizardCompleted) { - var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; - result = result.Where(item => !item.Policy.IsHidden); - - if (!string.IsNullOrWhiteSpace(deviceId)) + return Get(new GetUsers { - result = result.Where(i => _deviceManager.CanAccessDevice(i, deviceId)); - } - - if (!_networkManager.IsInLocalNetwork(Request.RemoteIp)) - { - result = result.Where(i => i.Policy.EnableRemoteAccess); - } + IsDisabled = false + }); } - return ToOptimizedResult(result - .OrderBy(u => u.Name) - .Select(i => _userManager.GetPublicUserDto(i, Request.RemoteIp)) - .ToArray() - ); + return Get(new GetUsers + { + IsHidden = false, + IsDisabled = false + }, true, true); } /// diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index ec6cb35eb9..be7b4ce59d 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -143,14 +143,6 @@ namespace MediaBrowser.Controller.Library /// UserDto. UserDto GetUserDto(User user, string remoteEndPoint = null); - /// - /// Gets the user public dto. - /// - /// Ther user.\ - /// The remote end point. - /// A public UserDto, aka a UserDto stripped of personal data. - PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null); - /// /// Authenticates the user. /// diff --git a/MediaBrowser.Model/Dto/PublicUserDto.cs b/MediaBrowser.Model/Dto/PublicUserDto.cs deleted file mode 100644 index b6bfaf2e9b..0000000000 --- a/MediaBrowser.Model/Dto/PublicUserDto.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Dto -{ - /// - /// Class PublicUserDto. Its goal is to show only public information about a user - /// - public class PublicUserDto : IItemDto - { - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the primary image tag. - /// - /// The primary image tag. - public string PrimaryImageTag { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has password. - /// - /// true if this instance has password; otherwise, false. - public bool HasPassword { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has configured password. - /// Note that in this case this method should not be here, but it is necessary when changing password at the - /// first login. - /// - /// true if this instance has configured password; otherwise, false. - public bool HasConfiguredPassword { get; set; } - - /// - /// Gets or sets the primary image aspect ratio. - /// - /// The primary image aspect ratio. - public double? PrimaryImageAspectRatio { get; set; } - - /// - public override string ToString() - { - return Name ?? base.ToString(); - } - } -} From 0676d1c2f45eb19c993a0e130c6fbef0eade67d9 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 May 2020 19:33:20 +0200 Subject: [PATCH 49/72] Update WebSocketSharpRequest.cs --- .../SocketSharp/WebSocketSharpRequest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 5554aa97f8..7488b1938f 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -208,9 +208,8 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { - string formatStr = httpReq.Query["format"].ToString(); - ReadOnlySpan format = formatStr; - if (formatStr == null) + ReadOnlySpan format = httpReq.Query["format"].ToString(); + if (formatStr == ReadOnlySpan.Empty) { const int FormatMaxLength = 4; ReadOnlySpan pi = httpReq.Path.ToString(); From b61ee09a36ed38958dc3897be6a30ca8ad191813 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 May 2020 20:00:37 +0200 Subject: [PATCH 50/72] Update WebSocketSharpRequest.cs --- .../SocketSharp/WebSocketSharpRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 7488b1938f..6ca58b1c32 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -209,7 +209,7 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { ReadOnlySpan format = httpReq.Query["format"].ToString(); - if (formatStr == ReadOnlySpan.Empty) + if (format == ReadOnlySpan.Empty) { const int FormatMaxLength = 4; ReadOnlySpan pi = httpReq.Path.ToString(); From 73d123fe3630a39339cdecef620ee190318ccf88 Mon Sep 17 00:00:00 2001 From: dkanada Date: Wed, 27 May 2020 13:55:45 +0900 Subject: [PATCH 51/72] fix issue with audio transcoding --- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 061316cb86..b8ea9c6da0 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -865,7 +865,7 @@ namespace MediaBrowser.Api.Playback.Hls { framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3); } - else if (state.VideoStream.RealFrameRate.HasValue) + else if (state.VideoStream?.RealFrameRate != null) { framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3); } From 6c9dc0418961a673f9e5dcfb36f66d076381a92e Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 27 May 2020 15:01:03 +0300 Subject: [PATCH 52/72] Handle errors during blurhash generation so it does not fail the scan --- Emby.Server.Implementations/Library/LibraryManager.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index e63776bfff..903c0b3cfb 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1841,7 +1841,15 @@ namespace Emby.Server.Implementations.Library ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); img.Width = size.Width; img.Height = size.Height; - img.BlurHash = _imageProcessor.GetImageBlurHash(img.Path); + try + { + img.BlurHash = _imageProcessor.GetImageBlurHash(img.Path); + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot compute blurhash for {0}", img.Path); + img.BlurHash = string.Empty; + } }); _itemRepository.SaveImages(item); From 2482bcb3b1ba9ea6e861709704ce1f184fcc0d9c Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 27 May 2020 16:27:27 +0300 Subject: [PATCH 53/72] Add blurhashes to ImageBlurHashes for all images --- Emby.Server.Implementations/Dto/DtoService.cs | 98 +++++++++++++++---- MediaBrowser.Model/Dto/BaseItemDto.cs | 3 +- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 77a734ebff..38c4f940d7 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -605,7 +605,7 @@ namespace Emby.Server.Implementations.Dto if (dictionary.TryGetValue(person.Name, out Person entity)) { - baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); + baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary); baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture); list.Add(baseItemPerson); } @@ -654,6 +654,70 @@ namespace Emby.Server.Implementations.Dto return _libraryManager.GetGenreId(name); } + private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0) + { + var image = item.GetImageInfo(imageType, imageIndex); + if (image != null) + { + return GetTagAndFillBlurhash(dto, item, image); + } + + return null; + } + + private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image) + { + var tag = GetImageCacheTag(item, image); + if (!string.IsNullOrEmpty(image.BlurHash)) + { + if (dto.ImageBlurHashes == null) + { + dto.ImageBlurHashes = new Dictionary>(); + } + + if (!dto.ImageBlurHashes.ContainsKey(image.Type)) + { + dto.ImageBlurHashes[image.Type] = new Dictionary(); + } + + dto.ImageBlurHashes[image.Type][tag] = image.BlurHash; + } + + return tag; + } + + private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit) + { + return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList()); + } + + private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List images) + { + var tags = GetImageTags(item, images); + var hashes = new Dictionary(); + for (int i = 0; i < images.Count; i++) + { + var img = images[i]; + if (!string.IsNullOrEmpty(img.BlurHash)) + { + var tag = tags[i]; + hashes[tag] = img.BlurHash; + } + } + + if (hashes.Count > 0) + { + if (dto.ImageBlurHashes == null) + { + dto.ImageBlurHashes = new Dictionary>(); + } + + dto.ImageBlurHashes[imageType] = hashes; + } + + return tags; + } + /// /// Sets simple property values on a DTOBaseItem /// @@ -674,8 +738,8 @@ namespace Emby.Server.Implementations.Dto dto.LockData = item.IsLocked; dto.ForcedSortName = item.ForcedSortName; } - dto.Container = item.Container; + dto.Container = item.Container; dto.EndDate = item.EndDate; if (options.ContainsField(ItemFields.ExternalUrls)) @@ -694,10 +758,12 @@ namespace Emby.Server.Implementations.Dto dto.AspectRatio = hasAspectRatio.AspectRatio; } + dto.ImageBlurHashes = new Dictionary>(); + var backdropLimit = options.GetImageLimit(ImageType.Backdrop); if (backdropLimit > 0) { - dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList()); + dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit); } if (options.ContainsField(ItemFields.ScreenshotImageTags)) @@ -705,7 +771,7 @@ namespace Emby.Server.Implementations.Dto var screenshotLimit = options.GetImageLimit(ImageType.Screenshot); if (screenshotLimit > 0) { - dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList()); + dto.ScreenshotImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Screenshot, screenshotLimit); } } @@ -718,7 +784,6 @@ namespace Emby.Server.Implementations.Dto if (options.EnableImages) { dto.ImageTags = new Dictionary(); - dto.ImageBlurHashes = new Dictionary(); // Prevent implicitly captured closure var currentItem = item; @@ -726,18 +791,12 @@ namespace Emby.Server.Implementations.Dto { if (options.GetImageLimit(image.Type) > 0) { - var tag = GetImageCacheTag(item, image); + var tag = GetTagAndFillBlurhash(dto, item, image); if (tag != null) { dto.ImageTags[image.Type] = tag; } - - var hash = image.BlurHash; - if (!string.IsNullOrEmpty(hash)) - { - dto.ImageBlurHashes[image.Type] = image.BlurHash; - } } } } @@ -877,8 +936,7 @@ namespace Emby.Server.Implementations.Dto if (albumParent != null) { dto.AlbumId = albumParent.Id; - - dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary); + dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary); } //if (options.ContainsField(ItemFields.MediaSourceCount)) @@ -1105,7 +1163,7 @@ namespace Emby.Server.Implementations.Dto episodeSeries = episodeSeries ?? episode.Series; if (episodeSeries != null) { - dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary); + dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary); } } @@ -1151,7 +1209,7 @@ namespace Emby.Server.Implementations.Dto series = series ?? season.Series; if (series != null) { - dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary); } } } @@ -1281,7 +1339,7 @@ namespace Emby.Server.Implementations.Dto if (image != null) { dto.ParentLogoItemId = GetDtoId(parent); - dto.ParentLogoImageTag = GetImageCacheTag(parent, image); + dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image); } } if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null) @@ -1291,7 +1349,7 @@ namespace Emby.Server.Implementations.Dto if (image != null) { dto.ParentArtItemId = GetDtoId(parent); - dto.ParentArtImageTag = GetImageCacheTag(parent, image); + dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image); } } if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView)) @@ -1301,7 +1359,7 @@ namespace Emby.Server.Implementations.Dto if (image != null) { dto.ParentThumbItemId = GetDtoId(parent); - dto.ParentThumbImageTag = GetImageCacheTag(parent, image); + dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image); } } if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0))) @@ -1311,7 +1369,7 @@ namespace Emby.Server.Implementations.Dto if (images.Count > 0) { dto.ParentBackdropItemId = GetDtoId(parent); - dto.ParentBackdropImageTags = GetImageTags(parent, images); + dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images); } } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 734bcaf81b..fc1cb01db7 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -512,9 +512,10 @@ namespace MediaBrowser.Model.Dto /// /// Gets or sets the blurhashes for the image tags. + /// Maps image type to dictionary mapping image tag to blurhash value. /// /// The blurhashes. - public Dictionary ImageBlurHashes { get; set; } + public Dictionary> ImageBlurHashes { get; set; } /// /// Gets or sets the series studio. From edcfcadcd327affb308b0c0eb5cfbb1416e27cae Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 27 May 2020 17:00:59 +0300 Subject: [PATCH 54/72] Make sure blurhash is recomputed if image changed or metadata refresh toggled --- .../Library/LibraryManager.cs | 47 +++++++++++++++---- .../Library/ILibraryManager.cs | 2 +- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 903c0b3cfb..84bcd1bc16 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1820,23 +1820,44 @@ namespace Emby.Server.Implementations.Library } } - public void UpdateImages(BaseItem item) + private bool ImageNeedsRefresh(ItemImageInfo image) + { + if (image.Path != null && image.IsLocalFile) + { + if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash)) + { + return true; + } + + try + { + return _fileSystem.GetLastWriteTimeUtc(image.Path) != image.DateModified; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot get file info for {0}", image.Path); + return false; + } + } + + return false; + } + + public void UpdateImages(BaseItem item, bool forceUpdate = false) { if (item == null) { throw new ArgumentNullException(nameof(item)); } - var outdated = item.ImageInfos - .Where(i => (i.IsLocalFile && (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.BlurHash)))) - .ToList(); - if (outdated.Count == 0) + var outdated = forceUpdate ? item.ImageInfos : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); + if (outdated.Length == 0) { RegisterItem(item); return; } - outdated.ForEach(img => + foreach (var img in outdated) { ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); img.Width = size.Width; @@ -1850,10 +1871,18 @@ namespace Emby.Server.Implementations.Library _logger.LogError(ex, "Cannot compute blurhash for {0}", img.Path); img.BlurHash = string.Empty; } - }); + + try + { + img.DateModified = _fileSystem.GetLastWriteTimeUtc(img.Path); + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot update DateModified for {0}", img.Path); + } + } _itemRepository.SaveImages(item); - RegisterItem(item); } @@ -1874,7 +1903,7 @@ namespace Emby.Server.Implementations.Library item.DateLastSaved = DateTime.UtcNow; - UpdateImages(item); + UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate); } _itemRepository.SaveItems(itemsList, cancellationToken); diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 81160efece..916e4fda77 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -118,7 +118,7 @@ namespace MediaBrowser.Controller.Library /// void QueueLibraryScan(); - void UpdateImages(BaseItem item); + void UpdateImages(BaseItem item, bool forceUpdate = false); /// /// Gets the default view. From f30b07130fc21247e3c6edd61d004a9286c5340c Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 27 May 2020 19:29:57 +0300 Subject: [PATCH 55/72] Workaround a bug in BlurHashSharp --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index ccd501214e..85461ca4ef 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -251,10 +251,18 @@ namespace Jellyfin.Drawing.Skia // We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance. // One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width. // See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components - float xComp = MathF.Sqrt(16.0f * dims.Width / dims.Height); - float yComp = xComp * dims.Height / dims.Width; + float xCompF = MathF.Sqrt(16.0f * dims.Width / dims.Height); + float yCompF = xCompF * dims.Height / dims.Width; - return BlurHashEncoder.Encode(Math.Min((int)xComp + 1, 9), Math.Min((int)yComp + 1, 9), path); + int xComp = Math.Min((int)xCompF + 1, 9); + int yComp = Math.Min((int)yCompF + 1, 9); + + // FIXME: current lib is bugged for xComp != yComp + // remove when https://github.com/Bond-009/BlurHashSharp/pull/1 is merged + int tmp = Math.Max(xComp, yComp); + xComp = yComp = tmp; + + return BlurHashEncoder.Encode(xComp, yComp, path); } private static bool HasDiacritics(string text) From 0ebad893a7dcc2623fb98d59a36f59089cc6a1ec Mon Sep 17 00:00:00 2001 From: Elouan MAILLY Date: Wed, 27 May 2020 20:07:27 +0000 Subject: [PATCH 56/72] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 150952d8ba..d4009e92bf 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -5,7 +5,7 @@ "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", "Books": "Livres", - "CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}", + "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}", "Channels": "Chaînes", "ChapterNameValue": "Chapitre {0}", "Collections": "Collections", @@ -15,7 +15,7 @@ "Favorites": "Favoris", "Folders": "Dossiers", "Genres": "Genres", - "HeaderAlbumArtists": "Artistes de l'album", + "HeaderAlbumArtists": "Artistes", "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", From a76cee7a953a77794ca9824cdff581a350fcf0ae Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 28 May 2020 17:23:16 +0300 Subject: [PATCH 57/72] Update BlurHashSharp to 1.0.1, remove workaround --- Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 2 +- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 221346b688..d3fdb5de4b 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -18,7 +18,7 @@ - + diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 85461ca4ef..7f0da2c9ee 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -257,11 +257,6 @@ namespace Jellyfin.Drawing.Skia int xComp = Math.Min((int)xCompF + 1, 9); int yComp = Math.Min((int)yCompF + 1, 9); - // FIXME: current lib is bugged for xComp != yComp - // remove when https://github.com/Bond-009/BlurHashSharp/pull/1 is merged - int tmp = Math.Max(xComp, yComp); - xComp = yComp = tmp; - return BlurHashEncoder.Encode(xComp, yComp, path); } From ed791dee46b8f3b72fbdb68a3382622b4337e254 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 28 May 2020 17:30:11 +0300 Subject: [PATCH 58/72] Do not compute dimensions or blurhash for remote images --- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 84bcd1bc16..6cbca7d02d 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1850,7 +1850,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(item)); } - var outdated = forceUpdate ? item.ImageInfos : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); + var outdated = forceUpdate ? item.ImageInfos.Where(i => i.IsLocalFile).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); if (outdated.Length == 0) { RegisterItem(item); From 9208acd5ae26d41af6791d7dd7322d682a8b80b6 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 28 May 2020 17:55:29 +0300 Subject: [PATCH 59/72] Convert non-local image to local before computing blurhash --- .../Library/LibraryManager.cs | 40 ++++++++++++++----- MediaBrowser.Controller/Entities/BaseItem.cs | 40 +++++++++++++++++++ 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6cbca7d02d..bb3e3dd117 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1840,7 +1840,7 @@ namespace Emby.Server.Implementations.Library } } - return false; + return image.Path != null && !image.IsLocalFile; } public void UpdateImages(BaseItem item, bool forceUpdate = false) @@ -1850,7 +1850,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(item)); } - var outdated = forceUpdate ? item.ImageInfos.Where(i => i.IsLocalFile).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); + var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); if (outdated.Length == 0) { RegisterItem(item); @@ -1859,26 +1859,46 @@ namespace Emby.Server.Implementations.Library foreach (var img in outdated) { - ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); - img.Width = size.Width; - img.Height = size.Height; + var image = img; + if (!img.IsLocalFile) + { + try + { + var index = item.GetImageIndex(img); + image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (ArgumentException) + { + _logger.LogWarning("Cannot get image index for {0}", img.Path); + continue; + } + catch (InvalidOperationException) + { + _logger.LogWarning("Cannot fetch image from {0}", img.Path); + continue; + } + } + + ImageDimensions size = _imageProcessor.GetImageDimensions(item, image); + image.Width = size.Width; + image.Height = size.Height; try { - img.BlurHash = _imageProcessor.GetImageBlurHash(img.Path); + image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path); } catch (Exception ex) { - _logger.LogError(ex, "Cannot compute blurhash for {0}", img.Path); - img.BlurHash = string.Empty; + _logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path); + image.BlurHash = string.Empty; } try { - img.DateModified = _fileSystem.GetLastWriteTimeUtc(img.Path); + image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path); } catch (Exception ex) { - _logger.LogError(ex, "Cannot update DateModified for {0}", img.Path); + _logger.LogError(ex, "Cannot update DateModified for {0}", image.Path); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 07aeb69db9..f4b71d8bf7 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2375,6 +2375,46 @@ namespace MediaBrowser.Controller.Entities .ElementAtOrDefault(imageIndex); } + /// + /// Computes image index for given image or raises if no matching image found. + /// + /// Image to compute index for. + /// Image index cannot be computed as no matching image found. + /// + /// Image index. + public int GetImageIndex(ItemImageInfo image) + { + if (image == null) + { + throw new ArgumentNullException(nameof(image)); + } + + if (image.Type == ImageType.Chapter) + { + var chapters = ItemRepository.GetChapters(this); + for (var i = 0; i < chapters.Count; i++) + { + if (chapters[i].ImagePath == image.Path) + { + return i; + } + } + + throw new ArgumentException("No chapter index found for image path", image.Path); + } + + var images = GetImages(image.Type).ToArray(); + for (var i = 0; i < images.Length; i++) + { + if (images[i].Path == image.Path) + { + return i; + } + } + + throw new ArgumentException("No image index found for image path", image.Path); + } + public IEnumerable GetImages(ImageType imageType) { if (imageType == ImageType.Chapter) From 58f099c0e2fec23d861bc9bdb76ac0d6d8e239b7 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 28 May 2020 19:12:08 +0300 Subject: [PATCH 60/72] Fix naming per code review --- .../Data/SqliteItemRepository.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index dd60dd2228..608801ac3d 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1141,21 +1141,21 @@ namespace Emby.Server.Implementations.Data public string ToValueString(ItemImageInfo image) { - const string delimeter = "*"; + const string Delimeter = "*"; var path = image.Path ?? string.Empty; var hash = image.BlurHash ?? string.Empty; return GetPathToSave(path) + - delimeter + + Delimeter + image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + - delimeter + + Delimeter + image.Type + - delimeter + + Delimeter + image.Width.ToString(CultureInfo.InvariantCulture) + - delimeter + + Delimeter + image.Height.ToString(CultureInfo.InvariantCulture) + - delimeter + + Delimeter + // Replace delimiters with other characters. // This can be removed when we migrate to a proper DB. hash.Replace('*', '/').Replace('|', '\\'); From 65461894d47844edf632b0516427f1f85b8b7c4e Mon Sep 17 00:00:00 2001 From: Lumenol Date: Thu, 28 May 2020 20:30:36 +0000 Subject: [PATCH 61/72] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index d4009e92bf..47ebe12540 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -5,7 +5,7 @@ "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", "Books": "Livres", - "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}", + "CameraImageUploadedFrom": "Une photo a été chargée depuis {0}", "Channels": "Chaînes", "ChapterNameValue": "Chapitre {0}", "Collections": "Collections", From 4748105dce13c0fe0b4d8fcbf44f26033d314b26 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 29 May 2020 11:28:19 +0200 Subject: [PATCH 62/72] Enable TreatWarningsAsErrors for Jellyfin.Server.Implementations in Release mode --- .../Channels/ChannelManager.cs | 4 +- .../Data/SqliteItemRepository.cs | 5 +- .../Emby.Server.Implementations.csproj | 1 + .../EntryPoints/UdpServerEntryPoint.cs | 3 +- .../HttpServer/HttpResultFactory.cs | 1 + .../IStartupOptions.cs | 2 + .../Library/DefaultAuthenticationProvider.cs | 38 ---- .../Library/LibraryManager.cs | 8 +- .../LiveTv/LiveTvManager.cs | 2 - .../LiveTv/RefreshChannelsScheduledTask.cs | 2 + .../MediaEncoder/EncodingManager.cs | 2 + .../Net/SocketFactory.cs | 2 + Emby.Server.Implementations/Net/UdpSocket.cs | 2 + .../Networking/NetworkManager.cs | 2 + .../Playlists/ManualPlaylistsFolder.cs | 2 + .../Playlists/PlaylistImageProvider.cs | 6 +- .../Playlists/PlaylistManager.cs | 2 + .../ResourceFileManager.cs | 2 + .../ScheduledTasks/ScheduledTaskWorker.cs | 11 +- .../ScheduledTasks/TaskManager.cs | 4 +- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 7 + .../Tasks/DeleteCacheFileTask.cs | 7 + .../ScheduledTasks/Tasks/DeleteLogFileTask.cs | 9 + .../Tasks/DeleteTranscodeFileTask.cs | 7 + .../Tasks/PeopleValidationTask.cs | 10 +- .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 2 + .../Tasks/RefreshMediaLibraryTask.cs | 17 +- .../ScheduledTasks/Triggers/DailyTrigger.cs | 7 +- .../Triggers/IntervalTrigger.cs | 2 + .../ScheduledTasks/Triggers/StartupTrigger.cs | 6 +- .../ScheduledTasks/Triggers/WeeklyTrigger.cs | 2 + .../Security/AuthenticationRepository.cs | 2 + .../Serialization/JsonSerializer.cs | 5 + .../Services/HttpResult.cs | 2 + .../Services/RequestHelper.cs | 7 +- .../Services/ResponseHelper.cs | 5 +- .../Services/ServiceController.cs | 2 + .../Services/ServiceExec.cs | 2 + .../Services/ServiceHandler.cs | 2 + .../Services/ServiceMethod.cs | 2 + .../Services/ServicePath.cs | 2 + .../Services/StringMapTypeDeserializer.cs | 2 + .../Services/SwaggerService.cs | 2 + .../Services/UrlExtensions.cs | 2 + .../Session/SessionManager.cs | 2 + .../SocketSharp/HttpFile.cs | 18 -- .../SocketSharp/HttpPostedFile.cs | 198 ------------------ .../SocketSharp/WebSocketSharpRequest.cs | 2 + .../Sorting/AiredEpisodeOrderComparer.cs | 2 + .../Sorting/CommunityRatingComparer.cs | 18 +- .../Sorting/DateLastMediaAddedComparer.cs | 18 +- .../Sorting/IsFavoriteOrLikeComparer.cs | 38 ++-- .../Sorting/IsFolderComparer.cs | 14 +- .../Sorting/IsPlayedComparer.cs | 38 ++-- .../Sorting/IsUnplayedComparer.cs | 38 ++-- .../Sorting/OfficialRatingComparer.cs | 14 +- .../Sorting/SeriesSortNameComparer.cs | 14 +- .../Sorting/StartDateComparer.cs | 19 +- .../Sorting/StudioComparer.cs | 2 + .../SyncPlay/SyncPlayController.cs | 5 + .../SyncPlay/SyncPlayManager.cs | 7 + .../TV/TVSeriesManager.cs | 7 +- .../Updates/InstallationManager.cs | 4 +- .../CollectionFolderImageProvider.cs | 2 + .../UserViews/DynamicImageProvider.cs | 6 +- .../UserViews/FolderImageProvider.cs | 6 +- .../Migrations/IMigrationRoutine.cs | 1 - .../Routines/CreateUserLoggingConfigFile.cs | 1 - Jellyfin.Server/Program.cs | 4 +- 69 files changed, 291 insertions(+), 401 deletions(-) delete mode 100644 Emby.Server.Implementations/SocketSharp/HttpFile.cs delete mode 100644 Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 138832fb86..04fe0bacb4 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -46,14 +46,14 @@ namespace Emby.Server.Implementations.Channels new ConcurrentDictionary>>(); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); - + /// /// Initializes a new instance of the class. /// /// The user manager. /// The dto service. /// The library manager. - /// The logger factory. + /// The logger. /// The server configuration manager. /// The filesystem. /// The user data manager. diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index ca5cd6fdd5..58702541e0 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; @@ -33,7 +35,7 @@ using SQLitePCL.pretty; namespace Emby.Server.Implementations.Data { /// - /// Class SQLiteItemRepository + /// Class SQLiteItemRepository. /// public class SqliteItemRepository : BaseSqliteRepository, IItemRepository { @@ -1971,6 +1973,7 @@ namespace Emby.Server.Implementations.Data /// Gets the chapter. /// /// The reader. + /// The item. /// ChapterInfo. private ChapterInfo GetChapter(IReadOnlyList reader, BaseItem item) { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index b69a126b3a..279ec30986 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -54,6 +54,7 @@ netstandard2.1 false true + true diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 6929c81f92..5bc1a81aa6 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -47,10 +47,11 @@ namespace Emby.Server.Implementations.EntryPoints } /// - public async Task RunAsync() + public Task RunAsync() { _udpServer = new UdpServer(_logger, _appHost, _config); _udpServer.Start(PortNumber, _cancellationTokenSource.Token); + return Task.CompletedTask; } /// diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 2e9ecc4ae6..dd7f753cc3 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -56,6 +56,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// Gets the result. /// + /// The request context. /// The content. /// Type of the content. /// The response headers. diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index acae702f30..0b9f805389 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; namespace Emby.Server.Implementations diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 52c8facc3e..02f1506076 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -135,43 +135,5 @@ namespace Emby.Server.Implementations.Library ? null : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); } - - /// - /// Gets the hashed string. - /// - public string GetHashedString(User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).ToString(); - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - var salt = passwordHash.Salt.ToArray(); - return new PasswordHash( - passwordHash.Id, - _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - salt), - salt, - passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); - } - - public ReadOnlySpan GetHashed(User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).Hash; - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - return _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - passwordHash.Salt.ToArray()); - } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0b86b2db7e..67a72d3137 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -50,7 +50,7 @@ using VideoResolver = Emby.Naming.Video.VideoResolver; namespace Emby.Server.Implementations.Library { /// - /// Class LibraryManager + /// Class LibraryManager. /// public class LibraryManager : ILibraryManager { @@ -135,6 +135,12 @@ namespace Emby.Server.Implementations.Library /// The user manager. /// The configuration manager. /// The user data repository. + /// The library monitor. + /// The file system. + /// The provider manager. + /// The userview manager. + /// The media encoder. + /// The item repository. public LibraryManager( IServerApplicationHost appHost, ILogger logger, diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1b10f2d27c..3e48425a24 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -10,10 +10,8 @@ using Emby.Server.Implementations.Library; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; -using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index 1056a33b9a..8e7d60a15a 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 677d68b4c9..7b7575707a 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index e42ff8496e..f347540c77 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 211ca67841..848f82d855 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index b3e88b6675..d1a28e7a19 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index cd9f7946e9..889760586c 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index bb56d9771b..f8a2d97417 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.Linq; using Emby.Server.Implementations.Images; @@ -32,9 +34,7 @@ namespace Emby.Server.Implementations.Playlists { var subItem = i.Item2; - var episode = subItem as Episode; - - if (episode != null) + if (subItem is Episode episode) { var series = episode.Series; if (series != null && series.HasImage(ImageType.Primary)) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 9b1510ac97..d4d1c1ff7b 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/ResourceFileManager.cs b/Emby.Server.Implementations/ResourceFileManager.cs index 6eda2b5032..d192be921f 100644 --- a/Emby.Server.Implementations/ResourceFileManager.cs +++ b/Emby.Server.Implementations/ResourceFileManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.IO; using MediaBrowser.Controller; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 5b188d9626..dc3e9a6070 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Globalization; using System.IO; @@ -51,7 +53,6 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The task manager. private ITaskManager TaskManager { get; set; } - private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. @@ -72,24 +73,28 @@ namespace Emby.Server.Implementations.ScheduledTasks /// or /// logger /// - public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem) + public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger) { if (scheduledTask == null) { throw new ArgumentNullException(nameof(scheduledTask)); } + if (applicationPaths == null) { throw new ArgumentNullException(nameof(applicationPaths)); } + if (taskManager == null) { throw new ArgumentNullException(nameof(taskManager)); } + if (jsonSerializer == null) { throw new ArgumentNullException(nameof(jsonSerializer)); } + if (logger == null) { throw new ArgumentNullException(nameof(logger)); @@ -100,7 +105,6 @@ namespace Emby.Server.Implementations.ScheduledTasks TaskManager = taskManager; JsonSerializer = jsonSerializer; Logger = logger; - _fileSystem = fileSystem; InitTriggerEvents(); } @@ -576,6 +580,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The start time. /// The end time. /// The status. + /// The exception. private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex) { var elapsedTime = endTime - startTime; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 6ffa581a9c..9076802393 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -199,7 +201,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The tasks. public void AddTasks(IEnumerable tasks) { - var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger, _fileSystem)); + var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger)); ScheduledTasks = ScheduledTasks.Concat(list).ToArray(); } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index ea6a70615b..fae0499148 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -169,18 +169,25 @@ namespace Emby.Server.Implementations.ScheduledTasks } } + /// public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages"); + /// public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription"); + /// public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + /// public string Key => "RefreshChapterImages"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 9df7c538b1..a6c13eaefe 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -165,18 +165,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } + /// public string Name => _localization.GetLocalizedString("TaskCleanCache"); + /// public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription"); + /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + /// public string Key => "DeleteCacheFiles"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index 3140aa4893..402b39a263 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -28,6 +28,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// Initializes a new instance of the class. /// /// The configuration manager. + /// The file system. + /// The localization manager. public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization) { ConfigurationManager = configurationManager; @@ -82,18 +84,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Task.CompletedTask; } + /// public string Name => _localization.GetLocalizedString("TaskCleanLogs"); + /// public string Description => string.Format(_localization.GetLocalizedString("TaskCleanLogsDescription"), ConfigurationManager.CommonConfiguration.LogFileRetentionDays); + /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + /// public string Key => "CleanLogFiles"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 1d133dcda8..0d36b82c09 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -132,18 +132,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } + /// public string Name => _localization.GetLocalizedString("TaskCleanTranscode"); + /// public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription"); + /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + /// public string Key => "DeleteTranscodeFiles"; + /// public bool IsHidden => false; + /// public bool IsEnabled => false; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index 63f867bf6c..c384cf4bbe 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -1,8 +1,9 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; @@ -18,19 +19,16 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The library manager. /// private readonly ILibraryManager _libraryManager; - - private readonly IServerApplicationHost _appHost; private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// /// The library manager. - /// The server application host - public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost, ILocalizationManager localization) + /// The localization manager. + public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization) { _libraryManager = libraryManager; - _appHost = appHost; _localization = localization; } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index 6a1afced79..9d9d77538e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index 74cb01444e..e470adcf48 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -1,9 +1,10 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; @@ -16,20 +17,19 @@ namespace Emby.Server.Implementations.ScheduledTasks public class RefreshMediaLibraryTask : IScheduledTask { /// - /// The _library manager + /// The _library manager. /// private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// /// The library manager. - public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config, ILocalizationManager localization) + /// The localization manager. + public RefreshMediaLibraryTask(ILibraryManager libraryManager, ILocalizationManager localization) { _libraryManager = libraryManager; - _config = config; _localization = localization; } @@ -61,18 +61,25 @@ namespace Emby.Server.Implementations.ScheduledTasks return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken); } + /// public string Name => _localization.GetLocalizedString("TaskRefreshLibrary"); + /// public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription"); + /// public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + /// public string Key => "RefreshLibrary"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index ea278de0d9..c7819d4c0f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -31,6 +31,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { @@ -77,10 +79,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// private void OnTriggered() { - if (Triggered != null) - { - Triggered(this, EventArgs.Empty); - } + Triggered?.Invoke(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index 3a34da3af2..74cd4ef1ea 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -34,6 +34,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index 08ff4f55f7..e171a9e9f5 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Threading.Tasks; using MediaBrowser.Model.Tasks; @@ -6,7 +8,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// - /// Class StartupTaskTrigger + /// Class StartupTaskTrigger. /// public class StartupTrigger : ITaskTrigger { @@ -26,6 +28,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public async void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 2a6a7b13cd..ad0b57af6e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -37,6 +37,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 4e4029f06f..750890ec8f 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs index bcc814daf2..5ec3a735a6 100644 --- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs +++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Globalization; using System.IO; @@ -11,6 +13,9 @@ namespace Emby.Server.Implementations.Serialization /// public class JsonSerializer : IJsonSerializer { + /// + /// Initializes a new instance of the class. + /// public JsonSerializer() { ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601; diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index 095193828b..8ba86f756d 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.IO; using System.Net; diff --git a/Emby.Server.Implementations/Services/RequestHelper.cs b/Emby.Server.Implementations/Services/RequestHelper.cs index 2563cac999..1f9c7fc223 100644 --- a/Emby.Server.Implementations/Services/RequestHelper.cs +++ b/Emby.Server.Implementations/Services/RequestHelper.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.IO; using System.Threading.Tasks; @@ -43,10 +45,7 @@ namespace Emby.Server.Implementations.Services private static string GetContentTypeWithoutEncoding(string contentType) { - return contentType == null - ? null - : contentType.Split(';')[0].ToLowerInvariant().Trim(); + return contentType?.Split(';')[0].ToLowerInvariant().Trim(); } - } } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index a566b18dd0..f2b1d06f3a 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Globalization; using System.IO; @@ -43,8 +45,7 @@ namespace Emby.Server.Implementations.Services response.StatusCode = httpResult.Status; } - var responseOptions = result as IHasHeaders; - if (responseOptions != null) + if (result is IHasHeaders responseOptions) { foreach (var responseHeaders in responseOptions.Headers) { diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index e24a95dbb3..ad6015c1cd 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 9f5f97028c..606f2a2405 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 934560de3c..7f44357e1b 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Reflection; diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs index 5018bf4a2e..59ee5908fd 100644 --- a/Emby.Server.Implementations/Services/ServiceMethod.cs +++ b/Emby.Server.Implementations/Services/ServiceMethod.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; namespace Emby.Server.Implementations.Services diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 27c4dcba07..278379a921 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index 56e23d5492..ab22fe019a 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Reflection; diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index 5177251c3e..16142a70df 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs index 483c63ade7..e3b6aa1975 100644 --- a/Emby.Server.Implementations/Services/UrlExtensions.cs +++ b/Emby.Server.Implementations/Services/UrlExtensions.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Common.Extensions; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 2b09a93efc..5c480e8424 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.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/SocketSharp/HttpFile.cs b/Emby.Server.Implementations/SocketSharp/HttpFile.cs deleted file mode 100644 index 120ac50d9c..0000000000 --- a/Emby.Server.Implementations/SocketSharp/HttpFile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.IO; -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.SocketSharp -{ - public class HttpFile : IHttpFile - { - public string Name { get; set; } - - public string FileName { get; set; } - - public long ContentLength { get; set; } - - public string ContentType { get; set; } - - public Stream InputStream { get; set; } - } -} diff --git a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs deleted file mode 100644 index 7479d81045..0000000000 --- a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.IO; - -public sealed class HttpPostedFile : IDisposable -{ - private string _name; - private string _contentType; - private Stream _stream; - private bool _disposed = false; - - internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) - { - _name = name; - _contentType = content_type; - _stream = new ReadSubStream(base_stream, offset, length); - } - - public string ContentType => _contentType; - - public int ContentLength => (int)_stream.Length; - - public string FileName => _name; - - public Stream InputStream => _stream; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - _stream.Dispose(); - _stream = null; - - _name = null; - _contentType = null; - - _disposed = true; - } - - private class ReadSubStream : Stream - { - private Stream _stream; - private long _offset; - private long _end; - private long _position; - - public ReadSubStream(Stream s, long offset, long length) - { - _stream = s; - _offset = offset; - _end = offset + length; - _position = offset; - } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => false; - - public override long Length => _end - _offset; - - public override long Position - { - get => _position - _offset; - set - { - if (value > Length) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _position = Seek(value, SeekOrigin.Begin); - } - } - - public override void Flush() - { - } - - public override int Read(byte[] buffer, int dest_offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (dest_offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "< 0"); - } - - int len = buffer.Length; - if (dest_offset > len) - { - throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset)); - } - - // reordered to avoid possible integer overflow - if (dest_offset > len - count) - { - throw new ArgumentException("Reading would overrun buffer", nameof(count)); - } - - if (count > _end - _position) - { - count = (int)(_end - _position); - } - - if (count <= 0) - { - return 0; - } - - _stream.Position = _position; - int result = _stream.Read(buffer, dest_offset, count); - if (result > 0) - { - _position += result; - } - else - { - _position = _end; - } - - return result; - } - - public override int ReadByte() - { - if (_position >= _end) - { - return -1; - } - - _stream.Position = _position; - int result = _stream.ReadByte(); - if (result < 0) - { - _position = _end; - } - else - { - _position++; - } - - return result; - } - - public override long Seek(long d, SeekOrigin origin) - { - long real; - switch (origin) - { - case SeekOrigin.Begin: - real = _offset + d; - break; - case SeekOrigin.End: - real = _end + d; - break; - case SeekOrigin.Current: - real = _position + d; - break; - default: - throw new ArgumentException("Unknown SeekOrigin value", nameof(origin)); - } - - long virt = real - _offset; - if (virt < 0 || virt > Length) - { - throw new ArgumentException("Invalid position", nameof(d)); - } - - _position = _stream.Seek(real, SeekOrigin.Begin); - return _position; - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - } -} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index ee5131c1ff..146c84d7cb 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 16507466f9..67e31f7f63 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs index 87d3ae2d6d..980954ba03 100644 --- a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; @@ -7,6 +9,12 @@ namespace Emby.Server.Implementations.Sorting { public class CommunityRatingComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.CommunityRating; + /// /// Compares the specified x. /// @@ -16,18 +24,16 @@ namespace Emby.Server.Implementations.Sorting public int Compare(BaseItem x, BaseItem y) { if (x == null) + { throw new ArgumentNullException(nameof(x)); + } if (y == null) + { throw new ArgumentNullException(nameof(y)); + } return (x.CommunityRating ?? 0).CompareTo(y.CommunityRating ?? 0); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.CommunityRating; } } diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 623675157d..5c1503ed2b 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -26,6 +28,12 @@ namespace Emby.Server.Implementations.Sorting /// The user data repository. public IUserDataManager UserDataRepository { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.DateLastContentAdded; + /// /// Compares the specified x. /// @@ -44,9 +52,7 @@ namespace Emby.Server.Implementations.Sorting /// DateTime. private static DateTime GetDate(BaseItem x) { - var folder = x as Folder; - - if (folder != null) + if (x is Folder folder) { if (folder.DateLastMediaAdded.HasValue) { @@ -56,11 +62,5 @@ namespace Emby.Server.Implementations.Sorting return DateTime.MinValue; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.DateLastContentAdded; } } diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index 66de05a6a2..aba14c6ca0 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -13,6 +15,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsFavoriteOrLiked; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsFavoriteOrLiked(User) ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsFavoriteOrLiked; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs index dfaa144cdc..a35192eff8 100644 --- a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Querying; @@ -6,6 +8,12 @@ namespace Emby.Server.Implementations.Sorting { public class IsFolderComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsFolder; + /// /// Compares the specified x. /// @@ -26,11 +34,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsFolder ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsFolder; } } diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index da3f3dd25b..39d9bc68ea 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -13,6 +15,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsUnplayed; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsPlayed(User) ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsUnplayed; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index d99d0eff21..478df4035f 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -13,6 +15,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsUnplayed; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsUnplayed(User) ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsUnplayed; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index 7afbd9ff7d..76bb798b5f 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; @@ -15,6 +17,12 @@ namespace Emby.Server.Implementations.Sorting _localization = localization; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.OfficialRating; + /// /// Compares the specified x. /// @@ -38,11 +46,5 @@ namespace Emby.Server.Implementations.Sorting return levelX.CompareTo(levelY); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.OfficialRating; } } diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 504b6d2838..b9205ee076 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; @@ -7,6 +9,12 @@ namespace Emby.Server.Implementations.Sorting { public class SeriesSortNameComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.SeriesSortName; + /// /// Compares the specified x. /// @@ -18,12 +26,6 @@ namespace Emby.Server.Implementations.Sorting return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.SeriesSortName; - private static string GetValue(BaseItem item) { var hasSeries = item as IHasSeries; diff --git a/Emby.Server.Implementations/Sorting/StartDateComparer.cs b/Emby.Server.Implementations/Sorting/StartDateComparer.cs index aa040fa15e..558a3d3513 100644 --- a/Emby.Server.Implementations/Sorting/StartDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/StartDateComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.LiveTv; @@ -8,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting { public class StartDateComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.StartDate; + /// /// Compares the specified x. /// @@ -26,19 +34,12 @@ namespace Emby.Server.Implementations.Sorting /// DateTime. private static DateTime GetDate(BaseItem x) { - var hasStartDate = x as LiveTvProgram; - - if (hasStartDate != null) + if (x is LiveTvProgram hasStartDate) { return hasStartDate.StartDate; } + return DateTime.MinValue; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.StartDate; } } diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index c9ac765c10..5766dc542b 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Linq; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index d430d4d162..d0812a13f6 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -65,6 +65,11 @@ namespace Emby.Server.Implementations.SyncPlay /// public bool IsGroupEmpty() => _group.IsEmpty(); + /// + /// Initializes a new instance of the class. + /// + /// The session manager. + /// The SyncPlay manager. public SyncPlayController( ISessionManager sessionManager, ISyncPlayManager syncPlayManager) diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 1f76dd4e36..129262e53c 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -57,6 +57,13 @@ namespace Emby.Server.Implementations.SyncPlay private bool _disposed = false; + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The user manager. + /// The session manager. + /// The library manager. public SyncPlayManager( ILogger logger, IUserManager userManager, diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 4c2f24e6f2..383615f748 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,8 +1,9 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -18,14 +19,12 @@ namespace Emby.Server.Implementations.TV private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; - public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager config) + public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager) { _userManager = userManager; _userDataManager = userDataManager; _libraryManager = libraryManager; - _config = config; } public QueryResult GetNextUp(NextUpQuery request, DtoOptions dtoOptions) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 0b2309889f..4f6a84ef7f 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,11 +1,11 @@ +#pragma warning disable CS1591 + using System; 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; diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index a3f3f6cb4d..7b7d66ca6c 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index 78ac95f85e..e7888595fc 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; @@ -19,13 +21,11 @@ namespace Emby.Server.Implementations.UserViews public class DynamicImageProvider : BaseDynamicImageProvider { private readonly IUserManager _userManager; - private readonly ILibraryManager _libraryManager; - public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager) + public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) { _userManager = userManager; - _libraryManager = libraryManager; } protected override IReadOnlyList GetItemsWithImages(BaseItem item) diff --git a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs index 4655cd928a..58a023638f 100644 --- a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; @@ -75,14 +77,14 @@ namespace Emby.Server.Implementations.UserViews return false; } - var folder = item as Folder; - if (folder != null) + if (item is Folder folder) { if (folder.IsTopParent) { return false; } } + return true; //return item.SourceType == SourceType.Library; } diff --git a/Jellyfin.Server/Migrations/IMigrationRoutine.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs index b79fdeac03..6b5780a262 100644 --- a/Jellyfin.Server/Migrations/IMigrationRoutine.cs +++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs @@ -1,5 +1,4 @@ using System; -using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations { diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 89514c89b7..b15e092906 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -3,7 +3,6 @@ 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 diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index b9895386f5..7c693f8c39 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -40,12 +40,12 @@ namespace Jellyfin.Server /// /// The name of logging configuration file containing application defaults. /// - public static readonly string LoggingConfigFileDefault = "logging.default.json"; + public const string LoggingConfigFileDefault = "logging.default.json"; /// /// The name of the logging configuration file containing the system-specific override settings. /// - public static readonly string LoggingConfigFileSystem = "logging.json"; + public const string LoggingConfigFileSystem = "logging.json"; private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); From f73c5d3f56f73c0c938d7aa75ad18aeb0a9fd8d0 Mon Sep 17 00:00:00 2001 From: WontTell Date: Sat, 30 May 2020 02:27:19 +0000 Subject: [PATCH 63/72] Added translation using Weblate (Spanish (Latin America)) --- Emby.Server.Implementations/Localization/Core/es_419.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/es_419.json diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -0,0 +1 @@ +{} From 730395886db06ef83fa7e6f29fd95666519eb458 Mon Sep 17 00:00:00 2001 From: WontTell Date: Sat, 30 May 2020 02:27:41 +0000 Subject: [PATCH 64/72] Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/ --- .../Localization/Core/es_419.json | 118 +++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json index 0967ef424b..b0fdc8386c 100644 --- a/Emby.Server.Implementations/Localization/Core/es_419.json +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -1 +1,117 @@ -{} +{ + "LabelRunningTimeValue": "Duración: {0}", + "ValueSpecialEpisodeName": "Especial - {0}", + "Sync": "Sincronizar", + "Songs": "Canciones", + "Shows": "Programas", + "Playlists": "Listas de reproducción", + "Photos": "Fotos", + "Movies": "Películas", + "HeaderNextUp": "A continuación", + "HeaderLiveTV": "TV en vivo", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderContinueWatching": "Continuar viendo", + "HeaderAlbumArtists": "Artistas del álbum", + "Genres": "Géneros", + "Folders": "Carpetas", + "Favorites": "Favoritos", + "Collections": "Colecciones", + "Channels": "Canales", + "Books": "Libros", + "Artists": "Artistas", + "Albums": "Álbumes", + "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", + "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", + "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", + "TaskRefreshChannels": "Actualizar canales", + "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.", + "TaskCleanTranscode": "Limpiar directorio de transcodificado", + "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", + "TaskUpdatePlugins": "Actualizar complementos", + "TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.", + "TaskRefreshPeople": "Actualizar personas", + "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad.", + "TaskCleanLogs": "Limpiar directorio de registros", + "TaskRefreshLibraryDescription": "Escanea tu biblioteca de medios por archivos nuevos y actualiza los metadatos.", + "TaskRefreshLibrary": "Escanear biblioteca de medios", + "TaskRefreshChapterImagesDescription": "Crea miniaturas para videos que tienen capítulos.", + "TaskRefreshChapterImages": "Extraer imágenes de los capítulos", + "TaskCleanCacheDescription": "Elimina archivos caché que ya no son necesarios para el sistema.", + "TaskCleanCache": "Limpiar directorio caché", + "TasksChannelsCategory": "Canales de Internet", + "TasksApplicationCategory": "Aplicación", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Mantenimiento", + "VersionNumber": "Versión {0}", + "ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca de medios", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}", + "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", + "UserOnlineFromDevice": "{0} está en línea desde {1}", + "UserOfflineFromDevice": "{0} se ha desconectado desde {1}", + "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", + "UserDownloadingItemWithValues": "{0} está descargando {1}", + "UserDeletedWithName": "El usuario {0} ha sido eliminado", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "User": "Usuario", + "TvShows": "Programas de TV", + "System": "Sistema", + "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}", + "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", + "ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado", + "ScheduledTaskStartedWithName": "{0} iniciado", + "ScheduledTaskFailedWithName": "{0} falló", + "ProviderValue": "Proveedor: {0}", + "PluginUpdatedWithName": "{0} fue actualizado", + "PluginUninstalledWithName": "{0} fue desinstalado", + "PluginInstalledWithName": "{0} fue instalado", + "Plugin": "Complemento", + "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", + "NotificationOptionVideoPlayback": "Reproducción de video iniciada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionTaskFailed": "Falla de tarea programada", + "NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor", + "NotificationOptionPluginUpdateInstalled": "Actualización de complemento instalada", + "NotificationOptionPluginUninstalled": "Complemento desinstalado", + "NotificationOptionPluginInstalled": "Complemento instalado", + "NotificationOptionPluginError": "Falla de complemento", + "NotificationOptionNewLibraryContent": "Nuevo contenido agregado", + "NotificationOptionInstallationFailed": "Falla de instalación", + "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", + "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", + "NotificationOptionAudioPlayback": "Reproducción de audio iniciada", + "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", + "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", + "NewVersionIsAvailable": "Una nueva versión del Servidor Jellyfin está disponible para descargar.", + "NameSeasonUnknown": "Temporada desconocida", + "NameSeasonNumber": "Temporada {0}", + "NameInstallFailed": "Falló la instalación de {0}", + "MusicVideos": "Videos musicales", + "Music": "Música", + "MixedContent": "Contenido mezclado", + "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", + "MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor", + "MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}", + "MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado", + "Latest": "Recientes", + "LabelIpAddressValue": "Dirección IP: {0}", + "ItemRemovedWithName": "{0} fue removido de la biblioteca", + "ItemAddedWithName": "{0} fue agregado a la biblioteca", + "Inherit": "Heredar", + "HomeVideos": "Videos caseros", + "HeaderRecordingGroups": "Grupos de grabación", + "HeaderCameraUploads": "Subidas desde la cámara", + "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", + "DeviceOnlineWithName": "{0} está conectado", + "DeviceOfflineWithName": "{0} se ha desconectado", + "ChapterNameValue": "Capítulo {0}", + "CameraImageUploadedFrom": "Una nueva imagen de cámara ha sido subida desde {0}", + "AuthenticationSucceededWithUserName": "{0} autenticado con éxito", + "Application": "Aplicación", + "AppDeviceValues": "App: {0}, Dispositivo: {1}" +} From 1b90798b90fc4e5f7fe8d813c34c87f519924abc Mon Sep 17 00:00:00 2001 From: Mister Rajoy Date: Sun, 31 May 2020 20:23:23 +0200 Subject: [PATCH 65/72] Order ids to merge to avoid stack overflow As said in https://github.com/jellyfin/jellyfin/issues/3176, merging 1 with 2 and then 2 with 1 cause an stack overflow. sorting ids first fix the problem --- MediaBrowser.Api/VideosService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index b11fd48d3c..e03542d372 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -125,10 +125,12 @@ namespace MediaBrowser.Api public void Post(MergeVersions request) { + var items = request.Ids.Split(',') .Select(i => _libraryManager.GetItemById(i)) .OfType