aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs4
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs4
-rw-r--r--Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs2
-rw-r--r--Emby.Server.Implementations/Cryptography/CryptographyProvider.cs4
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs34
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs8
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs94
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj1
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs4
-rw-r--r--Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs18
-rw-r--r--Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs40
-rw-r--r--Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs12
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs13
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs40
-rw-r--r--Emby.Server.Implementations/IStartupOptions.cs9
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs38
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs102
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs6
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs35
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs13
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs79
-rw-r--r--Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_419.json117
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/ta.json99
-rw-r--r--Emby.Server.Implementations/MediaEncoder/EncodingManager.cs2
-rw-r--r--Emby.Server.Implementations/Net/SocketFactory.cs2
-rw-r--r--Emby.Server.Implementations/Net/UdpSocket.cs2
-rw-r--r--Emby.Server.Implementations/Networking/NetworkManager.cs2
-rw-r--r--Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs2
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs6
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs7
-rw-r--r--Emby.Server.Implementations/ResourceFileManager.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs22
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/TaskManager.cs15
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs7
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs7
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs9
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs7
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs10
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs17
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs7
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs6
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs2
-rw-r--r--Emby.Server.Implementations/Security/AuthenticationRepository.cs2
-rw-r--r--Emby.Server.Implementations/Serialization/JsonSerializer.cs5
-rw-r--r--Emby.Server.Implementations/Services/HttpResult.cs2
-rw-r--r--Emby.Server.Implementations/Services/RequestHelper.cs7
-rw-r--r--Emby.Server.Implementations/Services/ResponseHelper.cs5
-rw-r--r--Emby.Server.Implementations/Services/ServiceController.cs2
-rw-r--r--Emby.Server.Implementations/Services/ServiceExec.cs2
-rw-r--r--Emby.Server.Implementations/Services/ServiceHandler.cs2
-rw-r--r--Emby.Server.Implementations/Services/ServiceMethod.cs2
-rw-r--r--Emby.Server.Implementations/Services/ServicePath.cs2
-rw-r--r--Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs2
-rw-r--r--Emby.Server.Implementations/Services/SwaggerService.cs2
-rw-r--r--Emby.Server.Implementations/Services/UrlExtensions.cs2
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs19
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs245
-rw-r--r--Emby.Server.Implementations/SocketSharp/HttpFile.cs18
-rw-r--r--Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs198
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs4
-rw-r--r--Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs18
-rw-r--r--Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs18
-rw-r--r--Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs38
-rw-r--r--Emby.Server.Implementations/Sorting/IsFolderComparer.cs14
-rw-r--r--Emby.Server.Implementations/Sorting/IsPlayedComparer.cs38
-rw-r--r--Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs38
-rw-r--r--Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs14
-rw-r--r--Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs14
-rw-r--r--Emby.Server.Implementations/Sorting/StartDateComparer.cs19
-rw-r--r--Emby.Server.Implementations/Sorting/StudioComparer.cs2
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayController.cs522
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs405
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs7
-rw-r--r--Emby.Server.Implementations/Udp/UdpServer.cs16
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs4
-rw-r--r--Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs2
-rw-r--r--Emby.Server.Implementations/UserViews/DynamicImageProvider.cs6
-rw-r--r--Emby.Server.Implementations/UserViews/FolderImageProvider.cs6
85 files changed, 2023 insertions, 609 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index e6410f857..be4e05a64 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -45,6 +45,7 @@ using Emby.Server.Implementations.Services;
using Emby.Server.Implementations.Session;
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;
@@ -78,6 +79,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.Configuration;
@@ -613,6 +615,8 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
+ serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
+
serviceCollection.AddSingleton<LiveTvDtoService>();
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
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<string, Tuple<DateTime, List<MediaSourceInfo>>>();
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
-
+
/// <summary>
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
/// </summary>
/// <param name="userManager">The user manager.</param>
/// <param name="dtoService">The dto service.</param>
/// <param name="libraryManager">The library manager.</param>
- /// <param name="loggerFactory">The logger factory.</param>
+ /// <param name="logger">The logger.</param>
/// <param name="config">The server configuration manager.</param>
/// <param name="fileSystem">The filesystem.</param>
/// <param name="userDataManager">The user data manager.</param>
diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
index 305e67e8c..94ee1ced7 100644
--- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Configuration
ValidateMetadataPath(newConfig);
ValidateSslCertificate(newConfig);
- ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration> { Argument = newConfig });
+ ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration>(newConfig));
base.ReplaceConfiguration(newConfiguration);
}
diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
index a037415a9..fd302d136 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;
@@ -129,8 +131,6 @@ namespace Emby.Server.Implementations.Cryptography
_randomNumberGenerator.Dispose();
}
- _randomNumberGenerator = null;
-
_disposed = true;
}
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index ca5cd6fdd..18d235c87 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
{
/// <summary>
- /// Class SQLiteItemRepository
+ /// Class SQLiteItemRepository.
/// </summary>
public class SqliteItemRepository : BaseSqliteRepository, IItemRepository
{
@@ -1141,24 +1143,24 @@ namespace Emby.Server.Implementations.Data
public string ToValueString(ItemImageInfo image)
{
- var delimeter = "*";
-
- var path = image.Path;
+ const string Delimeter = "*";
- if (path == null)
- {
- path = string.Empty;
- }
+ 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 +
- image.Height.ToString(CultureInfo.InvariantCulture);
+ Delimeter +
+ 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 +1194,11 @@ namespace Emby.Server.Implementations.Data
image.Width = width;
image.Height = height;
}
+
+ if (parts.Length >= 6)
+ {
+ image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|');
+ }
}
return image;
@@ -1971,6 +1978,7 @@ namespace Emby.Server.Implementations.Data
/// Gets the chapter.
/// </summary>
/// <param name="reader">The reader.</param>
+ /// <param name="item">The item.</param>
/// <returns>ChapterInfo.</returns>
private ChapterInfo GetChapter(IReadOnlyList<IResultSetValue> reader, BaseItem item)
{
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index 2283f2433..e39e0aa78 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -62,13 +62,7 @@ namespace Emby.Server.Implementations.Devices
{
_authRepo.UpdateDeviceOptions(deviceId, options);
- if (DeviceOptionsUpdated != null)
- {
- DeviceOptionsUpdated(this, new GenericEventArgs<Tuple<string, DeviceOptions>>()
- {
- Argument = new Tuple<string, DeviceOptions>(deviceId, options)
- });
- }
+ DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options)));
}
public DeviceOptions GetDeviceOptions(string deviceId)
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index c4b65d265..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<ImageType, Dictionary<string, string>>();
+ }
+
+ if (!dto.ImageBlurHashes.ContainsKey(image.Type))
+ {
+ dto.ImageBlurHashes[image.Type] = new Dictionary<string, string>();
+ }
+
+ 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<ItemImageInfo> images)
+ {
+ var tags = GetImageTags(item, images);
+ var hashes = new Dictionary<string, string>();
+ 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<ImageType, Dictionary<string, string>>();
+ }
+
+ dto.ImageBlurHashes[imageType] = hashes;
+ }
+
+ return tags;
+ }
+
/// <summary>
/// Sets simple property values on a DTOBaseItem
/// </summary>
@@ -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<ImageType, Dictionary<string, string>>();
+
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);
}
}
@@ -721,12 +787,11 @@ 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)
{
- var tag = GetImageCacheTag(item, image);
+ var tag = GetTagAndFillBlurhash(dto, item, image);
if (tag != null)
{
@@ -871,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))
@@ -1099,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);
}
}
@@ -1145,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);
}
}
}
@@ -1275,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)
@@ -1285,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))
@@ -1295,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)))
@@ -1305,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/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 @@
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers-->
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
/// <param name="foldersAddedTo">The folders added to.</param>
/// <param name="foldersRemovedFrom">The folders removed from.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- private async void SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
+ private async Task SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> 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<TimerEventInfo> e)
+ private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
- SendMessage("SeriesTimerCreated", e.Argument);
+ await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false);
}
- private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+ private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
- SendMessage("TimerCreated", e.Argument);
+ await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false);
}
- private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+ private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
- SendMessage("SeriesTimerCancelled", e.Argument);
+ await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false);
}
- private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+ private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> 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 b323a0a95..eb43a90f2 100644
--- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs
@@ -86,29 +86,29 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask;
}
- private void OnPackageInstalling(object sender, InstallationInfo e)
+ private async void OnPackageInstalling(object sender, InstallationInfo e)
{
SendMessageToAdminSessions("PackageInstalling", e);
}
- private void OnPackageInstallationCancelled(object sender, InstallationInfo e)
+ private async void OnPackageInstallationCancelled(object sender, InstallationInfo e)
{
SendMessageToAdminSessions("PackageInstallationCancelled", e);
}
- private void OnPackageInstallationCompleted(object sender, InstallationInfo e)
+ private async void OnPackageInstallationCompleted(object sender, InstallationInfo e)
{
SendMessageToAdminSessions("PackageInstallationCompleted", e);
}
- 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);
}
/// <summary>
@@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
- private void OnPluginUninstalled(object sender, IPlugin e)
+ private async void OnPluginUninstalled(object sender, IPlugin e)
{
SendMessageToAdminSessions("PluginUninstalled", e);
}
@@ -126,9 +126,9 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
- 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);
}
/// <summary>
@@ -136,11 +136,11 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
- private void OnUserUpdated(object sender, GenericEventArgs<User> e)
+ private async void OnUserUpdated(object sender, GenericEventArgs<User> e)
{
var dto = _userManager.GetUserDto(e.Argument);
- SendMessageToUserSession(e.Argument, "UserUpdated", dto);
+ await SendMessageToUserSession(e.Argument, "UserUpdated", dto).ConfigureAwait(false);
}
/// <summary>
@@ -148,26 +148,26 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
- private void OnUserDeleted(object sender, GenericEventArgs<User> e)
+ private async void OnUserDeleted(object sender, GenericEventArgs<User> 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<User> e)
+ private async void OnUserPolicyUpdated(object sender, GenericEventArgs<User> 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<User> e)
+ private async void OnUserConfigurationUpdated(object sender, GenericEventArgs<User> e)
{
var dto = _userManager.GetUserDto(e.Argument);
- SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto);
+ await SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto).ConfigureAwait(false);
}
- private async void SendMessageToAdminSessions<T>(string name, T data)
+ private async Task SendMessageToAdminSessions<T>(string name, T data)
{
try
{
@@ -179,7 +179,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
- private async void SendMessageToUserSession<T>(User user, string name, T data)
+ private async Task SendMessageToUserSession<T>(User user, string name, T data)
{
try
{
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index 50ba0f8fa..5bc1a81aa 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
/// </summary>
private readonly ILogger _logger;
private readonly IServerApplicationHost _appHost;
+ private readonly IConfiguration _config;
/// <summary>
/// The UDP server.
@@ -35,19 +37,21 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
public UdpServerEntryPoint(
ILogger<UdpServerEntryPoint> logger,
- IServerApplicationHost appHost)
+ IServerApplicationHost appHost,
+ IConfiguration configuration)
{
_logger = logger;
_appHost = appHost;
-
+ _config = configuration;
}
/// <inheritdoc />
- public async Task RunAsync()
+ public Task RunAsync()
{
- _udpServer = new UdpServer(_logger, _appHost);
+ _udpServer = new UdpServer(_logger, _appHost, _config);
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
+ return Task.CompletedTask;
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index 2e9ecc4ae..9d5969583 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -56,6 +56,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Gets the result.
/// </summary>
+ /// <param name="requestContext">The request context.</param>
/// <param name="content">The content.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="responseHeaders">The response headers.</param>
@@ -255,16 +256,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/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index 095725c50..0680c5ffe 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -78,6 +78,9 @@ namespace Emby.Server.Implementations.HttpServer
/// <value>The last activity date.</value>
public DateTime LastActivityDate { get; private set; }
+ /// <inheritdoc />
+ public DateTime LastKeepAliveDate { get; set; }
+
/// <summary>
/// Gets or sets the query string.
/// </summary>
@@ -218,7 +221,42 @@ namespace Emby.Server.Implementations.HttpServer
Connection = this
};
- await OnReceive(info).ConfigureAwait(false);
+ if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
+ {
+ await SendKeepAliveResponse();
+ }
+ else
+ {
+ await OnReceive(info).ConfigureAwait(false);
+ }
+ }
+
+ private Task SendKeepAliveResponse()
+ {
+ LastKeepAliveDate = DateTime.UtcNow;
+ return SendAsync(new WebSocketMessage<string>
+ {
+ MessageType = "KeepAlive"
+ }, CancellationToken.None);
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ _socket.Dispose();
+ }
}
}
}
diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs
index 16b68170b..0b9f80538 100644
--- a/Emby.Server.Implementations/IStartupOptions.cs
+++ b/Emby.Server.Implementations/IStartupOptions.cs
@@ -1,3 +1,7 @@
+#pragma warning disable CS1591
+
+using System;
+
namespace Emby.Server.Implementations
{
public interface IStartupOptions
@@ -36,5 +40,10 @@ namespace Emby.Server.Implementations
/// Gets the value of the --plugin-manifest-url command line option.
/// </summary>
string PluginManifestUrl { get; }
+
+ /// <summary>
+ /// Gets the value of the --published-server-url command line option.
+ /// </summary>
+ Uri PublishedServerUrl { get; }
}
}
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);
}
-
- /// <summary>
- /// Gets the hashed string.
- /// </summary>
- 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<byte> 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..677030b82 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;
@@ -50,7 +52,7 @@ using VideoResolver = Emby.Naming.Video.VideoResolver;
namespace Emby.Server.Implementations.Library
{
/// <summary>
- /// Class LibraryManager
+ /// Class LibraryManager.
/// </summary>
public class LibraryManager : ILibraryManager
{
@@ -67,6 +69,7 @@ namespace Emby.Server.Implementations.Library
private readonly IFileSystem _fileSystem;
private readonly IItemRepository _itemRepository;
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
+ private readonly IImageProcessor _imageProcessor;
private NamingOptions _namingOptions;
private string[] _videoFileExtensions;
@@ -135,6 +138,13 @@ namespace Emby.Server.Implementations.Library
/// <param name="userManager">The user manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="userDataRepository">The user data repository.</param>
+ /// <param name="libraryMonitorFactory">The library monitor.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="providerManagerFactory">The provider manager.</param>
+ /// <param name="userviewManagerFactory">The userview manager.</param>
+ /// <param name="mediaEncoder">The media encoder.</param>
+ /// <param name="itemRepository">The item repository.</param>
+ /// <param name="imageProcessor">The image processor.</param>
public LibraryManager(
IServerApplicationHost appHost,
ILogger<LibraryManager> logger,
@@ -147,7 +157,8 @@ namespace Emby.Server.Implementations.Library
Lazy<IProviderManager> providerManagerFactory,
Lazy<IUserViewManager> userviewManagerFactory,
IMediaEncoder mediaEncoder,
- IItemRepository itemRepository)
+ IItemRepository itemRepository,
+ IImageProcessor imageProcessor)
{
_appHost = appHost;
_logger = logger;
@@ -161,6 +172,7 @@ namespace Emby.Server.Implementations.Library
_userviewManagerFactory = userviewManagerFactory;
_mediaEncoder = mediaEncoder;
_itemRepository = itemRepository;
+ _imageProcessor = imageProcessor;
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
@@ -1815,10 +1827,90 @@ namespace Emby.Server.Implementations.Library
}
}
- public void UpdateImages(BaseItem item)
+ private bool ImageNeedsRefresh(ItemImageInfo image)
{
- _itemRepository.SaveImages(item);
+ 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 image.Path != null && !image.IsLocalFile;
+ }
+
+ public void UpdateImages(BaseItem item, bool forceUpdate = false)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
+ if (outdated.Length == 0)
+ {
+ RegisterItem(item);
+ return;
+ }
+
+ foreach (var img in outdated)
+ {
+ 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
+ {
+ image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path);
+ image.BlurHash = string.Empty;
+ }
+
+ try
+ {
+ image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannot update DateModified for {0}", image.Path);
+ }
+ }
+
+ _itemRepository.SaveImages(item);
RegisterItem(item);
}
@@ -1839,7 +1931,7 @@ namespace Emby.Server.Implementations.Library
item.DateLastSaved = DateTime.UtcNow;
- RegisterItem(item);
+ UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
}
_itemRepository.SaveItems(itemsList, cancellationToken);
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 01fe98f3a..a5e5981b8 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<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse
- {
- MediaSource = clone
-
- }, liveStream as IDirectStreamProvider);
+ return new Tuple<LiveStreamResponse, IDirectStreamProvider>(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 b8feb5535..140155d0e 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="user">The user.</param>
private void OnUserUpdated(User user)
{
- UserUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
+ UserUpdated?.Invoke(this, new GenericEventArgs<User>(user));
}
/// <summary>
@@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="user">The user.</param>
private void OnUserDeleted(User user)
{
- UserDeleted?.Invoke(this, new GenericEventArgs<User> { Argument = user });
+ UserDeleted?.Invoke(this, new GenericEventArgs<User>(user));
}
public NameIdPair[] GetAuthenticationProviders()
@@ -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);
@@ -776,7 +751,7 @@ namespace Emby.Server.Implementations.Library
_userRepository.CreateUser(user);
- EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
+ EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User>(user), _logger);
return user;
}
@@ -1001,7 +976,7 @@ namespace Emby.Server.Implementations.Library
if (fireEvent)
{
- UserPolicyUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
+ UserPolicyUpdated?.Invoke(this, new GenericEventArgs<User>(user));
}
}
@@ -1071,7 +1046,7 @@ namespace Emby.Server.Implementations.Library
if (fireEvent)
{
- UserConfigurationUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
+ UserConfigurationUpdated?.Invoke(this, new GenericEventArgs<User>(user));
}
}
}
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/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<TimerInfo> { Argument = item });
+ TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo>(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<TimerInfo> { Argument = timer });
+ TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
}
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 1b10f2d27..dbd0e6f2e 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;
@@ -148,27 +146,18 @@ namespace Emby.Server.Implementations.LiveTv
{
var timerId = e.Argument;
- TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>
- {
- Argument = new TimerEventInfo
- {
- Id = timerId
- }
- });
+ TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(timerId)));
}
private void OnEmbyTvTimerCreated(object sender, GenericEventArgs<TimerInfo> e)
{
var timer = e.Argument;
- TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>
- {
- Argument = new TimerEventInfo
+ TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
+ new TimerEventInfo(timer.Id)
{
- ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId),
- Id = timer.Id
- }
- });
+ ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId)
+ }));
}
public List<NameIdPair> GetTunerHostTypes()
@@ -788,22 +777,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[]
- {
- (ItemSortBy.StartDate, SortOrder.Ascending)
- };
- }
- else
+
+ // 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)
- };
- }
+ (ItemSortBy.StartDate, SortOrder.Ascending)
+ };
}
RemoveFields(options);
@@ -1734,13 +1713,7 @@ namespace Emby.Server.Implementations.LiveTv
if (!(service is EmbyTV.EmbyTV))
{
- TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>
- {
- Argument = new TimerEventInfo
- {
- Id = id
- }
- });
+ TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
}
}
@@ -1757,13 +1730,7 @@ namespace Emby.Server.Implementations.LiveTv
await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
- SeriesTimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>
- {
- Argument = new TimerEventInfo
- {
- Id = id
- }
- });
+ SeriesTimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
}
public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
@@ -2082,14 +2049,11 @@ namespace Emby.Server.Implementations.LiveTv
if (!(service is EmbyTV.EmbyTV))
{
- TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>
- {
- Argument = new TimerEventInfo
+ TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
+ new TimerEventInfo(newTimerId)
{
- ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId),
- Id = newTimerId
- }
- });
+ ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId)
+ }));
}
}
@@ -2114,14 +2078,11 @@ namespace Emby.Server.Implementations.LiveTv
await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
}
- SeriesTimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>
- {
- Argument = new TimerEventInfo
+ SeriesTimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
+ 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/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/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json
new file mode 100644
index 000000000..b0fdc8386
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/es_419.json
@@ -0,0 +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}"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index 150952d8b..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 photographie a été chargée depuis {0}",
+ "CameraImageUploadedFrom": "Une 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",
diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json
new file mode 100644
index 000000000..f722dd8c0
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/ta.json
@@ -0,0 +1,99 @@
+{
+ "VersionNumber": "பதிப்பு {0}",
+ "ValueSpecialEpisodeName": "சிறப்பு - {0}",
+ "TasksMaintenanceCategory": "பராமரிப்பு",
+ "TaskCleanCache": "தற்காலிக சேமிப்பு கோப்பகத்தை சுத்தம் செய்யவும்",
+ "TaskRefreshChapterImages": "அத்தியாயப் படங்களை பிரித்தெடுக்கவும்",
+ "TaskRefreshPeople": "மக்களைப் புதுப்பிக்கவும்",
+ "TaskCleanTranscode": "டிரான்ஸ்கோட் கோப்பகத்தை சுத்தம் செய்யவும்",
+ "TaskRefreshChannelsDescription": "இணையச் சேனல் தகவல்களைப் புதுப்பிக்கிறது.",
+ "System": "ஒருங்கியம்",
+ "NotificationOptionTaskFailed": "திட்டமிடப்பட்ட பணி தோல்வியடைந்தது",
+ "NotificationOptionPluginUpdateInstalled": "உட்செருகி புதுப்பிக்கப்பட்டது",
+ "NotificationOptionPluginUninstalled": "உட்செருகி நீக்கப்பட்டது",
+ "NotificationOptionPluginInstalled": "உட்செருகி நிறுவப்பட்டது",
+ "NotificationOptionPluginError": "உட்செருகி செயலிழந்தது",
+ "NotificationOptionCameraImageUploaded": "புகைப்படம் பதிவேற்றப்பட்டது",
+ "MixedContent": "கலப்பு உள்ளடக்கங்கள்",
+ "MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன",
+ "MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது",
+ "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது",
+ "Inherit": "மரபரிமையாகப் பெறு",
+ "HeaderRecordingGroups": "பதிவு குழுக்கள்",
+ "HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்",
+ "Folders": "கோப்புறைகள்",
+ "FailedLoginAttemptWithUserName": "{0} இலிருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது",
+ "DeviceOnlineWithName": "{0} இணைக்கப்பட்டது",
+ "DeviceOfflineWithName": "{0} துண்டிக்கப்பட்டது",
+ "Collections": "தொகுப்புகள்",
+ "CameraImageUploadedFrom": "{0} இலிருந்து புதிய புகைப்படம் பதிவேற்றப்பட்டது",
+ "AppDeviceValues": "செயலி: {0}, சாதனம்: {1}",
+ "TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
+ "TaskRefreshChannels": "சேனல்களை புதுப்பி",
+ "TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி",
+ "TaskRefreshLibrary": "மீடியா நூலகத்தை ஆராய்",
+ "TasksChannelsCategory": "இணைய சேனல்கள்",
+ "TasksApplicationCategory": "செயலி",
+ "TasksLibraryCategory": "நூலகம்",
+ "UserPolicyUpdatedWithName": "பயனர் கொள்கை {0} இற்கு புதுப்பிக்கப்பட்டுள்ளது",
+ "UserPasswordChangedWithName": "{0} பயனருக்கு கடவுச்சொல் மாற்றப்பட்டுள்ளது",
+ "UserLockedOutWithName": "பயனர் {0} முடக்கப்பட்டார்",
+ "UserDownloadingItemWithValues": "{0} ஆல் {1} பதிவிறக்கப்படுகிறது",
+ "UserDeletedWithName": "பயனர் {0} நீக்கப்பட்டார்",
+ "UserCreatedWithName": "பயனர் {0} உருவாக்கப்பட்டார்",
+ "User": "பயனர்",
+ "TvShows": "தொலைக்காட்சித் தொடர்கள்",
+ "Sync": "ஒத்திசைவு",
+ "StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
+ "Songs": "பாட்டுகள்",
+ "Shows": "தொடர்கள்",
+ "ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
+ "ScheduledTaskStartedWithName": "{0} துவங்கியது",
+ "ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது",
+ "ProviderValue": "வழங்குநர்: {0}",
+ "PluginUpdatedWithName": "{0} புதுப்பிக்கப்பட்டது",
+ "PluginUninstalledWithName": "{0} நீக்கப்பட்டது",
+ "PluginInstalledWithName": "{0} நிறுவப்பட்டது",
+ "Plugin": "உட்செருகி",
+ "Playlists": "தொடர் பட்டியல்கள்",
+ "Photos": "புகைப்படங்கள்",
+ "NotificationOptionVideoPlaybackStopped": "நிகழ்பட ஒளிபரப்பு நிறுத்தப்பட்டது",
+ "NotificationOptionVideoPlayback": "நிகழ்பட ஒளிபரப்பு துவங்கியது",
+ "NotificationOptionUserLockedOut": "பயனர் கணக்கு முடக்கப்பட்டது",
+ "NotificationOptionServerRestartRequired": "சேவையக மறுதொடக்கம் தேவை",
+ "NotificationOptionNewLibraryContent": "புதிய உள்ளடக்கங்கள் சேர்க்கப்பட்டன",
+ "NotificationOptionInstallationFailed": "நிறுவல் தோல்வியடைந்தது",
+ "NotificationOptionAudioPlaybackStopped": "ஒலி இசைத்தல் நிறுத்தப்பட்டது",
+ "NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது",
+ "NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது",
+ "NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்",
+ "NameSeasonUnknown": "பருவம் அறியப்படாதவை",
+ "NameSeasonNumber": "பருவம் {0}",
+ "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது",
+ "MusicVideos": "இசைப்படங்கள்",
+ "Music": "இசை",
+ "Movies": "திரைப்படங்கள்",
+ "Latest": "புதியன",
+ "LabelRunningTimeValue": "ஓடும் நேரம்: {0}",
+ "LabelIpAddressValue": "ஐபி முகவரி: {0}",
+ "ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது",
+ "ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது",
+ "HeaderNextUp": "அடுத்ததாக",
+ "HeaderLiveTV": "நேரடித் தொலைக்காட்சி",
+ "HeaderFavoriteSongs": "பிடித்த பாட்டுகள்",
+ "HeaderFavoriteShows": "பிடித்த தொடர்கள்",
+ "HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்",
+ "HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
+ "HeaderFavoriteAlbums": "பிடித்த ஆல்பங்கள்",
+ "HeaderContinueWatching": "தொடர்ந்து பார்",
+ "HeaderAlbumArtists": "இசைக் கலைஞர்கள்",
+ "Genres": "வகைகள்",
+ "Favorites": "பிடித்தவை",
+ "ChapterNameValue": "அத்தியாயம் {0}",
+ "Channels": "சேனல்கள்",
+ "Books": "புத்தகங்கள்",
+ "AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
+ "Artists": "கலைஞர்கள்",
+ "Application": "செயலி",
+ "Albums": "ஆல்பங்கள்"
+}
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..c51eb0586 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;
@@ -153,10 +155,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/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..e58c335a8 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
/// </summary>
/// <value>The task manager.</value>
private ITaskManager TaskManager { get; set; }
- private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
@@ -72,24 +73,28 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// or
/// logger
/// </exception>
- 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();
}
@@ -392,7 +396,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
((TaskManager)TaskManager).OnTaskExecuting(this);
- progress.ProgressChanged += progress_ProgressChanged;
+ progress.ProgressChanged += OnProgressChanged;
TaskCompletionStatus status;
CurrentExecutionStartTime = DateTime.UtcNow;
@@ -426,7 +430,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 +443,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
- 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<double>
- {
- Argument = e
- });
+ TaskProgress?.Invoke(this, new GenericEventArgs<double>(e));
}
/// <summary>
@@ -576,6 +577,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="startTime">The start time.</param>
/// <param name="endTime">The end time.</param>
/// <param name="status">The status.</param>
+ /// <param name="ex">The exception.</param>
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..efefa5506 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
/// <param name="tasks">The tasks.</param>
public void AddTasks(IEnumerable<IScheduledTask> 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();
}
@@ -240,10 +242,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="task">The task.</param>
internal void OnTaskExecuting(IScheduledTaskWorker task)
{
- TaskExecuting?.Invoke(this, new GenericEventArgs<IScheduledTaskWorker>
- {
- Argument = task
- });
+ TaskExecuting?.Invoke(this, new GenericEventArgs<IScheduledTaskWorker>(task));
}
/// <summary>
@@ -253,11 +252,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="result">The result.</param>
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/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
}
}
+ /// <inheritdoc />
public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
+ /// <inheritdoc />
public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
+ /// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
+ /// <inheritdoc />
public string Key => "RefreshChapterImages";
+ /// <inheritdoc />
public bool IsHidden => false;
+ /// <inheritdoc />
public bool IsEnabled => true;
+ /// <inheritdoc />
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
}
}
+ /// <inheritdoc />
public string Name => _localization.GetLocalizedString("TaskCleanCache");
+ /// <inheritdoc />
public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
+ /// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+ /// <inheritdoc />
public string Key => "DeleteCacheFiles";
+ /// <inheritdoc />
public bool IsHidden => false;
+ /// <inheritdoc />
public bool IsEnabled => true;
+ /// <inheritdoc />
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 <see cref="DeleteLogFileTask" /> class.
/// </summary>
/// <param name="configurationManager">The configuration manager.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="localization">The localization manager.</param>
public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization)
{
ConfigurationManager = configurationManager;
@@ -82,18 +84,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
return Task.CompletedTask;
}
+ /// <inheritdoc />
public string Name => _localization.GetLocalizedString("TaskCleanLogs");
+ /// <inheritdoc />
public string Description => string.Format(_localization.GetLocalizedString("TaskCleanLogsDescription"), ConfigurationManager.CommonConfiguration.LogFileRetentionDays);
+ /// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+ /// <inheritdoc />
public string Key => "CleanLogFiles";
+ /// <inheritdoc />
public bool IsHidden => false;
+ /// <inheritdoc />
public bool IsEnabled => true;
+ /// <inheritdoc />
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
}
}
+ /// <inheritdoc />
public string Name => _localization.GetLocalizedString("TaskCleanTranscode");
+ /// <inheritdoc />
public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription");
+ /// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+ /// <inheritdoc />
public string Key => "DeleteTranscodeFiles";
+ /// <inheritdoc />
public bool IsHidden => false;
+ /// <inheritdoc />
public bool IsEnabled => false;
+ /// <inheritdoc />
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.
/// </summary>
private readonly ILibraryManager _libraryManager;
-
- private readonly IServerApplicationHost _appHost;
private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="PeopleValidationTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
- /// <param name="appHost">The server application host</param>
- public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost, ILocalizationManager localization)
+ /// <param name="localization">The localization manager.</param>
+ 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 e7a5e2e3d..acab3aeea 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
{
/// <summary>
- /// The _library manager
+ /// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
- private readonly IServerConfigurationManager _config;
private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshMediaLibraryTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
- public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config, ILocalizationManager localization)
+ /// <param name="localization">The localization manager.</param>
+ 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);
}
+ /// <inheritdoc />
public string Name => _localization.GetLocalizedString("TaskRefreshLibrary");
+ /// <inheritdoc />
public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription");
+ /// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
+ /// <inheritdoc />
public string Key => "RefreshLibrary";
+ /// <inheritdoc />
public bool IsHidden => false;
+ /// <inheritdoc />
public bool IsEnabled => true;
+ /// <inheritdoc />
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
/// </summary>
/// <param name="lastResult">The last result.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="taskName">The name of the task.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
@@ -77,10 +79,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary>
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
/// </summary>
/// <param name="lastResult">The last result.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="taskName">The name of the task.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
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
{
/// <summary>
- /// Class StartupTaskTrigger
+ /// Class StartupTaskTrigger.
/// </summary>
public class StartupTrigger : ITaskTrigger
{
@@ -26,6 +28,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// Stars waiting for the trigger action
/// </summary>
/// <param name="lastResult">The last result.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="taskName">The name of the task.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
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
/// </summary>
/// <param name="lastResult">The last result.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="taskName">The name of the task.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
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
/// </summary>
public class JsonSerializer : IJsonSerializer
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="JsonSerializer" /> class.
+ /// </summary>
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 df98a35bc..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;
@@ -25,6 +27,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
@@ -1153,6 +1156,22 @@ namespace Emby.Server.Implementations.Session
await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false);
}
+ /// <inheritdoc />
+ public async Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken)
+ {
+ CheckDisposed();
+ var session = GetSessionToRemoteControl(sessionId);
+ await SendMessageToSession(session, "SyncPlayCommand", command, cancellationToken).ConfigureAwait(false);
+ }
+
+ /// <inheritdoc />
+ public async Task SendSyncPlayGroupUpdate<T>(string sessionId, GroupUpdate<T> command, CancellationToken cancellationToken)
+ {
+ CheckDisposed();
+ var session = GetSessionToRemoteControl(sessionId);
+ await SendMessageToSession(session, "SyncPlayGroupUpdate", command, cancellationToken).ConfigureAwait(false);
+ }
+
private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user)
{
var item = _libraryManager.GetItemById(id);
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index d4e4ba1f2..e7b4b0ec3 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -1,8 +1,13 @@
using System;
+using System.Collections.Generic;
+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 Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -14,6 +19,21 @@ namespace Emby.Server.Implementations.Session
public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable
{
/// <summary>
+ /// The timeout in seconds after which a WebSocket is considered to be lost.
+ /// </summary>
+ public const int WebSocketLostTimeout = 60;
+
+ /// <summary>
+ /// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets.
+ /// </summary>
+ public const float IntervalFactor = 0.2f;
+
+ /// <summary>
+ /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent.
+ /// </summary>
+ public const float ForceKeepAliveFactor = 0.75f;
+
+ /// <summary>
/// The _session manager
/// </summary>
private readonly ISessionManager _sessionManager;
@@ -27,6 +47,26 @@ namespace Emby.Server.Implementations.Session
private readonly IHttpServer _httpServer;
/// <summary>
+ /// The KeepAlive cancellation token.
+ /// </summary>
+ private CancellationTokenSource _keepAliveCancellationToken;
+
+ /// <summary>
+ /// Lock used for accesing the KeepAlive cancellation token.
+ /// </summary>
+ private readonly object _keepAliveLock = new object();
+
+ /// <summary>
+ /// The WebSocket watchlist.
+ /// </summary>
+ private readonly HashSet<IWebSocketConnection> _webSockets = new HashSet<IWebSocketConnection>();
+
+ /// <summary>
+ /// Lock used for accesing the WebSockets watchlist.
+ /// </summary>
+ private readonly object _webSocketsLock = new object();
+
+ /// <summary>
/// Initializes a new instance of the <see cref="SessionWebSocketListener" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
@@ -47,12 +87,13 @@ namespace Emby.Server.Implementations.Session
httpServer.WebSocketConnected += OnServerManagerWebSocketConnected;
}
- private void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e)
+ private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e)
{
var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString());
if (session != null)
{
EnsureController(session, e.Argument);
+ await KeepAliveWebSocket(e.Argument);
}
else
{
@@ -81,6 +122,7 @@ namespace Emby.Server.Implementations.Session
public void Dispose()
{
_httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected;
+ StopKeepAlive();
}
/// <summary>
@@ -99,5 +141,206 @@ namespace Emby.Server.Implementations.Session
var controller = (WebSocketController)controllerInfo.Item1;
controller.AddWebSocket(connection);
}
+
+ /// <summary>
+ /// Called when a WebSocket is closed.
+ /// </summary>
+ /// <param name="sender">The WebSocket.</param>
+ /// <param name="e">The event arguments.</param>
+ private void OnWebSocketClosed(object sender, EventArgs e)
+ {
+ var webSocket = (IWebSocketConnection)sender;
+ _logger.LogDebug("WebSocket {0} is closed.", webSocket);
+ RemoveWebSocket(webSocket);
+ }
+
+ /// <summary>
+ /// Adds a WebSocket to the KeepAlive watchlist.
+ /// </summary>
+ /// <param name="webSocket">The WebSocket to monitor.</param>
+ private async Task KeepAliveWebSocket(IWebSocketConnection webSocket)
+ {
+ lock (_webSocketsLock)
+ {
+ if (!_webSockets.Add(webSocket))
+ {
+ _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket);
+ return;
+ }
+ webSocket.Closed += OnWebSocketClosed;
+ webSocket.LastKeepAliveDate = DateTime.UtcNow;
+
+ StartKeepAlive();
+ }
+
+ // Notify WebSocket about timeout
+ try
+ {
+ await SendForceKeepAlive(webSocket);
+ }
+ catch (WebSocketException exception)
+ {
+ _logger.LogWarning(exception, "Cannot send ForceKeepAlive message to WebSocket {0}.", webSocket);
+ }
+ }
+
+ /// <summary>
+ /// Removes a WebSocket from the KeepAlive watchlist.
+ /// </summary>
+ /// <param name="webSocket">The WebSocket to remove.</param>
+ private void RemoveWebSocket(IWebSocketConnection webSocket)
+ {
+ lock (_webSocketsLock)
+ {
+ if (!_webSockets.Remove(webSocket))
+ {
+ _logger.LogWarning("WebSocket {0} not on watchlist.", webSocket);
+ }
+ else
+ {
+ webSocket.Closed -= OnWebSocketClosed;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Starts the KeepAlive watcher.
+ /// </summary>
+ private void StartKeepAlive()
+ {
+ lock (_keepAliveLock)
+ {
+ if (_keepAliveCancellationToken == null)
+ {
+ _keepAliveCancellationToken = new CancellationTokenSource();
+ // Start KeepAlive watcher
+ _ = RepeatAsyncCallbackEvery(
+ KeepAliveSockets,
+ TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor),
+ _keepAliveCancellationToken.Token);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Stops the KeepAlive watcher.
+ /// </summary>
+ private void StopKeepAlive()
+ {
+ lock (_keepAliveLock)
+ {
+ if (_keepAliveCancellationToken != null)
+ {
+ _keepAliveCancellationToken.Cancel();
+ _keepAliveCancellationToken = null;
+ }
+ }
+
+ lock (_webSocketsLock)
+ {
+ foreach (var webSocket in _webSockets)
+ {
+ webSocket.Closed -= OnWebSocketClosed;
+ }
+
+ _webSockets.Clear();
+ }
+ }
+
+ /// <summary>
+ /// Checks status of KeepAlive of WebSockets.
+ /// </summary>
+ private async Task KeepAliveSockets()
+ {
+ List<IWebSocketConnection> inactive;
+ List<IWebSocketConnection> lost;
+
+ lock (_webSocketsLock)
+ {
+ _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count);
+
+ inactive = _webSockets.Where(i =>
+ {
+ var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds;
+ return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout);
+ }).ToList();
+ lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList();
+ }
+
+ 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.Add(webSocket);
+ }
+ }
+
+ lock (_webSocketsLock)
+ {
+ 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())
+ {
+ StopKeepAlive();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Sends a ForceKeepAlive message to a WebSocket.
+ /// </summary>
+ /// <param name="webSocket">The WebSocket.</param>
+ /// <returns>Task.</returns>
+ private Task SendForceKeepAlive(IWebSocketConnection webSocket)
+ {
+ return webSocket.SendAsync(new WebSocketMessage<int>
+ {
+ MessageType = "ForceKeepAlive",
+ Data = WebSocketLostTimeout
+ }, CancellationToken.None);
+ }
+
+ /// <summary>
+ /// Runs a given async callback once every specified interval time, until cancelled.
+ /// </summary>
+ /// <param name="callback">The async callback.</param>
+ /// <param name="interval">The interval time.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ private async Task RepeatAsyncCallbackEvery(Func<Task> 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/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;
-
- /// <summary>
- /// Releases the unmanaged resources and disposes of the managed resources used.
- /// </summary>
- 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..aa76901a4 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;
@@ -209,7 +211,7 @@ namespace Emby.Server.Implementations.SocketSharp
private static string GetQueryStringContentType(HttpRequest httpReq)
{
ReadOnlySpan<char> format = httpReq.Query["format"].ToString();
- if (format == null)
+ if (format == ReadOnlySpan<char>.Empty)
{
const int FormatMaxLength = 4;
ReadOnlySpan<char> pi = httpReq.Path.ToString();
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;
@@ -8,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting
public class CommunityRatingComparer : IBaseItemComparer
{
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => ItemSortBy.CommunityRating;
+
+ /// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
@@ -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);
}
-
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- 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;
@@ -27,6 +29,12 @@ namespace Emby.Server.Implementations.Sorting
public IUserDataManager UserDataRepository { get; set; }
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => ItemSortBy.DateLastContentAdded;
+
+ /// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
@@ -44,9 +52,7 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>DateTime.</returns>
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;
}
-
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- 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;
@@ -14,6 +16,24 @@ namespace Emby.Server.Implementations.Sorting
public User User { get; set; }
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => ItemSortBy.IsFavoriteOrLiked;
+
+ /// <summary>
+ /// Gets or sets the user data repository.
+ /// </summary>
+ /// <value>The user data repository.</value>
+ public IUserDataManager UserDataRepository { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user manager.
+ /// </summary>
+ /// <value>The user manager.</value>
+ public IUserManager UserManager { get; set; }
+
+ /// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
@@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting
{
return x.IsFavoriteOrLiked(User) ? 0 : 1;
}
-
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name => ItemSortBy.IsFavoriteOrLiked;
-
- /// <summary>
- /// Gets or sets the user data repository.
- /// </summary>
- /// <value>The user data repository.</value>
- public IUserDataManager UserDataRepository { get; set; }
-
- /// <summary>
- /// Gets or sets the user manager.
- /// </summary>
- /// <value>The user manager.</value>
- 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;
@@ -7,6 +9,12 @@ namespace Emby.Server.Implementations.Sorting
public class IsFolderComparer : IBaseItemComparer
{
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => ItemSortBy.IsFolder;
+
+ /// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
@@ -26,11 +34,5 @@ namespace Emby.Server.Implementations.Sorting
{
return x.IsFolder ? 0 : 1;
}
-
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- 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;
@@ -14,6 +16,24 @@ namespace Emby.Server.Implementations.Sorting
public User User { get; set; }
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => ItemSortBy.IsUnplayed;
+
+ /// <summary>
+ /// Gets or sets the user data repository.
+ /// </summary>
+ /// <value>The user data repository.</value>
+ public IUserDataManager UserDataRepository { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user manager.
+ /// </summary>
+ /// <value>The user manager.</value>
+ public IUserManager UserManager { get; set; }
+
+ /// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
@@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting
{
return x.IsPlayed(User) ? 0 : 1;
}
-
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name => ItemSortBy.IsUnplayed;
-
- /// <summary>
- /// Gets or sets the user data repository.
- /// </summary>
- /// <value>The user data repository.</value>
- public IUserDataManager UserDataRepository { get; set; }
-
- /// <summary>
- /// Gets or sets the user manager.
- /// </summary>
- /// <value>The user manager.</value>
- 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;
@@ -14,6 +16,24 @@ namespace Emby.Server.Implementations.Sorting
public User User { get; set; }
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => ItemSortBy.IsUnplayed;
+
+ /// <summary>
+ /// Gets or sets the user data repository.
+ /// </summary>
+ /// <value>The user data repository.</value>
+ public IUserDataManager UserDataRepository { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user manager.
+ /// </summary>
+ /// <value>The user manager.</value>
+ public IUserManager UserManager { get; set; }
+
+ /// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
@@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting
{
return x.IsUnplayed(User) ? 0 : 1;
}
-
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name => ItemSortBy.IsUnplayed;
-
- /// <summary>
- /// Gets or sets the user data repository.
- /// </summary>
- /// <value>The user data repository.</value>
- public IUserDataManager UserDataRepository { get; set; }
-
- /// <summary>
- /// Gets or sets the user manager.
- /// </summary>
- /// <value>The user manager.</value>
- 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;
@@ -16,6 +18,12 @@ namespace Emby.Server.Implementations.Sorting
}
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => ItemSortBy.OfficialRating;
+
+ /// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
@@ -38,11 +46,5 @@ namespace Emby.Server.Implementations.Sorting
return levelX.CompareTo(levelY);
}
-
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- 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;
@@ -8,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting
public class SeriesSortNameComparer : IBaseItemComparer
{
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => ItemSortBy.SeriesSortName;
+
+ /// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
@@ -18,12 +26,6 @@ namespace Emby.Server.Implementations.Sorting
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
}
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- 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;
@@ -9,6 +11,12 @@ namespace Emby.Server.Implementations.Sorting
public class StartDateComparer : IBaseItemComparer
{
/// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name => ItemSortBy.StartDate;
+
+ /// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
@@ -26,19 +34,12 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>DateTime.</returns>
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;
}
-
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- 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
new file mode 100644
index 000000000..d0812a13f
--- /dev/null
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
@@ -0,0 +1,522 @@
+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;
+
+namespace Emby.Server.Implementations.SyncPlay
+{
+ /// <summary>
+ /// Class SyncPlayController.
+ /// </summary>
+ /// <remarks>
+ /// Class is not thread-safe, external locking is required when accessing methods.
+ /// </remarks>
+ public class SyncPlayController : ISyncPlayController
+ {
+ /// <summary>
+ /// Used to filter the sessions of a group.
+ /// </summary>
+ private enum BroadcastType
+ {
+ /// <summary>
+ /// All sessions will receive the message.
+ /// </summary>
+ AllGroup = 0,
+ /// <summary>
+ /// Only the specified session will receive the message.
+ /// </summary>
+ CurrentSession = 1,
+ /// <summary>
+ /// All sessions, except the current one, will receive the message.
+ /// </summary>
+ AllExceptCurrentSession = 2,
+ /// <summary>
+ /// Only sessions that are not buffering will receive the message.
+ /// </summary>
+ AllReady = 3
+ }
+
+ /// <summary>
+ /// The session manager.
+ /// </summary>
+ private readonly ISessionManager _sessionManager;
+
+ /// <summary>
+ /// The SyncPlay manager.
+ /// </summary>
+ private readonly ISyncPlayManager _syncPlayManager;
+
+ /// <summary>
+ /// The group to manage.
+ /// </summary>
+ private readonly GroupInfo _group = new GroupInfo();
+
+ /// <inheritdoc />
+ public Guid GetGroupId() => _group.GroupId;
+
+ /// <inheritdoc />
+ public Guid GetPlayingItemId() => _group.PlayingItem.Id;
+
+ /// <inheritdoc />
+ public bool IsGroupEmpty() => _group.IsEmpty();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayController" /> class.
+ /// </summary>
+ /// <param name="sessionManager">The session manager.</param>
+ /// <param name="syncPlayManager">The SyncPlay manager.</param>
+ public SyncPlayController(
+ ISessionManager sessionManager,
+ ISyncPlayManager syncPlayManager)
+ {
+ _sessionManager = sessionManager;
+ _syncPlayManager = syncPlayManager;
+ }
+
+ /// <summary>
+ /// Converts DateTime to UTC string.
+ /// </summary>
+ /// <param name="date">The date to convert.</param>
+ /// <value>The UTC string.</value>
+ private string DateToUTCString(DateTime date)
+ {
+ return date.ToUniversalTime().ToString("o");
+ }
+
+ /// <summary>
+ /// Filters sessions of this group.
+ /// </summary>
+ /// <param name="from">The current session.</param>
+ /// <param name="type">The filtering type.</param>
+ /// <value>The array of sessions matching the filter.</value>
+ private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type)
+ {
+ switch (type)
+ {
+ 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 Array.Empty<SessionInfo>();
+ }
+ }
+
+ /// <summary>
+ /// Sends a GroupUpdate message to the interested sessions.
+ /// </summary>
+ /// <param name="from">The current session.</param>
+ /// <param name="type">The filtering type.</param>
+ /// <param name="message">The message to send.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <value>The task.</value>
+ private Task SendGroupUpdate<T>(SessionInfo from, BroadcastType type, GroupUpdate<T> message, CancellationToken cancellationToken)
+ {
+ IEnumerable<Task> GetTasks()
+ {
+ SessionInfo[] sessions = FilterSessions(from, type);
+ foreach (var session in sessions)
+ {
+ yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), message, cancellationToken);
+ }
+ }
+
+ return Task.WhenAll(GetTasks());
+ }
+
+ /// <summary>
+ /// Sends a playback command to the interested sessions.
+ /// </summary>
+ /// <param name="from">The current session.</param>
+ /// <param name="type">The filtering type.</param>
+ /// <param name="message">The message to send.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <value>The task.</value>
+ private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken)
+ {
+ IEnumerable<Task> GetTasks()
+ {
+ SessionInfo[] sessions = FilterSessions(from, type);
+ foreach (var session in sessions)
+ {
+ yield return _sessionManager.SendSyncPlayCommand(session.Id.ToString(), message, cancellationToken);
+ }
+ }
+
+ return Task.WhenAll(GetTasks());
+ }
+
+ /// <summary>
+ /// Builds a new playback command with some default values.
+ /// </summary>
+ /// <param name="type">The command type.</param>
+ /// <value>The SendCommand.</value>
+ private SendCommand NewSyncPlayCommand(SendCommandType type)
+ {
+ return new SendCommand()
+ {
+ GroupId = _group.GroupId.ToString(),
+ Command = type,
+ PositionTicks = _group.PositionTicks,
+ When = DateToUTCString(_group.LastActivity),
+ EmittedAt = DateToUTCString(DateTime.UtcNow)
+ };
+ }
+
+ /// <summary>
+ /// Builds a new group update message.
+ /// </summary>
+ /// <param name="type">The update type.</param>
+ /// <param name="data">The data to send.</param>
+ /// <value>The GroupUpdate.</value>
+ private GroupUpdate<T> NewSyncPlayGroupUpdate<T>(GroupUpdateType type, T data)
+ {
+ return new GroupUpdate<T>()
+ {
+ GroupId = _group.GroupId.ToString(),
+ Type = type,
+ Data = data
+ };
+ }
+
+ /// <inheritdoc />
+ 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.LastActivity = DateTime.UtcNow;
+
+ var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow));
+ SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
+ var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause);
+ SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ 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, DateToUTCString(DateTime.UtcNow));
+ SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
+
+ 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);
+ SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken);
+ }
+ else
+ {
+ var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause);
+ SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken);
+ }
+ }
+ else
+ {
+ var playRequest = new PlayRequest();
+ playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id };
+ playRequest.StartPositionTicks = _group.PositionTicks;
+ var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest);
+ SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken);
+ }
+ }
+
+ /// <inheritdoc />
+ 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, cancellationToken);
+
+ var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName);
+ SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ 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.
+ // 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)
+ {
+ case PlaybackRequestType.Play:
+ HandlePlayRequest(session, request, cancellationToken);
+ break;
+ case PlaybackRequestType.Pause:
+ HandlePauseRequest(session, request, cancellationToken);
+ break;
+ case PlaybackRequestType.Seek:
+ HandleSeekRequest(session, request, cancellationToken);
+ break;
+ case PlaybackRequestType.Buffering:
+ HandleBufferingRequest(session, request, cancellationToken);
+ break;
+ case PlaybackRequestType.BufferingDone:
+ HandleBufferingDoneRequest(session, request, cancellationToken);
+ break;
+ case PlaybackRequestType.UpdatePing:
+ HandlePingUpdateRequest(session, request);
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Handles a play action requested by a session.
+ /// </summary>
+ /// <param name="session">The session.</param>
+ /// <param name="request">The play action.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
+ {
+ if (_group.IsPaused)
+ {
+ // Pick a suitable time that accounts for latency
+ var delay = _group.GetHighestPing() * 2;
+ delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;
+
+ // 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
+ );
+
+ var command = NewSyncPlayCommand(SendCommandType.Play);
+ SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
+ }
+ else
+ {
+ // Client got lost, sending current state
+ var command = NewSyncPlayCommand(SendCommandType.Play);
+ SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
+ }
+ }
+
+ /// <summary>
+ /// Handles a pause action requested by a session.
+ /// </summary>
+ /// <param name="session">The session.</param>
+ /// <param name="request">The pause action.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
+ {
+ 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, cancellationToken);
+ }
+ else
+ {
+ // Client got lost, sending current state
+ var command = NewSyncPlayCommand(SendCommandType.Pause);
+ SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
+ }
+ }
+
+ /// <summary>
+ /// Handles a seek action requested by a session.
+ /// </summary>
+ /// <param name="session">The session.</param>
+ /// <param name="request">The seek action.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
+ {
+ // Sanitize PositionTicks
+ var ticks = SanitizePositionTicks(request.PositionTicks);
+
+ // Pause and seek
+ _group.IsPaused = true;
+ _group.PositionTicks = ticks;
+ _group.LastActivity = DateTime.UtcNow;
+
+ var command = NewSyncPlayCommand(SendCommandType.Seek);
+ SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
+ }
+
+ /// <summary>
+ /// Handles a buffering action requested by a session.
+ /// </summary>
+ /// <param name="session">The session.</param>
+ /// <param name="request">The buffering action.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
+ {
+ 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, cancellationToken);
+
+ 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);
+ SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
+ }
+ }
+
+ /// <summary>
+ /// Handles a buffering-done action requested by a session.
+ /// </summary>
+ /// <param name="session">The session.</param>
+ /// <param name="request">The buffering-done action.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
+ {
+ if (_group.IsPaused)
+ {
+ _group.SetBuffering(session, false);
+
+ var requestTicks = SanitizePositionTicks(request.PositionTicks);
+
+ var when = request.When ?? DateTime.UtcNow;
+ var currentTime = DateTime.UtcNow;
+ var elapsedTime = currentTime - when;
+ var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime;
+ var delay = _group.PositionTicks - clientPosition.Ticks;
+
+ if (_group.IsBuffering())
+ {
+ // Others are still buffering, tell this client to pause when ready
+ var command = NewSyncPlayCommand(SendCommandType.Pause);
+ var pauseAtTime = currentTime.AddMilliseconds(delay);
+ command.When = DateToUTCString(pauseAtTime);
+ SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
+ }
+ else
+ {
+ // Let other clients resume as soon as the buffering client catches up
+ _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(SendCommandType.Play);
+ SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken);
+ }
+ 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, cancellationToken);
+ }
+ }
+ }
+ else
+ {
+ // Group was not waiting, make sure client has latest state
+ var command = NewSyncPlayCommand(SendCommandType.Play);
+ SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
+ }
+ }
+
+ /// <summary>
+ /// Sanitizes the PositionTicks, considers the current playing item when available.
+ /// </summary>
+ /// <param name="positionTicks">The PositionTicks.</param>
+ /// <value>The sanitized PositionTicks.</value>
+ 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;
+ }
+
+ /// <summary>
+ /// Updates ping of a session.
+ /// </summary>
+ /// <param name="session">The session.</param>
+ /// <param name="request">The update.</param>
+ 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);
+ }
+
+ /// <inheritdoc />
+ public GroupInfoView GetInfo()
+ {
+ 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).Distinct().ToList()
+ };
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
new file mode 100644
index 000000000..129262e53
--- /dev/null
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
@@ -0,0 +1,405 @@
+using System;
+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;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.SyncPlay;
+
+namespace Emby.Server.Implementations.SyncPlay
+{
+ /// <summary>
+ /// Class SyncPlayManager.
+ /// </summary>
+ public class SyncPlayManager : ISyncPlayManager, IDisposable
+ {
+ /// <summary>
+ /// The logger.
+ /// </summary>
+ private readonly ILogger _logger;
+
+ /// <summary>
+ /// The user manager.
+ /// </summary>
+ private readonly IUserManager _userManager;
+
+ /// <summary>
+ /// The session manager.
+ /// </summary>
+ private readonly ISessionManager _sessionManager;
+
+ /// <summary>
+ /// The library manager.
+ /// </summary>
+ private readonly ILibraryManager _libraryManager;
+
+ /// <summary>
+ /// The map between sessions and groups.
+ /// </summary>
+ private readonly Dictionary<string, ISyncPlayController> _sessionToGroupMap =
+ new Dictionary<string, ISyncPlayController>(StringComparer.OrdinalIgnoreCase);
+
+ /// <summary>
+ /// The groups.
+ /// </summary>
+ private readonly Dictionary<Guid, ISyncPlayController> _groups =
+ new Dictionary<Guid, ISyncPlayController>();
+
+ /// <summary>
+ /// Lock used for accesing any group.
+ /// </summary>
+ private readonly object _groupsLock = new object();
+
+ private bool _disposed = false;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayManager" /> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="sessionManager">The session manager.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ public SyncPlayManager(
+ ILogger<SyncPlayManager> logger,
+ IUserManager userManager,
+ ISessionManager sessionManager,
+ ILibraryManager libraryManager)
+ {
+ _logger = logger;
+ _userManager = userManager;
+ _sessionManager = sessionManager;
+ _libraryManager = libraryManager;
+
+ _sessionManager.SessionEnded += OnSessionManagerSessionEnded;
+ _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped;
+ }
+
+ /// <summary>
+ /// Gets all groups.
+ /// </summary>
+ /// <value>All groups.</value>
+ public IEnumerable<ISyncPlayController> Groups => _groups.Values;
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and optionally managed resources.
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _sessionManager.SessionEnded -= OnSessionManagerSessionEnded;
+ _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped;
+
+ _disposed = true;
+ }
+
+ private void CheckDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+ }
+
+ private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e)
+ {
+ var session = e.SessionInfo;
+ if (!IsSessionInGroup(session))
+ {
+ return;
+ }
+
+ LeaveGroup(session, CancellationToken.None);
+ }
+
+ private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e)
+ {
+ var session = e.Session;
+ if (!IsSessionInGroup(session))
+ {
+ return;
+ }
+
+ LeaveGroup(session, CancellationToken.None);
+ }
+
+ private bool IsSessionInGroup(SessionInfo session)
+ {
+ return _sessionToGroupMap.ContainsKey(session.Id);
+ }
+
+ private bool HasAccessToItem(User user, Guid itemId)
+ {
+ var item = _libraryManager.GetItemById(itemId);
+
+ // Check ParentalRating access
+ var hasParentalRatingAccess = true;
+ if (user.Policy.MaxParentalRating.HasValue)
+ {
+ hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating;
+ }
+
+ 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.Any();
+ }
+ else
+ {
+ return hasParentalRatingAccess;
+ }
+ }
+
+ private Guid? GetSessionGroup(SessionInfo session)
+ {
+ ISyncPlayController group;
+ _sessionToGroupMap.TryGetValue(session.Id, out group);
+ if (group != null)
+ {
+ return group.GetGroupId();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /// <inheritdoc />
+ public void NewGroup(SessionInfo session, CancellationToken cancellationToken)
+ {
+ var user = _userManager.GetUserById(session.UserId);
+
+ if (user.Policy.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups)
+ {
+ _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id);
+
+ var error = new GroupUpdate<string>()
+ {
+ Type = GroupUpdateType.CreateGroupDenied
+ };
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None);
+ return;
+ }
+
+ lock (_groupsLock)
+ {
+ if (IsSessionInGroup(session))
+ {
+ LeaveGroup(session, cancellationToken);
+ }
+
+ var group = new SyncPlayController(_sessionManager, this);
+ _groups[group.GetGroupId()] = group;
+
+ group.InitGroup(session, cancellationToken);
+ }
+ }
+
+ /// <inheritdoc />
+ public void JoinGroup(SessionInfo session, Guid groupId, JoinGroupRequest request, CancellationToken cancellationToken)
+ {
+ var user = _userManager.GetUserById(session.UserId);
+
+ if (user.Policy.SyncPlayAccess == SyncPlayAccess.None)
+ {
+ _logger.LogWarning("JoinGroup: {0} does not have access to SyncPlay.", session.Id);
+
+ var error = new GroupUpdate<string>()
+ {
+ Type = GroupUpdateType.JoinGroupDenied
+ };
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None);
+ return;
+ }
+
+ lock (_groupsLock)
+ {
+ ISyncPlayController group;
+ _groups.TryGetValue(groupId, out group);
+
+ if (group == null)
+ {
+ _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId);
+
+ var error = new GroupUpdate<string>()
+ {
+ Type = GroupUpdateType.GroupDoesNotExist
+ };
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None);
+ return;
+ }
+
+ if (!HasAccessToItem(user, group.GetPlayingItemId()))
+ {
+ _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId());
+
+ var error = new GroupUpdate<string>()
+ {
+ GroupId = group.GetGroupId().ToString(),
+ Type = GroupUpdateType.LibraryAccessDenied
+ };
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None);
+ return;
+ }
+
+ if (IsSessionInGroup(session))
+ {
+ if (GetSessionGroup(session).Equals(groupId))
+ {
+ return;
+ }
+
+ LeaveGroup(session, cancellationToken);
+ }
+
+ group.SessionJoin(session, request, cancellationToken);
+ }
+ }
+
+ /// <inheritdoc />
+ 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)
+ {
+ ISyncPlayController group;
+ _sessionToGroupMap.TryGetValue(session.Id, out group);
+
+ if (group == null)
+ {
+ _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id);
+
+ var error = new GroupUpdate<string>()
+ {
+ Type = GroupUpdateType.NotInGroup
+ };
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None);
+ return;
+ }
+
+ group.SessionLeave(session, cancellationToken);
+
+ if (group.IsGroupEmpty())
+ {
+ _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId());
+ _groups.Remove(group.GetGroupId(), out _);
+ }
+ }
+ }
+
+ /// <inheritdoc />
+ public List<GroupInfoView> ListGroups(SessionInfo session, Guid filterItemId)
+ {
+ var user = _userManager.GetUserById(session.UserId);
+
+ if (user.Policy.SyncPlayAccess == SyncPlayAccess.None)
+ {
+ return new List<GroupInfoView>();
+ }
+
+ // Filter by item if requested
+ if (!filterItemId.Equals(Guid.Empty))
+ {
+ return _groups.Values.Where(
+ group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())
+ ).Select(
+ group => group.GetInfo()
+ ).ToList();
+ }
+ // Otherwise show all available groups
+ else
+ {
+ return _groups.Values.Where(
+ group => HasAccessToItem(user, group.GetPlayingItemId())
+ ).Select(
+ group => group.GetInfo()
+ ).ToList();
+ }
+ }
+
+ /// <inheritdoc />
+ public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
+ {
+ var user = _userManager.GetUserById(session.UserId);
+
+ if (user.Policy.SyncPlayAccess == SyncPlayAccess.None)
+ {
+ _logger.LogWarning("HandleRequest: {0} does not have access to SyncPlay.", session.Id);
+
+ var error = new GroupUpdate<string>()
+ {
+ Type = GroupUpdateType.JoinGroupDenied
+ };
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None);
+ return;
+ }
+
+ lock (_groupsLock)
+ {
+ ISyncPlayController group;
+ _sessionToGroupMap.TryGetValue(session.Id, out group);
+
+ if (group == null)
+ {
+ _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id);
+
+ var error = new GroupUpdate<string>()
+ {
+ Type = GroupUpdateType.NotInGroup
+ };
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None);
+ return;
+ }
+
+ group.HandleRequest(session, request, cancellationToken);
+ }
+ }
+
+ /// <inheritdoc />
+ public void AddSessionToGroup(SessionInfo session, ISyncPlayController group)
+ {
+ if (IsSessionInGroup(session))
+ {
+ throw new InvalidOperationException("Session in other group already!");
+ }
+
+ _sessionToGroupMap[session.Id] = group;
+ }
+
+ /// <inheritdoc />
+ public void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group)
+ {
+ if (!IsSessionInGroup(session))
+ {
+ throw new InvalidOperationException("Session not in any group!");
+ }
+
+ ISyncPlayController tempGroup;
+ _sessionToGroupMap.Remove(session.Id, out tempGroup);
+
+ if (!tempGroup.GetGroupId().Equals(group.GetGroupId()))
+ {
+ throw new InvalidOperationException("Session was in wrong group!");
+ }
+ }
+ }
+}
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<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index c91d137a7..a26f714b1 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
/// </summary>
private readonly ILogger _logger;
private readonly IServerApplicationHost _appHost;
+ private readonly IConfiguration _config;
+
+ /// <summary>
+ /// Address Override Configuration Key.
+ /// </summary>
+ public const string AddressOverrideConfigKey = "PublishedServerUrl";
private Socket _udpSocket;
private IPEndPoint _endpoint;
@@ -31,15 +38,18 @@ namespace Emby.Server.Implementations.Udp
/// <summary>
/// Initializes a new instance of the <see cref="UdpServer" /> class.
/// </summary>
- 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 = !string.IsNullOrEmpty(_config[AddressOverrideConfigKey])
+ ? _config[AddressOverrideConfigKey]
+ : await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(localUrl))
{
@@ -105,7 +115,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/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 684fe5672..178f32c31 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -1,12 +1,12 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections;
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<UserView>
{
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<BaseItem> 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;
}