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 f95b8ce7d..ab5e56ab0 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 f407317ec..5062683a1 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 de83b023d..1e42dbf67 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 adb8e793d..e8837892c 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 50ba0f8fa..fa566d24b 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 70d5bd9f4..4f12ad046 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 7b17cc913..614ab5669 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 7ebb043d8..285a59a24 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 b64fe8634..16c659532 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 9b1510ac9..021bc47cd 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 5b188d962..ca983764b 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 ecf58dbc0..f2e04d1fb 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 c897036eb..51563fd5d 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 36b03f09c..10726e2aa 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 f03f5efd8..3e4198aae 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 cfec39b4e..1b8f41db6 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 80f01b66e..865d07b2c 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 bb203f895..fcc90a1f7 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 8ab268a64..5ddf1e7e6 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 c4e97ffe5..496102d83 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 bfb34db55..f2432aaeb 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 88fc94a6f..d11260039 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 cc2541f74..cdd322c94 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 648568fd7..0c0e01f11 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 4342ccd8a..4229a4335 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 625054b9e..e7dc3da3c 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 c2b47eb9b..db8cd1875 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 53063810b..0c197ee02 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 3107ec242..333805e31 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 a475c9910..289047d6b 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 d6c1295f4..c48a38192 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 656c04f46..d8b7d848a 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 c493760d5..868956df2 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 d2563d1d0..0cccf931c 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 02570650e..327b5836f 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 63a8dc2aa..c3158b2f2 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 514d1e737..c067189a6 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 57082acc5..2d15aed7e 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 40081b282..fc555c5f7 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 756e500dd..cc5b840c7 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 7423efaf6..f3aaef930 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 e6691c513..1d18da6a0 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 a20f11503..b055ad41a 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 f1699d930..85cc9e3c1 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 0cefbbe01..704d4ec37 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 347583965..74c32c523 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 a5947bbf4..6f4b4ab1b 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 f23a24084..17c4dffcc 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 7e35cc85b..d9bd094d9 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 4cd318abb..10e9179c0 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 2021038d8..f8b5dee81 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 c26eeec77..30c44fbe0 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 8235b72d1..102db3b44 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 5ea0252cb..774592abc 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 c264cb936..48f53f06c 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 394fb9af9..94f5bd3db 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 58755b171..a18ad36c5 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 c9fe679e1..244463803 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 6a8f655ac..f565fb025 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 02b3a198c..2f01836bd 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 570ee7baa..f05e31047 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 3dc1fca36..d71013f01 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 5b12fff1c..4194f17c6 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 31603a754..3a8939a79 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 0be30b0ba..1512c5233 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 607355d8d..55393d32c 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 5b7eefd70..b080f3e4a 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 8c9798c5c..c754d276c 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 d2e43634d..06cc3e73c 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 57942ac23..1e9b47267 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 4e672a007..158e622a8 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 525576d61..000000000 --- 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 29613adbf..74c2cb4f4 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 21d8a31f2..1d840a300 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 1b4800863..efb2c157c 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 74040c2cb..e71ff3c21 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 bc97dd6f1..107f41540 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 d36706c38..40222c9dc 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 92f06c973..adb2cd2ab 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 bea7ec1db..45554c3dc 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 2cd8bd306..0e5db01dd 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 c117efde9..1310f68ae 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 b98c00240..6dd6653dc 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 167be18c9..34e3eabc9 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 e7e8d7cec..d68f37c9c 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 e44143755..74f982437 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 a034de8ba..1ebbc3323 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 4b37bd64a..17b2868a3 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 cd387bd54..e089dd1e5 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 dd30c9c84..2de02e403 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 1ef0b25c9..44f60f811 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 90ce6f2e5..b893a3509 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 f819a295c..8ffa3c4ba 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 72362f4f3..6f6979316 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 f415840b0..6af4a872c 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 613bfca69..baefeb39c 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 00caf5e11..81f47d978 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 a197f0fbe..36ff5d041 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 4b9102392..b23119d08 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 53f23a8e0..bba69d4b4 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 8b6af019d..299bb0a21 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 72ec673ee..ea65d976a 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 83fdb5fd6..0d257395a 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 e348cd725..af5ba5b17 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 a538efd25..8a49b6863 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 064ce6520..45970cf6b 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 a224d73b7..b1cc8cfdf 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 8154fbd0e..d1a94d8b3 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 85b77af24..9767509d0 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 dc8e0f91b..69c43efd4 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 09e900643..856f638c5 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 c75092b79..264982930 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 e30dd84dc..90422d19c 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 bb553a576..bda46dd2b 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 a1fbc5177..19039d448 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 1ef6dd67e..367c45b9d 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 0fdfe5761..b24409fcc 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 dcb6fa270..8b17757b8 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 29ba10dbb..83f982a5c 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 52348f802..ea5d4e7d7 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 45b8fcce9..f017c1a11 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 ad174f15d..97b979935 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 a2f163422..82e13e0eb 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 440818c3e..273350182 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 5b0ccb28a..72bb3d9c6 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 f5ac3d169..034734a9e 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 2bfbfcb20..5b6ed92df 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 68bcc590c..cfac4d1e2 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 744c6ec14..a40cf73e4 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 141ae1608..54139fe9c 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 7575224d4..962b81b95 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 4fb724515..144949a3b 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 79a128e9b..7b299b1aa 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 ffcfab24f..febc2bc09 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 bfa163b40..402fbe81a 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 b7003c4c8..ef435b21e 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 4f2067b98..f3a1518ed 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 324a38e70..000000000 --- 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 9ff9ea457..c13f1a89f 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 eb6a1527d..ca72e19ee 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 2b481ad7e..f2e6d8ef3 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 d4f4fa840..9467a2b00 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 a22ec3c07..c63a2ceda 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 ee2b9d8fd..78ab6c706 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 2873c1003..b7fad87ab 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 5ca00f770..e6067ee6e 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 161e04821..c96eb0b59 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 06f29df3f..d9f7a852c 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 9e6049246..c07379570 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 fca93d176..ee25be4b6 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 a264c6178..6b503ba6b 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 6fb4df676..13b1a0dcb 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 f113cf380..000000000 --- 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 15b60ad84..edf71c1a7 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 84e29e76a..7954ef4b4 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 93de0a8cd..1c8875890 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 1543aea16..0df86cb22 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 8d879c174..e04208f76 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 266f1c7e6..42586243d 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 bae954d78..5afedeeaf 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 123d0fad2..ed1aa7ac6 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 6e52314fa..c7a721df6 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 3c4fbec9e..92ba4139e 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 8a018312e..4470f1ad9 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 6223bb559..09b6ff9b5 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 1edd98fad..16d126ac7 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 8e50836f4..7c23eee44 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 3d2e9c0dc..332ba113c 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 4dccd2d68..3ea65195c 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 b153f15ec..abc581d8e 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 3f4edced6..f413f1e17 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 19e9e53e7..d07ff1548 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 197ba05e5..162576aa7 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 f485d680e..1c997d584 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 5da4998e8..51db66d21 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 980e1f88b..9794bd292 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 473a7bccc..09abfbb3f 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 bdb2b2439..62b68b49e 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 5687ba84b..6b4cfe4f0 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 f8cfacc20..b0827ac99 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 0f9956873..0f10605ea 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 493a8063a..ba2c024b7 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 42a56b92b..4d6f35efc 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 8f4e688f0..d6dc83413 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 0872eb4b1..0fd24edcc 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 30bf27f38..3cc9ff726 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 20a0c8cc7..9e6bbbc00 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 a2b701664..aec910c92 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 1775470b5..b6196a43f 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 cfa7684c9..7582cb748 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 534ad19ec..b2cbe737d 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 ed160e176..bf87088e4 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 4dd1bb5d0..c79d7fe75 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 ca0743cca..9063903ae 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 cc6c2b62b..48950667e 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 5144c035a..77100dfe7 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 c6f92e7ed..31001aeb2 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 699e0ea3a..5aeaffc2b 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 be1b08223..9c59a7c88 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 42c2105f5..4651a4169 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 abbe91eff..b5a5068e7 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 3eef965dd..9ef67966b 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 368c642e8..6bb13d4c9 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 ab868cad4..7e4553bac 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 f6bb6451b..36b8e6ee5 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 ae2b3fd4e..9f85022ef 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 6ef0e44a2..48e1c94ad 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 7125f34c5..bf3677850 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 ea5d4e7d7..cce508809 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 c63a2ceda..d1a7c2506 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 d1a7c2506..19af81c85 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 133a35527..55fc463d0 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 b7c15a840..000000000 --- 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 4d906a1bf..8419014c2 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 c93c02c48..b1519b572 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 000000000..4a20ceba0 --- /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 000000000..6bfd6aa9b --- /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 000000000..f17cca9ee --- /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 771027103..4bfc0c73f 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 000000000..d37e8563b --- /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 000000000..7630428d7 --- /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 000000000..c9465b27a --- /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 000000000..ec91ea69d --- /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 000000000..599c0dbfc --- /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 000000000..769316e80 --- /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 000000000..87b9ad66d --- /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 000000000..c5c2f3540 --- /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 000000000..c7c5f534d --- /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 000000000..7dba74ae9 --- /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 000000000..44d7a0af2 --- /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 6bfd6aa9b..7583793bb 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 4a20ceba0..b156e5a87 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 7583793bb..f76d243d5 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 d37e8563b..42e85ef86 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 c9465b27a..d35ae3101 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 ec91ea69d..09920a19f 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 f76d243d5..f6311d098 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 000000000..cddf68c42 --- /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 ae2b3fd4e..cf576c358 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 f6311d098..743933810 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 743933810..5c44326f5 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 b1519b572..6a64209c1 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 b156e5a87..fb37b2fb6 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 5c44326f5..60d70e5fd 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 f17cca9ee..0f9d1b733 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 4bfc0c73f..39c065b89 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 d35ae3101..5b08eac0a 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 09920a19f..d0cf8fa9c 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 c5c2f3540..cc49e92a9 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 c7c5f534d..ceb778b36 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 000000000..8d8a2646a --- /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 7dba74ae9..cae769db0 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 44d7a0af2..da770736c 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 769316e80..d9f391403 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 87b9ad66d..02e4774d0 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 0f9d1b733..c273e6c38 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 000000000..049684d94 --- /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 000000000..f7887dc33 --- /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 fb37b2fb6..83b477944 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 c273e6c38..af220ed81 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 049684d94..a69e0e293 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 ceb778b36..0ef8b2785 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 da770736c..3d99b2718 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 2292d86a4..171047e65 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 930f2d35d..d8e02ef39 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 31eb7ccb7..fb766ab57 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 171047e65..c819c163a 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 d8e02ef39..b0c6d0aa0 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 83b477944..02cf08cd7 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 60d70e5fd..e7df8925e 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 af220ed81..2eaf9ce83 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 a69e0e293..897413015 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 42e85ef86..8e886a2cb 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 d0cf8fa9c..433d6d8bc 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 599c0dbfc..50ad70630 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 3d99b2718..b3d49d09e 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 e7df8925e..5aefd1fd9 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 0ef8b2785..20e76932d 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 b0c6d0aa0..7a316b070 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 5aefd1fd9..eb61da7f3 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 897413015..930968d9f 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 50ba0f8fa..6929c81f9 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 16b68170b..a3a047057 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 c91d137a7..57228d208 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 6e15d058f..135ba9d7f 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 a2d75d0b8..1b343790e 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 c819c163a..4c33ff71b 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 7a316b070..d1ee22ea8 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 02cf08cd7..8cc3d1fac 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 eb61da7f3..7074e2225 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 2eaf9ce83..4b6e16762 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 930968d9f..9a26ffd99 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 8e886a2cb..c01fead83 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 5b08eac0a..34eae4062 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 433d6d8bc..fbc208d27 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 20e76932d..9f40f9577 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 cae769db0..ba97641f6 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 8419014c2..730323c22 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 6a64209c1..aab745de4 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 8cc3d1fac..9c9758de1 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 7074e2225..d3197d97b 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 4b6e16762..bcdc833e4 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 9a26ffd99..4a9307e62 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 39c065b89..4c2f834cb 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 c01fead83..087748de0 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 7630428d7..a3975c334 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 34eae4062..de1fcd259 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 fbc208d27..6c962ec85 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 cddf68c42..d891a8167 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 50ad70630..7b833506b 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 cc49e92a9..895702f3d 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 9f40f9577..89d245787 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 8d8a2646a..d67b6bd55 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 ba97641f6..9de23194e 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 b3d49d09e..f1e175fde 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 d9f391403..0f06e381f 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 02e4774d0..113719871 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 f7887dc33..0a6036154 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 cf576c358..3e027e831 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 d1ee22ea8..3704445ab 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 9c9758de1..c7bd242a7 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 d3197d97b..93cec1304 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 bcdc833e4..9137faf9f 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 fb766ab57..b371a59e9 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 087748de0..bda49bd1b 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 6c962ec85..006fb687b 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 7b833506b..f28ecf16d 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 9137faf9f..8f552ef89 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 a5293b41c..3af18f681 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 8f552ef89..8064ea7dc 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 0b3bbe29e..1237b603b 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 5af7f1622..fa89b4c63 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 ca5cd6fdd..5a43a138b 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 c4b65d265..a34a3a192 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 0b86b2db7..bc35b0410 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 a6e1f490a..9f0e3a004 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 5c7462ee2..1d7307863 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 2e9b3e6cb..ecfe2ed76 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 88e67b648..1d3f0d3b4 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 36c746624..be5906cbc 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 7ed8fa767..e5f6ea09d 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 fc46dec2e..ba0297107 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 607355d8d..8c6c9683a 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 57942ac23..39bdc09ed 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 bc35b0410..0a97e007c 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 2db6d717a..808627b41 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 2e1c97f67..559a5415f 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 0a97e007c..c31fd5bf9 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 ecfe2ed76..09b99781b 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 ac517e10c..7475617c9 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 c31fd5bf9..e1cb282cc 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 9f0e3a004..221346b68 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 1d7307863..d2da0cf17 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 5a43a138b..10eb96b10 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 e1cb282cc..c48664a31 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 09b99781b..559db550b 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 808627b41..2db6d717a 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 559a5415f..81160efec 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 a34a3a192..07105786b 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 c48664a31..9f412b725 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 d2da0cf17..99091ea57 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 fa89b4c63..54de7212a 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 be5906cbc..e38eaf760 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 a3a047057..5d22bfddc 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 57228d208..1ae3888dc 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 135ba9d7f..cc250b06e 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 5d22bfddc..acae702f3 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 99091ea57..7ab0a54df 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 e5f6ea09d..035ab1dd9 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 a468e0c35..29040f92e 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 9f412b725..75350ac2a 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 1237b603b..35da6f635 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 10eb96b10..dd60dd222 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 07105786b..593e7be7d 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 75350ac2a..579fb7edd 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 559db550b..d0846bfc3 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 e38eaf760..8800fdf99 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 035ab1dd9..07aeb69db 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 ba0297107..12f5db2e0 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 8c6c9683a..df84dcf12 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 1ae3888dc..a26f714b1 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 39beaa479..59b8a8c98 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 34494644d..fbf413f2b 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 1f02461da..90196b046 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 7475617c9..2289fd991 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 579fb7edd..e63776bff 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 1d3f0d3b4..4baec6204 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 df84dcf12..6213206ff 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 d0846bfc3..89fe72635 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 39bdc09ed..664ea332e 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 9f6251270..3d94fa7dc 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 64cd308a2..66805b7c8 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 c5d60b2a0..bcab4adba 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 6abc3a82c..c7431d143 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 9d7c0d365..7403a2a16 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 10c1f321b..613d332b2 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 8e3236407..9bc2b62ec 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 41c0c5115..997571a91 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 e1dbb663b..dea85d299 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 2e9ecc4ae..cffae7b1c 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 3efe1ee25..5a5dc3329 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 bc86cc59a..70dd8f321 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 1b10f2d27..a3dd45a53 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 ee5131c1f..5554aa97f 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 2e9b3e6cb..eaff22fff 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 0e74c9267..175984575 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 1162bff13..5be656bdb 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 293cf5ea5..f44cf1452 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 6691080bc..c7485a2e9 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 f796aa486..24297d500 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 061316cb8..50d34cca9 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 d1c53c1c1..aefb3f019 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 d5d2f58c0..c244b0033 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 61a330675..2d2f73148 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 1127a08de..7cd783595 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 648568fd7..5880730fd 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 593e7be7d..77a734ebf 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 6213206ff..734bcaf81 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 7ab0a54df..ccd501214 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 1f5a7d177..0680c5ffe 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 3af18f681..e7b4b0ec3 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 c7bd242a7..d430d4d16 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 93cec1304..1f76dd4e3 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 8064ea7dc..1e14ea552 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 bda49bd1b..28a3ac505 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 b8feb5535..d63bc6bda 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 7d4d5fcf9..78fc6c694 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 ec6cb35eb..be7b4ce59 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 b6bfaf2e9..000000000 --- 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 5554aa97f..7488b1938 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 7488b1938..6ca58b1c3 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 061316cb8..b8ea9c6da 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 e63776bff..903c0b3cf 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 77a734ebf..38c4f940d 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 734bcaf81..fc1cb01db 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 903c0b3cf..84bcd1bc1 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 81160efec..916e4fda7 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 ccd501214..85461ca4e 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 150952d8b..d4009e92b 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 221346b68..d3fdb5de4 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 85461ca4e..7f0da2c9e 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 84bcd1bc1..6cbca7d02 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 6cbca7d02..bb3e3dd11 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 07aeb69db..f4b71d8bf 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 dd60dd222..608801ac3 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 d4009e92b..47ebe1254 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 138832fb8..04fe0bacb 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 ca5cd6fdd..58702541e 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 b69a126b3..279ec3098 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 6929c81f9..5bc1a81aa 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 2e9ecc4ae..dd7f753cc 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 acae702f3..0b9f80538 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 52c8facc3..02f150607 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 0b86b2db7..67a72d313 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 1b10f2d27..3e48425a2 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 1056a33b9..8e7d60a15 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 677d68b4c..7b7575707 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 e42ff8496..f347540c7 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 211ca6784..848f82d85 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 b3e88b667..d1a28e7a1 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 cd9f7946e..889760586 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 bb56d9771..f8a2d9741 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 9b1510ac9..d4d1c1ff7 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 6eda2b503..d192be921 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 5b188d962..dc3e9a607 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 6ffa581a9..907680239 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 ea6a70615..fae049914 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 9df7c538b..a6c13eaef 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 3140aa489..402b39a26 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 1d133dcda..0d36b82c0 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 63f867bf6..c384cf4bb 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 6a1afced7..9d9d77538 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 74cb01444..e470adcf4 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 ea278de0d..c7819d4c0 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 3a34da3af..74cd4ef1e 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 08ff4f55f..e171a9e9f 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 2a6a7b13c..ad0b57af6 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 4e4029f06..750890ec8 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 bcc814daf..5ec3a735a 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 095193828..8ba86f756 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 2563cac99..1f9c7fc22 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 a566b18dd..f2b1d06f3 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 e24a95dbb..ad6015c1c 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 9f5f97028..606f2a240 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 934560de3..7f44357e1 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 5018bf4a2..59ee5908f 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 27c4dcba0..278379a92 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 56e23d549..ab22fe019 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 5177251c3..16142a70d 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 483c63ade..e3b6aa197 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 2b09a93ef..5c480e842 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 120ac50d9..000000000 --- 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 7479d8104..000000000 --- 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 ee5131c1f..146c84d7c 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 16507466f..67e31f7f6 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 87d3ae2d6..980954ba0 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 623675157..5c1503ed2 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 66de05a6a..aba14c6ca 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 dfaa144cd..a35192eff 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 da3f3dd25..39d9bc68e 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 d99d0eff2..478df4035 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 7afbd9ff7..76bb798b5 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 504b6d283..b9205ee07 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 aa040fa15..558a3d351 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 c9ac765c1..5766dc542 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 d430d4d16..d0812a13f 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 1f76dd4e3..129262e53 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 4c2f24e6f..383615f74 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 0b2309889..4f6a84ef7 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 a3f3f6cb4..7b7d66ca6 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 78ac95f85..e7888595f 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 4655cd928..58a023638 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 b79fdeac0..6b5780a26 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 89514c89b..b15e09290 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 b9895386f..7c693f8c3 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 000000000..0967ef424 --- /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 0967ef424..b0fdc8386 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 b11fd48d3..e03542d37 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