aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs260
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs10
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs6
-rw-r--r--Emby.Server.Implementations/Data/BaseSqliteRepository.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs39
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs53
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs146
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs53
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj5
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs1
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs4
-rw-r--r--Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs3
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthService.cs5
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs291
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/SessionContext.cs21
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs4
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketManager.cs2
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs10
-rw-r--r--Emby.Server.Implementations/IStartupOptions.cs2
-rw-r--r--Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs2
-rw-r--r--Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs12
-rw-r--r--Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs4
-rw-r--r--Emby.Server.Implementations/Library/ExclusiveLiveStream.cs6
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs39
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs5
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs37
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs5
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs12
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs47
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs (renamed from Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs)0
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs3
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs4
-rw-r--r--Emby.Server.Implementations/Library/Validators/StudiosValidator.cs13
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs28
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs73
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs7
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs622
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs36
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs24
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs48
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs31
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs24
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs42
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs39
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs24
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs24
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs25
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs18
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs24
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs37
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs72
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs42
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs37
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs36
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs48
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs30
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs18
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs42
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs31
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs24
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs157
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs91
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs42
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs24
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs24
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs25
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs25
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs67
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs18
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs36
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs61
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs121
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs7
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs119
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs22
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs103
-rw-r--r--Emby.Server.Implementations/Localization/Core/bg-BG.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/cs.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr-CA.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/gl.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/pl.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt-BR.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/tr.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ur_PK.json9
-rw-r--r--Emby.Server.Implementations/Localization/Core/vi.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-CN.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-HK.json9
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json6
-rw-r--r--Emby.Server.Implementations/Net/SocketFactory.cs20
-rw-r--r--Emby.Server.Implementations/Net/UdpSocket.cs4
-rw-r--r--Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs5
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs4
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs3
-rw-r--r--Emby.Server.Implementations/Properties/AssemblyInfo.cs1
-rw-r--r--Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs113
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs3
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs4
-rw-r--r--Emby.Server.Implementations/Security/AuthenticationRepository.cs408
-rw-r--r--Emby.Server.Implementations/Serialization/MyXmlSerializer.cs2
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs194
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs4
-rw-r--r--Emby.Server.Implementations/Sorting/ArtistComparer.cs2
-rw-r--r--Emby.Server.Implementations/SyncPlay/Group.cs44
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs8
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs22
122 files changed, 2232 insertions, 2303 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index bf7ddace2..3a504d2f4 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -38,7 +38,6 @@ using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Plugins;
using Emby.Server.Implementations.QuickConnect;
using Emby.Server.Implementations.ScheduledTasks;
-using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.SyncPlay;
@@ -59,7 +58,6 @@ using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
@@ -75,7 +73,6 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.QuickConnect;
using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Subtitles;
@@ -117,6 +114,11 @@ namespace Emby.Server.Implementations
/// </summary>
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
+ /// <summary>
+ /// The disposable parts.
+ /// </summary>
+ private readonly List<IDisposable> _disposableParts = new List<IDisposable>();
+
private readonly IFileSystem _fileSystemManager;
private readonly IConfiguration _startupConfig;
private readonly IXmlSerializer _xmlSerializer;
@@ -129,6 +131,62 @@ namespace Emby.Server.Implementations
private string[] _urlPrefixes;
/// <summary>
+ /// Gets or sets all concrete types.
+ /// </summary>
+ /// <value>All concrete types.</value>
+ private Type[] _allConcreteTypes;
+
+ private DeviceId _deviceId;
+
+ private bool _disposed = false;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
+ /// </summary>
+ /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
+ /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
+ /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
+ /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
+ public ApplicationHost(
+ IServerApplicationPaths applicationPaths,
+ ILoggerFactory loggerFactory,
+ IStartupOptions options,
+ IConfiguration startupConfig,
+ IFileSystem fileSystem,
+ IServiceCollection serviceCollection)
+ {
+ ApplicationPaths = applicationPaths;
+ LoggerFactory = loggerFactory;
+ _startupOptions = options;
+ _startupConfig = startupConfig;
+ _fileSystemManager = fileSystem;
+ ServiceCollection = serviceCollection;
+
+ Logger = LoggerFactory.CreateLogger<ApplicationHost>();
+ fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
+
+ ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
+ ApplicationVersionString = ApplicationVersion.ToString(3);
+ ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
+
+ _xmlSerializer = new MyXmlSerializer();
+ ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
+ _pluginManager = new PluginManager(
+ LoggerFactory.CreateLogger<PluginManager>(),
+ this,
+ ConfigurationManager.Configuration,
+ ApplicationPaths.PluginsPath,
+ ApplicationVersion);
+ }
+
+ /// <summary>
+ /// Occurs when [has pending restart changed].
+ /// </summary>
+ public event EventHandler HasPendingRestartChanged;
+
+ /// <summary>
/// Gets a value indicating whether this instance can self restart.
/// </summary>
public bool CanSelfRestart => _startupOptions.RestartPath != null;
@@ -159,11 +217,6 @@ namespace Emby.Server.Implementations
public INetworkManager NetManager { get; internal set; }
/// <summary>
- /// Occurs when [has pending restart changed].
- /// </summary>
- public event EventHandler HasPendingRestartChanged;
-
- /// <summary>
/// Gets a value indicating whether this instance has changes that require the entire application to restart.
/// </summary>
/// <value><c>true</c> if this instance has pending application restart; otherwise, <c>false</c>.</value>
@@ -191,17 +244,6 @@ namespace Emby.Server.Implementations
protected IServerApplicationPaths ApplicationPaths { get; set; }
/// <summary>
- /// Gets or sets all concrete types.
- /// </summary>
- /// <value>All concrete types.</value>
- private Type[] _allConcreteTypes;
-
- /// <summary>
- /// The disposable parts.
- /// </summary>
- private readonly List<IDisposable> _disposableParts = new List<IDisposable>();
-
- /// <summary>
/// Gets or sets the configuration manager.
/// </summary>
/// <value>The configuration manager.</value>
@@ -227,47 +269,55 @@ namespace Emby.Server.Implementations
/// </summary>
public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
+ /// <inheritdoc />
+ public Version ApplicationVersion { get; }
+
+ /// <inheritdoc />
+ public string ApplicationVersionString { get; }
+
/// <summary>
- /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
+ /// Gets the current application user agent.
/// </summary>
- /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
- /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
- /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
- /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
- /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
- /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
- public ApplicationHost(
- IServerApplicationPaths applicationPaths,
- ILoggerFactory loggerFactory,
- IStartupOptions options,
- IConfiguration startupConfig,
- IFileSystem fileSystem,
- IServiceCollection serviceCollection)
- {
- ApplicationPaths = applicationPaths;
- LoggerFactory = loggerFactory;
- _startupOptions = options;
- _startupConfig = startupConfig;
- _fileSystemManager = fileSystem;
- ServiceCollection = serviceCollection;
+ /// <value>The application user agent.</value>
+ public string ApplicationUserAgent { get; }
- Logger = LoggerFactory.CreateLogger<ApplicationHost>();
- fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
+ /// <summary>
+ /// Gets the email address for use within a comment section of a user agent field.
+ /// Presently used to provide contact information to MusicBrainz service.
+ /// </summary>
+ public string ApplicationUserAgentAddress => "team@jellyfin.org";
- ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
- ApplicationVersionString = ApplicationVersion.ToString(3);
- ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
+ /// <summary>
+ /// Gets the current application name.
+ /// </summary>
+ /// <value>The application name.</value>
+ public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
- _xmlSerializer = new MyXmlSerializer();
- ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
- _pluginManager = new PluginManager(
- LoggerFactory.CreateLogger<PluginManager>(),
- this,
- ConfigurationManager.Configuration,
- ApplicationPaths.PluginsPath,
- ApplicationVersion);
+ public string SystemId
+ {
+ get
+ {
+ _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
+
+ return _deviceId.Value;
+ }
}
+ /// <inheritdoc/>
+ public string Name => ApplicationProductName;
+
+ private CertificateInfo CertificateInfo { get; set; }
+
+ public X509Certificate2 Certificate { get; private set; }
+
+ /// <inheritdoc/>
+ public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps;
+
+ public string FriendlyName =>
+ string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName)
+ ? Environment.MachineName
+ : ConfigurationManager.Configuration.ServerName;
+
/// <summary>
/// Temporary function to migration network settings out of system.xml and into network.xml.
/// TODO: remove at the point when a fixed migration path has been decided upon.
@@ -300,45 +350,6 @@ namespace Emby.Server.Implementations
.Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase);
}
- /// <inheritdoc />
- public Version ApplicationVersion { get; }
-
- /// <inheritdoc />
- public string ApplicationVersionString { get; }
-
- /// <summary>
- /// Gets the current application user agent.
- /// </summary>
- /// <value>The application user agent.</value>
- public string ApplicationUserAgent { get; }
-
- /// <summary>
- /// Gets the email address for use within a comment section of a user agent field.
- /// Presently used to provide contact information to MusicBrainz service.
- /// </summary>
- public string ApplicationUserAgentAddress => "team@jellyfin.org";
-
- /// <summary>
- /// Gets the current application name.
- /// </summary>
- /// <value>The application name.</value>
- public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
-
- private DeviceId _deviceId;
-
- public string SystemId
- {
- get
- {
- _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
-
- return _deviceId.Value;
- }
- }
-
- /// <inheritdoc/>
- public string Name => ApplicationProductName;
-
/// <summary>
/// Creates an instance of type and resolves all constructor dependencies.
/// </summary>
@@ -456,6 +467,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// Runs the startup tasks.
/// </summary>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns><see cref="Task" />.</returns>
public async Task RunStartupTasksAsync(CancellationToken cancellationToken)
{
@@ -469,7 +481,7 @@ namespace Emby.Server.Implementations
_mediaEncoder.SetFFmpegPath();
- Logger.LogInformation("ServerId: {0}", SystemId);
+ Logger.LogInformation("ServerId: {ServerId}", SystemId);
var entryPoints = GetExports<IServerEntryPoint>();
@@ -594,8 +606,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
- ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
-
ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
ServiceCollection.AddSingleton<EncodingHelper>();
@@ -617,8 +627,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
- ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>();
-
ServiceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
ServiceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
@@ -654,8 +662,7 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
- ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
- ServiceCollection.AddSingleton<ISessionContext, SessionContext>();
+ ServiceCollection.AddScoped<ISessionContext, SessionContext>();
ServiceCollection.AddSingleton<IAuthService, AuthService>();
ServiceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
@@ -684,8 +691,6 @@ namespace Emby.Server.Implementations
_mediaEncoder = Resolve<IMediaEncoder>();
_sessionManager = Resolve<ISessionManager>();
- ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
-
SetStaticProperties();
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
@@ -866,10 +871,6 @@ namespace Emby.Server.Implementations
}
}
- private CertificateInfo CertificateInfo { get; set; }
-
- public X509Certificate2 Certificate { get; private set; }
-
private IEnumerable<string> GetUrlPrefixes()
{
var hosts = new[] { "+" };
@@ -1099,7 +1100,6 @@ namespace Emby.Server.Implementations
ServerName = FriendlyName,
LocalAddress = GetSmartApiUrl(source),
SupportsLibraryMonitor = true,
- EncoderLocation = _mediaEncoder.EncoderLocation,
SystemArchitecture = RuntimeInformation.OSArchitecture,
PackageName = _startupOptions.PackageName
};
@@ -1125,9 +1125,6 @@ namespace Emby.Server.Implementations
}
/// <inheritdoc/>
- public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps;
-
- /// <inheritdoc/>
public string GetSmartApiUrl(IPAddress remoteAddr, int? port = null)
{
// Published server ends with a /
@@ -1213,14 +1210,7 @@ namespace Emby.Server.Implementations
}.ToString().TrimEnd('/');
}
- public string FriendlyName =>
- string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName)
- ? Environment.MachineName
- : ConfigurationManager.Configuration.ServerName;
-
- /// <summary>
- /// Shuts down.
- /// </summary>
+ /// <inheritdoc />
public async Task Shutdown()
{
if (IsShuttingDown)
@@ -1258,41 +1248,7 @@ namespace Emby.Server.Implementations
}
}
- public virtual void LaunchUrl(string url)
- {
- if (!CanLaunchWebBrowser)
- {
- throw new NotSupportedException();
- }
-
- var process = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = url,
- UseShellExecute = true,
- ErrorDialog = false
- },
- EnableRaisingEvents = true
- };
- process.Exited += (sender, args) => ((Process)sender).Dispose();
-
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error launching url: {url}", url);
- throw;
- }
- }
-
- private bool _disposed = false;
-
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
+ /// <inheritdoc />
public void Dispose()
{
Dispose(true);
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index aa54510a7..6faa5d363 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Channels
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
- return !(channel is IDisableMediaSourceDisplay);
+ return channel is not IDisableMediaSourceDisplay;
}
/// <inheritdoc />
@@ -815,7 +815,7 @@ namespace Emby.Server.Implementations.Channels
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
- await using FileStream jsonStream = File.OpenRead(cachePath);
+ await using FileStream jsonStream = AsyncFile.OpenRead(cachePath);
var cachedResult = await JsonSerializer.DeserializeAsync<ChannelItemResult>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (cachedResult != null)
{
@@ -838,7 +838,7 @@ namespace Emby.Server.Implementations.Channels
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
- await using FileStream jsonStream = File.OpenRead(cachePath);
+ await using FileStream jsonStream = AsyncFile.OpenRead(cachePath);
var cachedResult = await JsonSerializer.DeserializeAsync<ChannelItemResult>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (cachedResult != null)
{
@@ -1079,11 +1079,11 @@ namespace Emby.Server.Implementations.Channels
// was used for status
// if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
- //{
+ // {
// item.ExternalEtag = info.Etag;
// forceUpdate = true;
// _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name);
- //}
+ // }
if (!internalChannelId.Equals(item.ChannelId))
{
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 8270c2e84..79ef70fff 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Collections
var libraryOptions = new LibraryOptions
{
- PathInfos = new[] { new MediaPathInfo { Path = path } },
+ PathInfos = new[] { new MediaPathInfo(path) },
EnableRealtimeMonitor = false,
SaveLocalMetadata = true
};
@@ -196,8 +196,8 @@ namespace Emby.Server.Implementations.Collections
}
/// <inheritdoc />
- public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids)
- => AddToCollectionAsync(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
+ public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
+ => AddToCollectionAsync(collectionId, itemIds, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
{
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index 6f23a0888..01c9fbca8 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Data
protected virtual int? CacheSize => null;
/// <summary>
- /// Gets the journal mode. <see href="https://www.sqlite.org/pragma.html#pragma_journal_mode" />
+ /// Gets the journal mode. <see href="https://www.sqlite.org/pragma.html#pragma_journal_mode" />.
/// </summary>
/// <value>The journal mode.</value>
protected virtual string JournalMode => "TRUNCATE";
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 2cb10765f..88fc5018d 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -16,7 +16,6 @@ using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -25,7 +24,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
@@ -75,6 +73,12 @@ namespace Emby.Server.Implementations.Data
/// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
/// </summary>
+ /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ /// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{SqliteItemRepository}"/> interface.</param>
+ /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+ /// <param name="imageProcessor">Instance of the <see cref="IImageProcessor"/> interface.</param>
+ /// <exception cref="ArgumentNullException">config is null.</exception>
public SqliteItemRepository(
IServerConfigurationManager config,
IServerApplicationHost appHost,
@@ -1135,15 +1139,25 @@ namespace Emby.Server.Implementations.Data
Path = RestorePath(path.ToString())
};
- if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
+ if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)
+ && ticks >= DateTime.MinValue.Ticks
+ && ticks <= DateTime.MaxValue.Ticks)
{
image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
}
+ else
+ {
+ return null;
+ }
if (Enum.TryParse(imageType.ToString(), true, out ImageType type))
{
image.Type = type;
}
+ else
+ {
+ return null;
+ }
// Optional parameters: width*height*blurhash
if (nextSegment + 1 < value.Length - 1)
@@ -1886,12 +1900,7 @@ namespace Emby.Server.Implementations.Data
return result;
}
- /// <summary>
- /// Gets chapters for an item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns>IEnumerable{ChapterInfo}.</returns>
- /// <exception cref="ArgumentNullException">id</exception>
+ /// <inheritdoc />
public List<ChapterInfo> GetChapters(BaseItem item)
{
CheckDisposed();
@@ -1914,13 +1923,7 @@ namespace Emby.Server.Implementations.Data
}
}
- /// <summary>
- /// Gets a single chapter for an item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="index">The index.</param>
- /// <returns>ChapterInfo.</returns>
- /// <exception cref="ArgumentNullException">id</exception>
+ /// <inheritdoc />
public ChapterInfo GetChapter(BaseItem item, int index)
{
CheckDisposed();
@@ -2032,7 +2035,7 @@ namespace Emby.Server.Implementations.Data
for (var i = startIndex; i < endIndex; i++)
{
- insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
+ insertText.AppendFormat(CultureInfo.InvariantCulture, "(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
}
insertText.Length -= 1; // Remove last ,
@@ -4879,7 +4882,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
foreach (var t in _knownTypes)
{
- dict[t.Name] = t.FullName ;
+ dict[t.Name] = t.FullName;
}
dict["Program"] = typeof(LiveTvProgram).FullName;
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index ef9af1dcd..829f1de2f 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -129,19 +129,17 @@ namespace Emby.Server.Implementations.Data
return list;
}
- /// <summary>
- /// Saves the user data.
- /// </summary>
- public void SaveUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken)
{
if (userData == null)
{
throw new ArgumentNullException(nameof(userData));
}
- if (internalUserId <= 0)
+ if (userId <= 0)
{
- throw new ArgumentNullException(nameof(internalUserId));
+ throw new ArgumentNullException(nameof(userId));
}
if (string.IsNullOrEmpty(key))
@@ -149,22 +147,23 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(key));
}
- PersistUserData(internalUserId, key, userData, cancellationToken);
+ PersistUserData(userId, key, userData, cancellationToken);
}
- public void SaveAllUserData(long internalUserId, UserItemData[] userData, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public void SaveAllUserData(long userId, UserItemData[] userData, CancellationToken cancellationToken)
{
if (userData == null)
{
throw new ArgumentNullException(nameof(userData));
}
- if (internalUserId <= 0)
+ if (userId <= 0)
{
- throw new ArgumentNullException(nameof(internalUserId));
+ throw new ArgumentNullException(nameof(userId));
}
- PersistAllUserData(internalUserId, userData, cancellationToken);
+ PersistAllUserData(userId, userData, cancellationToken);
}
/// <summary>
@@ -174,7 +173,6 @@ namespace Emby.Server.Implementations.Data
/// <param name="key">The key.</param>
/// <param name="userData">The user data.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
public void PersistUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -264,19 +262,19 @@ namespace Emby.Server.Implementations.Data
/// <summary>
/// Gets the user data.
/// </summary>
- /// <param name="internalUserId">The user id.</param>
+ /// <param name="userId">The user id.</param>
/// <param name="key">The key.</param>
/// <returns>Task{UserItemData}.</returns>
/// <exception cref="ArgumentNullException">
/// userId
/// or
- /// key
+ /// key.
/// </exception>
- public UserItemData GetUserData(long internalUserId, string key)
+ public UserItemData GetUserData(long userId, string key)
{
- if (internalUserId <= 0)
+ if (userId <= 0)
{
- throw new ArgumentNullException(nameof(internalUserId));
+ throw new ArgumentNullException(nameof(userId));
}
if (string.IsNullOrEmpty(key))
@@ -288,7 +286,7 @@ namespace Emby.Server.Implementations.Data
{
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
{
- statement.TryBind("@UserId", internalUserId);
+ statement.TryBind("@UserId", userId);
statement.TryBind("@Key", key);
foreach (var row in statement.ExecuteQuery())
@@ -301,7 +299,7 @@ namespace Emby.Server.Implementations.Data
}
}
- public UserItemData GetUserData(long internalUserId, List<string> keys)
+ public UserItemData GetUserData(long userId, List<string> keys)
{
if (keys == null)
{
@@ -313,19 +311,19 @@ namespace Emby.Server.Implementations.Data
return null;
}
- return GetUserData(internalUserId, keys[0]);
+ return GetUserData(userId, keys[0]);
}
/// <summary>
/// Return all user-data associated with the given user.
/// </summary>
- /// <param name="internalUserId"></param>
- /// <returns></returns>
- public List<UserItemData> GetAllUserData(long internalUserId)
+ /// <param name="userId">The internal user id.</param>
+ /// <returns>The list of user item data.</returns>
+ public List<UserItemData> GetAllUserData(long userId)
{
- if (internalUserId <= 0)
+ if (userId <= 0)
{
- throw new ArgumentNullException(nameof(internalUserId));
+ throw new ArgumentNullException(nameof(userId));
}
var list = new List<UserItemData>();
@@ -334,7 +332,7 @@ namespace Emby.Server.Implementations.Data
{
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId"))
{
- statement.TryBind("@UserId", internalUserId);
+ statement.TryBind("@UserId", userId);
foreach (var row in statement.ExecuteQuery())
{
@@ -349,7 +347,8 @@ namespace Emby.Server.Implementations.Data
/// <summary>
/// Read a row from the specified reader into the provided userData object.
/// </summary>
- /// <param name="reader"></param>
+ /// <param name="reader">The list of result set values.</param>
+ /// <returns>The user item data.</returns>
private UserItemData ReadRow(IReadOnlyList<ResultSetValue> reader)
{
var userData = new UserItemData();
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
deleted file mode 100644
index 2637addce..000000000
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
-using Jellyfin.Data.Events;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.Devices;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Session;
-
-namespace Emby.Server.Implementations.Devices
-{
- public class DeviceManager : IDeviceManager
- {
- private readonly IUserManager _userManager;
- private readonly IAuthenticationRepository _authRepo;
- private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new ();
-
- public DeviceManager(IAuthenticationRepository authRepo, IUserManager userManager)
- {
- _userManager = userManager;
- _authRepo = authRepo;
- }
-
- public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
-
- public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
- {
- _capabilitiesMap[deviceId] = capabilities;
- }
-
- public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
- {
- _authRepo.UpdateDeviceOptions(deviceId, options);
-
- DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options)));
- }
-
- public DeviceOptions GetDeviceOptions(string deviceId)
- {
- return _authRepo.GetDeviceOptions(deviceId);
- }
-
- public ClientCapabilities GetCapabilities(string id)
- {
- return _capabilitiesMap.TryGetValue(id, out ClientCapabilities result)
- ? result
- : new ClientCapabilities();
- }
-
- public DeviceInfo GetDevice(string id)
- {
- var session = _authRepo.Get(new AuthenticationInfoQuery
- {
- DeviceId = id
- }).Items.FirstOrDefault();
-
- var device = session == null ? null : ToDeviceInfo(session);
-
- return device;
- }
-
- public QueryResult<DeviceInfo> GetDevices(DeviceQuery query)
- {
- IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
- {
- // UserId = query.UserId
- HasUser = true
- }).Items;
-
- // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
- if (query.SupportsSync.HasValue)
- {
- var val = query.SupportsSync.Value;
-
- sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val);
- }
-
- if (!query.UserId.Equals(Guid.Empty))
- {
- var user = _userManager.GetUserById(query.UserId);
-
- sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
- }
-
- var array = sessions.Select(ToDeviceInfo).ToArray();
-
- return new QueryResult<DeviceInfo>(array);
- }
-
- private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo)
- {
- var caps = GetCapabilities(authInfo.DeviceId);
-
- return new DeviceInfo
- {
- AppName = authInfo.AppName,
- AppVersion = authInfo.AppVersion,
- Id = authInfo.DeviceId,
- LastUserId = authInfo.UserId,
- LastUserName = authInfo.UserName,
- Name = authInfo.DeviceName,
- DateLastActivity = authInfo.DateLastActivity,
- IconUrl = caps?.IconUrl
- };
- }
-
- public bool CanAccessDevice(User user, string deviceId)
- {
- if (user == null)
- {
- throw new ArgumentException("user not found");
- }
-
- if (string.IsNullOrEmpty(deviceId))
- {
- throw new ArgumentNullException(nameof(deviceId));
- }
-
- if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator))
- {
- return true;
- }
-
- if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase))
- {
- var capabilities = GetCapabilities(deviceId);
-
- if (capabilities != null && capabilities.SupportsPersistentIdentifier)
- {
- return false;
- }
- }
-
- return true;
- }
- }
-}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 7411239a1..74400b512 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -51,8 +51,6 @@ namespace Emby.Server.Implementations.Dto
private readonly IMediaSourceManager _mediaSourceManager;
private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
- private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
-
public DtoService(
ILogger<DtoService> logger,
ILibraryManager libraryManager,
@@ -75,6 +73,8 @@ namespace Emby.Server.Implementations.Dto
_livetvManagerFactory = livetvManagerFactory;
}
+ private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
+
/// <inheritdoc />
public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
{
@@ -507,7 +507,6 @@ namespace Emby.Server.Implementations.Dto
/// </summary>
/// <param name="dto">The dto.</param>
/// <param name="item">The item.</param>
- /// <returns>Task.</returns>
private void AttachPeople(BaseItemDto dto, BaseItem item)
{
// Ordering by person type to ensure actors and artists are at the front.
@@ -616,7 +615,6 @@ namespace Emby.Server.Implementations.Dto
/// </summary>
/// <param name="dto">The dto.</param>
/// <param name="item">The item.</param>
- /// <returns>Task.</returns>
private void AttachStudios(BaseItemDto dto, BaseItem item)
{
dto.Studios = item.Studios
@@ -807,7 +805,7 @@ namespace Emby.Server.Implementations.Dto
dto.MediaType = item.MediaType;
- if (!(item is LiveTvProgram))
+ if (item is not LiveTvProgram)
{
dto.LocationType = item.LocationType;
}
@@ -928,9 +926,9 @@ namespace Emby.Server.Implementations.Dto
}
// if (options.ContainsField(ItemFields.MediaSourceCount))
- //{
+ // {
// Songs always have one
- //}
+ // }
}
if (item is IHasArtist hasArtist)
@@ -938,10 +936,10 @@ namespace Emby.Server.Implementations.Dto
dto.Artists = hasArtist.Artists;
// var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
- //{
+ // {
// EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
- //});
+ // });
// dto.ArtistItems = artistItems.Items
// .Select(i =>
@@ -958,7 +956,7 @@ namespace Emby.Server.Implementations.Dto
// Include artists that are not in the database yet, e.g., just added via metadata editor
// var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
dto.ArtistItems = hasArtist.Artists
- //.Except(foundArtists, new DistinctNameComparer())
+ // .Except(foundArtists, new DistinctNameComparer())
.Select(i =>
{
// This should not be necessary but we're seeing some cases of it
@@ -990,10 +988,10 @@ namespace Emby.Server.Implementations.Dto
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
// var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
- //{
+ // {
// EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
- //});
+ // });
// dto.AlbumArtists = artistItems.Items
// .Select(i =>
@@ -1008,7 +1006,7 @@ namespace Emby.Server.Implementations.Dto
// .ToList();
dto.AlbumArtists = hasAlbumArtist.AlbumArtists
- //.Except(foundArtists, new DistinctNameComparer())
+ // .Except(foundArtists, new DistinctNameComparer())
.Select(i =>
{
// This should not be necessary but we're seeing some cases of it
@@ -1035,8 +1033,7 @@ namespace Emby.Server.Implementations.Dto
}
// Add video info
- var video = item as Video;
- if (video != null)
+ if (item is Video video)
{
dto.VideoType = video.VideoType;
dto.Video3DFormat = video.Video3DFormat;
@@ -1075,9 +1072,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.MediaStreams))
{
// Add VideoInfo
- var iHasMediaSources = item as IHasMediaSources;
-
- if (iHasMediaSources != null)
+ if (item is IHasMediaSources)
{
MediaStream[] mediaStreams;
@@ -1146,7 +1141,7 @@ namespace Emby.Server.Implementations.Dto
// TODO maybe remove the if statement entirely
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{
- episodeSeries = episodeSeries ?? episode.Series;
+ episodeSeries ??= episode.Series;
if (episodeSeries != null)
{
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
@@ -1159,7 +1154,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.SeriesStudio))
{
- episodeSeries = episodeSeries ?? episode.Series;
+ episodeSeries ??= episode.Series;
if (episodeSeries != null)
{
dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
@@ -1172,7 +1167,7 @@ namespace Emby.Server.Implementations.Dto
{
dto.AirDays = series.AirDays;
dto.AirTime = series.AirTime;
- dto.Status = series.Status.HasValue ? series.Status.Value.ToString() : null;
+ dto.Status = series.Status?.ToString();
}
// Add SeasonInfo
@@ -1185,7 +1180,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.SeriesStudio))
{
- series = series ?? season.Series;
+ series ??= season.Series;
if (series != null)
{
dto.SeriesStudio = series.Studios.FirstOrDefault();
@@ -1196,7 +1191,7 @@ namespace Emby.Server.Implementations.Dto
// TODO maybe remove the if statement entirely
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{
- series = series ?? season.Series;
+ series ??= season.Series;
if (series != null)
{
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
@@ -1283,7 +1278,7 @@ namespace Emby.Server.Implementations.Dto
var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
- if (parent == null && !(originalItem is UserRootFolder) && !(originalItem is UserView) && !(originalItem is AggregateFolder) && !(originalItem is ICollectionFolder) && !(originalItem is Channel))
+ if (parent == null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is not AggregateFolder && originalItem is not ICollectionFolder && originalItem is not Channel)
{
parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
}
@@ -1316,9 +1311,12 @@ namespace Emby.Server.Implementations.Dto
var imageTags = dto.ImageTags;
- while (((!(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0) || parent is Series) &&
- (parent = parent ?? (isFirst ? GetImageDisplayParent(item, item) ?? owner : parent)) != null)
+ while ((!(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0)
+ || (!(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0)
+ || (!(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0)
+ || parent is Series)
{
+ parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent;
if (parent == null)
{
break;
@@ -1348,7 +1346,7 @@ namespace Emby.Server.Implementations.Dto
}
}
- if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
+ if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && parent is not ICollectionFolder && parent is not UserView)
{
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
@@ -1398,7 +1396,6 @@ namespace Emby.Server.Implementations.Dto
/// </summary>
/// <param name="dto">The dto.</param>
/// <param name="item">The item.</param>
- /// <returns>Task.</returns>
public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
{
dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index e0f841d52..876597b8d 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -23,6 +23,7 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
@@ -30,8 +31,8 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.9" />
<PackageReference Include="Mono.Nat" Version="3.0.1" />
- <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
- <PackageReference Include="sharpcompress" Version="0.28.3" />
+ <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.1" />
+ <PackageReference Include="sharpcompress" Version="0.29.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
</ItemGroup>
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index 0a4efd73c..640754af4 100644
--- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -9,7 +9,6 @@ using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Events;
using Jellyfin.Networking.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index 5bb4100ba..df48346e3 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.EntryPoints
private static bool EnableRefreshMessage(BaseItem item)
{
- if (!(item is Folder folder))
+ if (item is not Folder folder)
{
return false;
}
@@ -403,7 +403,7 @@ namespace Emby.Server.Implementations.EntryPoints
return false;
}
- if (item is IItemByName && !(item is MusicArtist))
+ if (item is IItemByName && item is not MusicArtist)
{
return false;
}
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index 2e72b18f5..feaccf9fa 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -37,6 +37,9 @@ namespace Emby.Server.Implementations.EntryPoints
/// <summary>
/// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
/// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger{UdpServerEntryPoint}"/> interface.</param>
+ /// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
+ /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
public UdpServerEntryPoint(
ILogger<UdpServerEntryPoint> logger,
IServerApplicationHost appHost,
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
index 9afabf527..e2ad07177 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
@@ -17,9 +18,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
_authorizationContext = authorizationContext;
}
- public AuthorizationInfo Authenticate(HttpRequest request)
+ public async Task<AuthorizationInfo> Authenticate(HttpRequest request)
{
- var auth = _authorizationContext.GetAuthorizationInfo(request);
+ var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false);
if (!auth.HasToken)
{
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
deleted file mode 100644
index b2625a68c..000000000
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ /dev/null
@@ -1,291 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Net;
-using Jellyfin.Extensions;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Security;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Net.Http.Headers;
-
-namespace Emby.Server.Implementations.HttpServer.Security
-{
- public class AuthorizationContext : IAuthorizationContext
- {
- private readonly IAuthenticationRepository _authRepo;
- private readonly IUserManager _userManager;
-
- public AuthorizationContext(IAuthenticationRepository authRepo, IUserManager userManager)
- {
- _authRepo = authRepo;
- _userManager = userManager;
- }
-
- public AuthorizationInfo GetAuthorizationInfo(HttpContext requestContext)
- {
- if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached))
- {
- return (AuthorizationInfo)cached!; // Cache should never contain null
- }
-
- return GetAuthorization(requestContext);
- }
-
- public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext)
- {
- var auth = GetAuthorizationDictionary(requestContext);
- var authInfo = GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query);
- return authInfo;
- }
-
- /// <summary>
- /// Gets the authorization.
- /// </summary>
- /// <param name="httpReq">The HTTP req.</param>
- /// <returns>Dictionary{System.StringSystem.String}.</returns>
- private AuthorizationInfo GetAuthorization(HttpContext httpReq)
- {
- var auth = GetAuthorizationDictionary(httpReq);
- var authInfo = GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query);
-
- httpReq.Request.HttpContext.Items["AuthorizationInfo"] = authInfo;
- return authInfo;
- }
-
- private AuthorizationInfo GetAuthorizationInfoFromDictionary(
- in Dictionary<string, string>? auth,
- in IHeaderDictionary headers,
- in IQueryCollection queryString)
- {
- string? deviceId = null;
- string? device = null;
- string? client = null;
- string? version = null;
- string? token = null;
-
- if (auth != null)
- {
- auth.TryGetValue("DeviceId", out deviceId);
- auth.TryGetValue("Device", out device);
- auth.TryGetValue("Client", out client);
- auth.TryGetValue("Version", out version);
- auth.TryGetValue("Token", out token);
- }
-
- if (string.IsNullOrEmpty(token))
- {
- token = headers["X-Emby-Token"];
- }
-
- if (string.IsNullOrEmpty(token))
- {
- token = headers["X-MediaBrowser-Token"];
- }
-
- if (string.IsNullOrEmpty(token))
- {
- token = queryString["ApiKey"];
- }
-
- // TODO deprecate this query parameter.
- if (string.IsNullOrEmpty(token))
- {
- token = queryString["api_key"];
- }
-
- var authInfo = new AuthorizationInfo
- {
- Client = client,
- Device = device,
- DeviceId = deviceId,
- Version = version,
- Token = token,
- IsAuthenticated = false,
- HasToken = false
- };
-
- if (string.IsNullOrWhiteSpace(token))
- {
- // Request doesn't contain a token.
- return authInfo;
- }
-
- authInfo.HasToken = true;
- var result = _authRepo.Get(new AuthenticationInfoQuery
- {
- AccessToken = token
- });
-
- if (result.Items.Count > 0)
- {
- authInfo.IsAuthenticated = true;
- }
-
- var originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null;
-
- if (originalAuthenticationInfo != null)
- {
- var updateToken = false;
-
- // TODO: Remove these checks for IsNullOrWhiteSpace
- if (string.IsNullOrWhiteSpace(authInfo.Client))
- {
- authInfo.Client = originalAuthenticationInfo.AppName;
- }
-
- if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
- {
- authInfo.DeviceId = originalAuthenticationInfo.DeviceId;
- }
-
- // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
- var allowTokenInfoUpdate = authInfo.Client == null || !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase);
-
- if (string.IsNullOrWhiteSpace(authInfo.Device))
- {
- authInfo.Device = originalAuthenticationInfo.DeviceName;
- }
- else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
- {
- if (allowTokenInfoUpdate)
- {
- updateToken = true;
- originalAuthenticationInfo.DeviceName = authInfo.Device;
- }
- }
-
- if (string.IsNullOrWhiteSpace(authInfo.Version))
- {
- authInfo.Version = originalAuthenticationInfo.AppVersion;
- }
- else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
- {
- if (allowTokenInfoUpdate)
- {
- updateToken = true;
- originalAuthenticationInfo.AppVersion = authInfo.Version;
- }
- }
-
- if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3)
- {
- originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow;
- updateToken = true;
- }
-
- if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty))
- {
- authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId);
-
- if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase))
- {
- originalAuthenticationInfo.UserName = authInfo.User.Username;
- updateToken = true;
- }
-
- authInfo.IsApiKey = false;
- }
- else
- {
- authInfo.IsApiKey = true;
- }
-
- if (updateToken)
- {
- _authRepo.Update(originalAuthenticationInfo);
- }
- }
-
- return authInfo;
- }
-
- /// <summary>
- /// Gets the auth.
- /// </summary>
- /// <param name="httpReq">The HTTP req.</param>
- /// <returns>Dictionary{System.StringSystem.String}.</returns>
- private Dictionary<string, string>? GetAuthorizationDictionary(HttpContext httpReq)
- {
- var auth = httpReq.Request.Headers["X-Emby-Authorization"];
-
- if (string.IsNullOrEmpty(auth))
- {
- auth = httpReq.Request.Headers[HeaderNames.Authorization];
- }
-
- return GetAuthorization(auth.Count > 0 ? auth[0] : null);
- }
-
- /// <summary>
- /// Gets the auth.
- /// </summary>
- /// <param name="httpReq">The HTTP req.</param>
- /// <returns>Dictionary{System.StringSystem.String}.</returns>
- private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq)
- {
- var auth = httpReq.Headers["X-Emby-Authorization"];
-
- if (string.IsNullOrEmpty(auth))
- {
- auth = httpReq.Headers[HeaderNames.Authorization];
- }
-
- return GetAuthorization(auth.Count > 0 ? auth[0] : null);
- }
-
- /// <summary>
- /// Gets the authorization.
- /// </summary>
- /// <param name="authorizationHeader">The authorization header.</param>
- /// <returns>Dictionary{System.StringSystem.String}.</returns>
- private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader)
- {
- if (authorizationHeader == null)
- {
- return null;
- }
-
- var firstSpace = authorizationHeader.IndexOf(' ');
-
- // There should be at least two parts
- if (firstSpace == -1)
- {
- return null;
- }
-
- var name = authorizationHeader[..firstSpace];
-
- if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase)
- && !name.Equals("Emby", StringComparison.OrdinalIgnoreCase))
- {
- return null;
- }
-
- authorizationHeader = authorizationHeader[(firstSpace + 1)..];
-
- var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- foreach (var item in authorizationHeader.Split(','))
- {
- var trimmedItem = item.Trim();
- var firstEqualsSign = trimmedItem.IndexOf('=');
-
- if (firstEqualsSign > 0)
- {
- var key = trimmedItem[..firstEqualsSign].ToString();
- var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString());
- result[key] = value;
- }
- }
-
- return result;
- }
-
- private static string NormalizeValue(string value)
- {
- return string.IsNullOrEmpty(value) ? value : WebUtility.HtmlEncode(value);
- }
- }
-}
diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
index c375f36ce..a7647caf9 100644
--- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System;
+using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
@@ -23,27 +24,33 @@ namespace Emby.Server.Implementations.HttpServer.Security
_sessionManager = sessionManager;
}
- public SessionInfo GetSession(HttpContext requestContext)
+ public async Task<SessionInfo> GetSession(HttpContext requestContext)
{
- var authorization = _authContext.GetAuthorizationInfo(requestContext);
+ var authorization = await _authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false);
var user = authorization.User;
- return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user);
+ return await _sessionManager.LogSessionActivity(
+ authorization.Client,
+ authorization.Version,
+ authorization.DeviceId,
+ authorization.Device,
+ requestContext.GetNormalizedRemoteIp().ToString(),
+ user).ConfigureAwait(false);
}
- public SessionInfo GetSession(object requestContext)
+ public Task<SessionInfo> GetSession(object requestContext)
{
return GetSession((HttpContext)requestContext);
}
- public User? GetUser(HttpContext requestContext)
+ public async Task<User?> GetUser(HttpContext requestContext)
{
- var session = GetSession(requestContext);
+ var session = await GetSession(requestContext).ConfigureAwait(false);
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
}
- public User? GetUser(object requestContext)
+ public Task<User?> GetUser(object requestContext)
{
return GetUser(((HttpRequest)requestContext).HttpContext);
}
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index 5d38ea0ca..7010a6fb0 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.HttpServer
public event EventHandler<EventArgs>? Closed;
/// <summary>
- /// Gets or sets the remote end point.
+ /// Gets the remote end point.
/// </summary>
public IPAddress? RemoteEndPoint { get; }
@@ -82,7 +82,7 @@ namespace Emby.Server.Implementations.HttpServer
public DateTime LastKeepAliveDate { get; set; }
/// <summary>
- /// Gets or sets the query string.
+ /// Gets the query string.
/// </summary>
/// <value>The query string.</value>
public IQueryCollection QueryString { get; }
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
index 861c0a95e..f86bfd755 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
@@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <inheritdoc />
public async Task WebSocketRequestHandler(HttpContext context)
{
- _ = _authService.Authenticate(context.Request);
+ _ = await _authService.Authenticate(context.Request).ConfigureAwait(false);
try
{
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 7c3c7da23..1bc229b0c 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -5,11 +5,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO
@@ -248,7 +246,7 @@ namespace Emby.Server.Implementations.IO
{
try
{
- using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
+ using (Stream thisFileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read, 1))
{
result.Length = thisFileStream.Length;
}
@@ -423,7 +421,7 @@ namespace Emby.Server.Implementations.IO
}
}
- public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
+ public virtual void SetAttributes(string path, bool isHidden, bool readOnly)
{
if (!OperatingSystem.IsWindows())
{
@@ -437,14 +435,14 @@ namespace Emby.Server.Implementations.IO
return;
}
- if (info.IsReadOnly == isReadOnly && info.IsHidden == isHidden)
+ if (info.IsReadOnly == readOnly && info.IsHidden == isHidden)
{
return;
}
var attributes = File.GetAttributes(path);
- if (isReadOnly)
+ if (readOnly)
{
attributes = attributes | FileAttributes.ReadOnly;
}
diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs
index a430b9e72..1d97882db 100644
--- a/Emby.Server.Implementations/IStartupOptions.cs
+++ b/Emby.Server.Implementations/IStartupOptions.cs
@@ -10,7 +10,7 @@ namespace Emby.Server.Implementations
string? FFmpegPath { get; }
/// <summary>
- /// Gets the value of the --service command line option.
+ /// Gets a value value indicating whether to run as service by the --service command line option.
/// </summary>
bool IsService { get; }
diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index 833fb0b7a..4a026fd21 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.Images
public int Order => 0;
- protected virtual bool Supports(BaseItem _) => true;
+ protected virtual bool Supports(BaseItem item) => true;
public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
index ff5f26ce0..0229fbae7 100644
--- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
@@ -30,27 +30,27 @@ namespace Emby.Server.Implementations.Images
string[] includeItemTypes;
- if (string.Equals(viewType, CollectionType.Movies))
+ if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Movie" };
}
- else if (string.Equals(viewType, CollectionType.TvShows))
+ else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Series" };
}
- else if (string.Equals(viewType, CollectionType.Music))
+ else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "MusicAlbum" };
}
- else if (string.Equals(viewType, CollectionType.Books))
+ else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Book", "AudioBook" };
}
- else if (string.Equals(viewType, CollectionType.BoxSets))
+ else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "BoxSet" };
}
- else if (string.Equals(viewType, CollectionType.HomeVideos) || string.Equals(viewType, CollectionType.Photos))
+ else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Video", "Photo" };
}
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index c7d113963..bc5b4499f 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library
if (parent != null)
{
// Ignore trailer folders but allow it at the collection level
- if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase)
+ if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)
&& !(parent is AggregateFolder)
&& !(parent is UserRootFolder))
{
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Library
if (parent != null)
{
// Don't resolve these into audio files
- if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFilename, StringComparison.Ordinal)
+ if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal)
&& _libraryManager.IsAudioFile(filename))
{
return true;
diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
index 6c65b5899..868071a99 100644
--- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
+++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
@@ -4,6 +4,7 @@
using System;
using System.Globalization;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
@@ -41,6 +42,11 @@ namespace Emby.Server.Implementations.Library
return _closeFn();
}
+ public Stream GetStream()
+ {
+ throw new NotSupportedException();
+ }
+
public Task Open(CancellationToken openCancellationToken)
{
return Task.CompletedTask;
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 13fb8b2fd..6f0f3d080 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -287,14 +287,14 @@ namespace Emby.Server.Implementations.Library
if (item is IItemByName)
{
- if (!(item is MusicArtist))
+ if (item is not MusicArtist)
{
return;
}
}
else if (!item.IsFolder)
{
- if (!(item is Video) && !(item is LiveTvChannel))
+ if (item is not Video && item is not LiveTvChannel)
{
return;
}
@@ -866,7 +866,7 @@ namespace Emby.Server.Implementations.Library
{
var path = Person.GetPath(name);
var id = GetItemByNameId<Person>(path);
- if (!(GetItemById(id) is Person item))
+ if (GetItemById(id) is not Person item)
{
item = new Person
{
@@ -1761,22 +1761,20 @@ namespace Emby.Server.Implementations.Library
return orderedItems ?? items;
}
- public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderByList)
+ public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy)
{
var isFirst = true;
IOrderedEnumerable<BaseItem> orderedItems = null;
- foreach (var orderBy in orderByList)
+ foreach (var (name, sortOrder) in orderBy)
{
- var comparer = GetComparer(orderBy.Item1, user);
+ var comparer = GetComparer(name, user);
if (comparer == null)
{
continue;
}
- var sortOrder = orderBy.Item2;
-
if (isFirst)
{
orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer);
@@ -2118,7 +2116,7 @@ namespace Emby.Server.Implementations.Library
public LibraryOptions GetLibraryOptions(BaseItem item)
{
- if (!(item is CollectionFolder collectionFolder))
+ if (item is not CollectionFolder collectionFolder)
{
// List.Find is more performant than FirstOrDefault due to enumerator allocation
collectionFolder = GetCollectionFolders(item)
@@ -2716,7 +2714,7 @@ namespace Emby.Server.Implementations.Library
var namingOptions = GetNamingOptions();
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => string.Equals(i.Name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase))
+ .Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
@@ -2760,7 +2758,7 @@ namespace Emby.Server.Implementations.Library
var namingOptions = GetNamingOptions();
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ .Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
@@ -3076,9 +3074,9 @@ namespace Emby.Server.Implementations.Library
});
}
- public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
+ public void AddMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
{
- AddMediaPathInternal(virtualFolderName, pathInfo, true);
+ AddMediaPathInternal(virtualFolderName, mediaPath, true);
}
private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions)
@@ -3131,11 +3129,11 @@ namespace Emby.Server.Implementations.Library
}
}
- public void UpdateMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
+ public void UpdateMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
{
- if (pathInfo == null)
+ if (mediaPath == null)
{
- throw new ArgumentNullException(nameof(pathInfo));
+ throw new ArgumentNullException(nameof(mediaPath));
}
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
@@ -3148,9 +3146,9 @@ namespace Emby.Server.Implementations.Library
var list = libraryOptions.PathInfos.ToList();
foreach (var originalPathInfo in list)
{
- if (string.Equals(pathInfo.Path, originalPathInfo.Path, StringComparison.Ordinal))
+ if (string.Equals(mediaPath.Path, originalPathInfo.Path, StringComparison.Ordinal))
{
- originalPathInfo.NetworkPath = pathInfo.NetworkPath;
+ originalPathInfo.NetworkPath = mediaPath.NetworkPath;
break;
}
}
@@ -3173,10 +3171,7 @@ namespace Emby.Server.Implementations.Library
{
if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal)))
{
- list.Add(new MediaPathInfo
- {
- Path = location
- });
+ list.Add(new MediaPathInfo(location));
}
}
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index 806269182..16b45161f 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -17,6 +17,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
@@ -49,7 +50,7 @@ namespace Emby.Server.Implementations.Library
{
try
{
- await using FileStream jsonStream = File.OpenRead(cacheFilePath);
+ await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
// _logger.LogDebug("Found cached media info");
@@ -86,7 +87,7 @@ namespace Emby.Server.Implementations.Library
if (cacheFilePath != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
- await using FileStream createStream = File.OpenWrite(cacheFilePath);
+ await using FileStream createStream = AsyncFile.OpenWrite(cacheFilePath);
await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 91c9e61cf..16231c73f 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -521,7 +521,7 @@ namespace Emby.Server.Implementations.Library
// TODO: @bond Fix
var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions);
- _logger.LogInformation("Live stream opened: " + json);
+ _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
if (!request.UserId.Equals(Guid.Empty))
@@ -587,13 +587,6 @@ namespace Emby.Server.Implementations.Library
mediaSource.InferTotalBitrate();
}
- public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
- {
- var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
-
- return Task.FromResult(info.Value as IDirectStreamProvider);
- }
-
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
{
var result = await OpenLiveStreamInternal(request, cancellationToken).ConfigureAwait(false);
@@ -602,7 +595,8 @@ namespace Emby.Server.Implementations.Library
public async Task<MediaSourceInfo> GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken)
{
- var liveStreamInfo = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false);
+ // TODO probably shouldn't throw here but it is kept for "backwards compatibility"
+ var liveStreamInfo = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException();
var mediaSource = liveStreamInfo.MediaSource;
@@ -638,7 +632,7 @@ namespace Emby.Server.Implementations.Library
{
try
{
- await using FileStream jsonStream = File.OpenRead(cacheFilePath);
+ await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
// _logger.LogDebug("Found cached media info");
@@ -771,18 +765,19 @@ namespace Emby.Server.Implementations.Library
mediaSource.InferTotalBitrate(true);
}
- public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken)
+ public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
- var info = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false);
- return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider);
+ // TODO probably shouldn't throw here but it is kept for "backwards compatibility"
+ var info = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException();
+ return Task.FromResult(new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider));
}
- private Task<ILiveStream> GetLiveStreamInfo(string id, CancellationToken cancellationToken)
+ public ILiveStream GetLiveStreamInfo(string id)
{
if (string.IsNullOrEmpty(id))
{
@@ -791,12 +786,16 @@ namespace Emby.Server.Implementations.Library
if (_openStreams.TryGetValue(id, out ILiveStream info))
{
- return Task.FromResult(info);
- }
- else
- {
- return Task.FromException<ILiveStream>(new ResourceNotFoundException());
+ return info;
}
+
+ return null;
+ }
+
+ /// <inheritdoc />
+ public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId)
+ {
+ return _openStreams.Values.FirstOrDefault(stream => string.Equals(uniqueId, stream?.UniqueId, StringComparison.OrdinalIgnoreCase));
}
public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index 06300adeb..e2f1fb0ad 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -36,9 +36,10 @@ namespace Emby.Server.Implementations.Library
return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList();
}
- public List<BaseItem> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions)
+ /// <inheritdoc />
+ public List<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions)
{
- return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
+ return GetInstantMixFromGenres(artist.Genres, user, dtoOptions);
}
public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions)
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index e893d6335..fd9747b4b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -21,11 +21,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// </summary>
public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
{
- private readonly ILibraryManager LibraryManager;
+ private readonly ILibraryManager _libraryManager;
public AudioResolver(ILibraryManager libraryManager)
{
- LibraryManager = libraryManager;
+ _libraryManager = libraryManager;
}
/// <summary>
@@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
var files = args.FileSystemChildren
- .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+ .Where(i => !_libraryManager.IgnoreFile(i, args.Parent))
.ToList();
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
- if (LibraryManager.IsAudioFile(args.Path))
+ if (_libraryManager.IsAudioFile(args.Path))
{
var extension = Path.GetExtension(args.Path);
@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
// For conflicting extensions, give priority to videos
- if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path))
+ if (isMixedCollectionType && _libraryManager.IsVideoFile(args.Path))
{
return null;
}
@@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
}
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
+ var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var resolver = new AudioBookListResolver(namingOptions);
var resolverResult = resolver.Resolve(files).ToList();
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index cdb492022..b102b86cf 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -5,6 +5,7 @@
using System;
using System.IO;
using System.Linq;
+using DiscUtils.Udf;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -16,17 +17,17 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <summary>
/// Resolves a Path into a Video or Video subclass.
/// </summary>
- /// <typeparam name="T"></typeparam>
+ /// <typeparam name="T">The type of item to resolve.</typeparam>
public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
where T : Video, new()
{
- protected readonly ILibraryManager LibraryManager;
-
protected BaseVideoResolver(ILibraryManager libraryManager)
{
LibraryManager = libraryManager;
}
+ protected ILibraryManager LibraryManager { get; }
+
/// <summary>
/// Resolves the specified args.
/// </summary>
@@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
break;
}
- if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService))
+ if (IsBluRayDirectory(filename))
{
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
@@ -201,6 +202,22 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
video.IsoType = IsoType.BluRay;
}
+ else
+ {
+ // use disc-utils, both DVDs and BDs use UDF filesystem
+ using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
+ {
+ UdfReader udfReader = new UdfReader(videoFileStream);
+ if (udfReader.DirectoryExists("VIDEO_TS"))
+ {
+ video.IsoType = IsoType.Dvd;
+ }
+ else if (udfReader.DirectoryExists("BDMV"))
+ {
+ video.IsoType = IsoType.BluRay;
+ }
+ }
+ }
}
}
@@ -279,25 +296,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
/// <summary>
- /// Determines whether [is blu ray directory] [the specified directory name].
+ /// Determines whether [is bluray directory] [the specified directory name].
/// </summary>
- protected bool IsBluRayDirectory(string fullPath, string directoryName, IDirectoryService directoryService)
+ /// <param name="directoryName">The directory name.</param>
+ /// <returns>Whether the directory is a bluray directory.</returns>
+ protected bool IsBluRayDirectory(string directoryName)
{
- if (!string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
-
- return true;
- // var blurayExtensions = new[]
- //{
- // ".mts",
- // ".m2ts",
- // ".bdmv",
- // ".mpls"
- //};
-
- // return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
+ return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
index 9599faea4..9599faea4 100644
--- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
index fa45ccf84..3f29ab191 100644
--- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
@@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <summary>
/// Class ItemResolver.
/// </summary>
- /// <typeparam name="T"></typeparam>
+ /// <typeparam name="T">The type of BaseItem.</typeparam>
public abstract class ItemResolver<T> : IItemResolver
where T : BaseItem, new()
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 889e29a6b..8b55a7744 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -400,7 +400,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return movie;
}
- if (IsBluRayDirectory(child.FullName, filename, directoryService))
+ if (IsBluRayDirectory(filename))
{
var movie = new T
{
@@ -481,7 +481,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return true;
}
- if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService)))
+ if (subfolders.Any(s => IsBluRayDirectory(s.Name)))
{
videoTypes.Add(VideoType.BluRay);
return true;
diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
index ecd44be47..2c4ead719 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
@@ -18,7 +18,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// </summary>
public class PlaylistResolver : FolderResolver<Playlist>
{
- private string[] _musicPlaylistCollectionTypes = new string[] {
+ private string[] _musicPlaylistCollectionTypes =
+ {
string.Empty,
CollectionType.Music
};
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index d6ae91056..cf2d22f4d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
if ((season != null ||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
args.HasParent<Series>())
- && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.Contains(parent.Name, StringComparer.OrdinalIgnoreCase)))
+ && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name)))
{
var episode = ResolveVideo<Episode>(args, false);
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index a1562abd3..4d8a6494c 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -8,7 +8,6 @@ using System.IO;
using Emby.Naming.TV;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index 8aa605a90..c4e230f21 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -177,6 +177,7 @@ namespace Emby.Server.Implementations.Library
return dto;
}
+ /// <inheritdoc />
public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions options)
{
var userData = GetUserData(user, item);
@@ -191,7 +192,7 @@ namespace Emby.Server.Implementations.Library
/// </summary>
/// <param name="data">The data.</param>
/// <returns>DtoUserItemData.</returns>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception>
private UserItemDataDto GetUserItemDataDto(UserItemData data)
{
if (data == null)
@@ -212,6 +213,7 @@ namespace Emby.Server.Implementations.Library
};
}
+ /// <inheritdoc />
public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks)
{
var playedToCompletion = false;
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
index 9a8c5f39d..8577d722e 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -87,12 +87,15 @@ namespace Emby.Server.Implementations.Library.Validators
foreach (var item in deadEntities)
{
- _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
+ _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
- _libraryManager.DeleteItem(item, new DeleteOptions
- {
- DeleteFileLocation = false
- }, false);
+ _libraryManager.DeleteItem(
+ item,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false
+ },
+ false);
}
progress.Report(100);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 3fcadf5b1..f6e0111b6 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -7,6 +5,7 @@ using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
@@ -33,7 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return targetFile;
}
- public Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ public Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
if (directStreamProvider != null)
{
@@ -45,23 +44,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
- Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
+ Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None))
+ using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO))
{
onStarted();
- _logger.LogInformation("Copying recording stream to file {0}", targetFile);
+ _logger.LogInformation("Copying recording to file {FilePath}", targetFile);
// The media source is infinite so we need to handle stopping ourselves
using var durationToken = new CancellationTokenSource(duration);
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
-
- await directStreamProvider.CopyToAsync(output, cancellationTokenSource.Token).ConfigureAwait(false);
+ var linkedCancellationToken = cancellationTokenSource.Token;
+
+ await using var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream());
+ await _streamHelper.CopyToAsync(
+ fileStream,
+ output,
+ IODefaults.CopyToBufferSize,
+ 1000,
+ linkedCancellationToken).ConfigureAwait(false);
}
- _logger.LogInformation("Recording completed to file {0}", targetFile);
+ _logger.LogInformation("Recording completed: {FilePath}", targetFile);
}
private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
@@ -71,10 +77,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Opened recording stream from tuner provider");
- Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
+ Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None);
+ await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, AsyncFile.UseAsyncIO);
onStarted();
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 797063120..026b6bc0b 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -159,8 +159,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
var recordingFolders = GetRecordingFolders().ToArray();
- var virtualFolders = _libraryManager.GetVirtualFolders()
- .ToList();
+ var virtualFolders = _libraryManager.GetVirtualFolders();
var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
@@ -177,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
continue;
}
- var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray();
+ var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo(i)).ToArray();
var libraryOptions = new LibraryOptions
{
@@ -210,7 +209,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var path in pathsToRemove)
{
- await RemovePathFromLibrary(path).ConfigureAwait(false);
+ await RemovePathFromLibraryAsync(path).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -219,13 +218,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private async Task RemovePathFromLibrary(string path)
+ private async Task RemovePathFromLibraryAsync(string path)
{
_logger.LogDebug("Removing path from library: {0}", path);
var requiresRefresh = false;
- var virtualFolders = _libraryManager.GetVirtualFolders()
- .ToList();
+ var virtualFolders = _libraryManager.GetVirtualFolders();
foreach (var virtualFolder in virtualFolders)
{
@@ -460,7 +458,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
{
var tunerChannelId = tunerChannel.TunerChannelId;
- if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1)
+ if (tunerChannelId.Contains(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase))
{
tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
}
@@ -612,16 +610,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new NotImplementedException();
}
- public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
+ public Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken)
{
- var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ?
+ var existingTimer = string.IsNullOrWhiteSpace(info.ProgramId) ?
null :
- _timerProvider.GetTimerByProgramId(timer.ProgramId);
+ _timerProvider.GetTimerByProgramId(info.ProgramId);
if (existingTimer != null)
{
- if (existingTimer.Status == RecordingStatus.Cancelled ||
- existingTimer.Status == RecordingStatus.Completed)
+ if (existingTimer.Status == RecordingStatus.Cancelled
+ || existingTimer.Status == RecordingStatus.Completed)
{
existingTimer.Status = RecordingStatus.New;
existingTimer.IsManual = true;
@@ -634,32 +632,32 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- timer.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+ info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
LiveTvProgram programInfo = null;
- if (!string.IsNullOrWhiteSpace(timer.ProgramId))
+ if (!string.IsNullOrWhiteSpace(info.ProgramId))
{
- programInfo = GetProgramInfoFromCache(timer);
+ programInfo = GetProgramInfoFromCache(info);
}
if (programInfo == null)
{
- _logger.LogInformation("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
- programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
+ _logger.LogInformation("Unable to find program with Id {0}. Will search using start date", info.ProgramId);
+ programInfo = GetProgramInfoFromCache(info.ChannelId, info.StartDate);
}
if (programInfo != null)
{
- CopyProgramInfoToTimerInfo(programInfo, timer);
+ CopyProgramInfoToTimerInfo(programInfo, info);
}
- timer.IsManual = true;
- _timerProvider.Add(timer);
+ info.IsManual = true;
+ _timerProvider.Add(info);
- TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
+ TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(info));
- return Task.FromResult(timer.Id);
+ return Task.FromResult(info.Id);
}
public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
@@ -913,18 +911,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
- List<ProgramInfo> programs;
-
if (epgChannel == null)
{
_logger.LogDebug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
- programs = new List<ProgramInfo>();
+ continue;
}
- else
- {
- programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
+
+ List<ProgramInfo> programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
.ConfigureAwait(false)).ToList();
- }
// Replace the value that came from the provider with a normalized value
foreach (var program in programs)
@@ -940,7 +934,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- return new List<ProgramInfo>();
+ return Enumerable.Empty<ProgramInfo>();
}
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
@@ -1292,7 +1286,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
- _logger.LogInformation("Writing file to path: " + recordPath);
+ _logger.LogInformation("Writing file to: {Path}", recordPath);
Action onStarted = async () =>
{
@@ -1417,13 +1411,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void TriggerRefresh(string path)
{
- _logger.LogInformation("Triggering refresh on {path}", path);
+ _logger.LogInformation("Triggering refresh on {Path}", path);
var item = GetAffectedBaseItem(Path.GetDirectoryName(path));
if (item != null)
{
- _logger.LogInformation("Refreshing recording parent {path}", item.Path);
+ _logger.LogInformation("Refreshing recording parent {Path}", item.Path);
_providerManager.QueueRefresh(
item.Id,
@@ -1458,7 +1452,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase))
{
var parentItem = item.GetParent();
- if (parentItem != null && !(parentItem is AggregateFolder))
+ if (parentItem != null && parentItem is not AggregateFolder)
{
item = parentItem;
}
@@ -1512,8 +1506,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
DeleteLibraryItemsForTimers(timersToDelete);
- var librarySeries = _libraryManager.FindByPath(seriesPath, true) as Folder;
- if (librarySeries == null)
+ if (_libraryManager.FindByPath(seriesPath, true) is not Folder librarySeries)
{
return;
}
@@ -1667,7 +1660,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- process.Exited += Process_Exited;
+ process.Exited += OnProcessExited;
process.Start();
}
catch (Exception ex)
@@ -1681,7 +1674,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return arguments.Replace("{path}", path, StringComparison.OrdinalIgnoreCase);
}
- private void Process_Exited(object sender, EventArgs e)
+ private void OnProcessExited(object sender, EventArgs e)
{
using (var process = (Process)sender)
{
@@ -2239,7 +2232,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var enabledTimersForSeries = new List<TimerInfo>();
foreach (var timer in allTimers)
{
- var existingTimer = _timerProvider.GetTimer(timer.Id)
+ var existingTimer = _timerProvider.GetTimer(timer.Id)
?? (string.IsNullOrWhiteSpace(timer.ProgramId)
? null
: _timerProvider.GetTimerByProgramId(timer.ProgramId));
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 93781cb7b..d806a0295 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO);
await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false);
await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false);
@@ -319,11 +319,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
}
- catch (ObjectDisposedException)
- {
- // TODO Investigate and properly fix.
- // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
- }
catch (Exception ex)
{
_logger.LogError(ex, "Error reading ffmpeg recording log");
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
index 0ec52a959..20a8213a7 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
@@ -8,7 +8,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
internal class EpgChannelData
{
-
private readonly Dictionary<string, ChannelInfo> _channelsById;
private readonly Dictionary<string, ChannelInfo> _channelsByNumber;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
index 4712724d6..dfe3517b2 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Records the specified media source.
/// </summary>
- Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
+ Task Record(IDirectStreamProvider? directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 6c52a9a73..a861e6ae4 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -23,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
}
- public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
+ public event EventHandler<GenericEventArgs<TimerInfo>>? TimerFired;
public void RestartTimers()
{
@@ -145,9 +143,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private void TimerCallback(object state)
+ private void TimerCallback(object? state)
{
- var timerId = (string)state;
+ var timerId = (string?)state ?? throw new ArgumentNullException(nameof(state));
var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
if (timer != null)
@@ -156,12 +154,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- public TimerInfo GetTimer(string id)
+ public TimerInfo? GetTimer(string id)
{
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
}
- public TimerInfo GetTimerByProgramId(string programId)
+ public TimerInfo? GetTimerByProgramId(string programId)
{
return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index b7639a51c..8125ed57d 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -14,8 +14,9 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common;
+using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
using Jellyfin.Extensions.Json;
+using MediaBrowser.Common;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Cryptography;
@@ -96,12 +97,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var dates = GetScheduleRequestDates(startDateUtc, endDateUtc);
_logger.LogInformation("Channel Station ID is: {ChannelID}", channelId);
- var requestList = new List<ScheduleDirect.RequestScheduleForChannel>()
+ var requestList = new List<RequestScheduleForChannelDto>()
{
- new ScheduleDirect.RequestScheduleForChannel()
+ new RequestScheduleForChannelDto()
{
- stationID = channelId,
- date = dates
+ StationId = channelId,
+ Date = dates
}
};
@@ -113,61 +114,61 @@ namespace Emby.Server.Implementations.LiveTv.Listings
options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var dailySchedules = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Day>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var dailySchedules = await JsonSerializer.DeserializeAsync<List<DayDto>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
programRequestOptions.Headers.TryAddWithoutValidation("token", token);
- var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
+ var programsID = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var programDetails = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- var programDict = programDetails.ToDictionary(p => p.programID, y => y);
+ var programDetails = await JsonSerializer.DeserializeAsync<List<ProgramDetailsDto>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y);
var programIdsWithImages = programDetails
- .Where(p => p.hasImageArtwork).Select(p => p.programID)
+ .Where(p => p.HasImageArtwork).Select(p => p.ProgramId)
.ToList();
var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
var programsInfo = new List<ProgramInfo>();
- foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
+ foreach (ProgramDto schedule in dailySchedules.SelectMany(d => d.Programs))
{
// _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
// " which corresponds to channel " + channelNumber + " and program id " +
- // schedule.programID + " which says it has images? " +
- // programDict[schedule.programID].hasImageArtwork);
+ // schedule.ProgramId + " which says it has images? " +
+ // programDict[schedule.ProgramId].hasImageArtwork);
if (images != null)
{
- var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
+ var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]);
if (imageIndex > -1)
{
- var programEntry = programDict[schedule.programID];
+ var programEntry = programDict[schedule.ProgramId];
- var allImages = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
- var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase));
- var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase));
+ var allImages = images[imageIndex].Data ?? new List<ImageDataDto>();
+ var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase));
+ var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase));
const double DesiredAspect = 2.0 / 3;
- programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
+ programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
GetProgramImage(ApiUrl, allImages, true, DesiredAspect);
const double WideAspect = 16.0 / 9;
- programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
+ programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
// Don't supply the same image twice
- if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal))
+ if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
{
- programEntry.thumbImage = null;
+ programEntry.ThumbImage = null;
}
- programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
+ programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
@@ -176,15 +177,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID]));
+ programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.ProgramId]));
}
return programsInfo;
}
- private static int GetSizeOrder(ScheduleDirect.ImageData image)
+ private static int GetSizeOrder(ImageDataDto image)
{
- if (int.TryParse(image.height, out int value))
+ if (int.TryParse(image.Height, out int value))
{
return value;
}
@@ -192,53 +193,53 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return 0;
}
- private static string GetChannelNumber(ScheduleDirect.Map map)
+ private static string GetChannelNumber(MapDto map)
{
- var channelNumber = map.logicalChannelNumber;
+ var channelNumber = map.LogicalChannelNumber;
if (string.IsNullOrWhiteSpace(channelNumber))
{
- channelNumber = map.channel;
+ channelNumber = map.Channel;
}
if (string.IsNullOrWhiteSpace(channelNumber))
{
- channelNumber = map.atscMajor + "." + map.atscMinor;
+ channelNumber = map.AtscMajor + "." + map.AtscMinor;
}
return channelNumber.TrimStart('0');
}
- private static bool IsMovie(ScheduleDirect.ProgramDetails programInfo)
+ private static bool IsMovie(ProgramDetailsDto programInfo)
{
- return string.Equals(programInfo.entityType, "movie", StringComparison.OrdinalIgnoreCase);
+ return string.Equals(programInfo.EntityType, "movie", StringComparison.OrdinalIgnoreCase);
}
- private ProgramInfo GetProgram(string channelId, ScheduleDirect.Program programInfo, ScheduleDirect.ProgramDetails details)
+ private ProgramInfo GetProgram(string channelId, ProgramDto programInfo, ProgramDetailsDto details)
{
- var startAt = GetDate(programInfo.airDateTime);
- var endAt = startAt.AddSeconds(programInfo.duration);
+ var startAt = GetDate(programInfo.AirDateTime);
+ var endAt = startAt.AddSeconds(programInfo.Duration);
var audioType = ProgramAudio.Stereo;
- var programId = programInfo.programID ?? string.Empty;
+ var programId = programInfo.ProgramId ?? string.Empty;
string newID = programId + "T" + startAt.Ticks + "C" + channelId;
- if (programInfo.audioProperties != null)
+ if (programInfo.AudioProperties != null)
{
- if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
+ if (programInfo.AudioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.Atmos;
}
- else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
+ else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.DolbyDigital;
}
- else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase)))
+ else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.DolbyDigital;
}
- else if (programInfo.audioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase)))
+ else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.Stereo;
}
@@ -249,9 +250,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
string episodeTitle = null;
- if (details.episodeTitle150 != null)
+ if (details.EpisodeTitle150 != null)
{
- episodeTitle = details.episodeTitle150;
+ episodeTitle = details.EpisodeTitle150;
}
var info = new ProgramInfo
@@ -260,22 +261,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Id = newID,
StartDate = startAt,
EndDate = endAt,
- Name = details.titles[0].title120 ?? "Unknown",
+ Name = details.Titles[0].Title120 ?? "Unknown",
OfficialRating = null,
CommunityRating = null,
EpisodeTitle = episodeTitle,
Audio = audioType,
// IsNew = programInfo.@new ?? false,
- IsRepeat = programInfo.@new == null,
- IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase),
- ImageUrl = details.primaryImage,
- ThumbImageUrl = details.thumbImage,
- IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
- IsSports = string.Equals(details.entityType, "sports", StringComparison.OrdinalIgnoreCase),
+ IsRepeat = programInfo.New == null,
+ IsSeries = string.Equals(details.EntityType, "episode", StringComparison.OrdinalIgnoreCase),
+ ImageUrl = details.PrimaryImage,
+ ThumbImageUrl = details.ThumbImage,
+ IsKids = string.Equals(details.Audience, "children", StringComparison.OrdinalIgnoreCase),
+ IsSports = string.Equals(details.EntityType, "sports", StringComparison.OrdinalIgnoreCase),
IsMovie = IsMovie(details),
- Etag = programInfo.md5,
- IsLive = string.Equals(programInfo.liveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
- IsPremiere = programInfo.premiere || (programInfo.isPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
+ Etag = programInfo.Md5,
+ IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
+ IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
};
var showId = programId;
@@ -298,15 +299,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
info.ShowId = showId;
- if (programInfo.videoProperties != null)
+ if (programInfo.VideoProperties != null)
{
- info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
- info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
+ info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
+ info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
}
- if (details.contentRating != null && details.contentRating.Count > 0)
+ if (details.ContentRating != null && details.ContentRating.Count > 0)
{
- info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-", StringComparison.Ordinal)
+ info.OfficialRating = details.ContentRating[0].Code.Replace("TV", "TV-", StringComparison.Ordinal)
.Replace("--", "-", StringComparison.Ordinal);
var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
@@ -316,15 +317,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- if (details.descriptions != null)
+ if (details.Descriptions != null)
{
- if (details.descriptions.description1000 != null && details.descriptions.description1000.Count > 0)
+ if (details.Descriptions.Description1000 != null && details.Descriptions.Description1000.Count > 0)
{
- info.Overview = details.descriptions.description1000[0].description;
+ info.Overview = details.Descriptions.Description1000[0].Description;
}
- else if (details.descriptions.description100 != null && details.descriptions.description100.Count > 0)
+ else if (details.Descriptions.Description100 != null && details.Descriptions.Description100.Count > 0)
{
- info.Overview = details.descriptions.description100[0].description;
+ info.Overview = details.Descriptions.Description100[0].Description;
}
}
@@ -334,18 +335,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
info.SeriesProviderIds[MetadataProvider.Zap2It.ToString()] = info.SeriesId;
- if (details.metadata != null)
+ if (details.Metadata != null)
{
- foreach (var metadataProgram in details.metadata)
+ foreach (var metadataProgram in details.Metadata)
{
var gracenote = metadataProgram.Gracenote;
if (gracenote != null)
{
- info.SeasonNumber = gracenote.season;
+ info.SeasonNumber = gracenote.Season;
- if (gracenote.episode > 0)
+ if (gracenote.Episode > 0)
{
- info.EpisodeNumber = gracenote.episode;
+ info.EpisodeNumber = gracenote.Episode;
}
break;
@@ -354,25 +355,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- if (!string.IsNullOrWhiteSpace(details.originalAirDate))
+ if (!string.IsNullOrWhiteSpace(details.OriginalAirDate))
{
- info.OriginalAirDate = DateTime.Parse(details.originalAirDate, CultureInfo.InvariantCulture);
+ info.OriginalAirDate = DateTime.Parse(details.OriginalAirDate, CultureInfo.InvariantCulture);
info.ProductionYear = info.OriginalAirDate.Value.Year;
}
- if (details.movie != null)
+ if (details.Movie != null)
{
- if (!string.IsNullOrEmpty(details.movie.year)
- && int.TryParse(details.movie.year, out int year))
+ if (!string.IsNullOrEmpty(details.Movie.Year)
+ && int.TryParse(details.Movie.Year, out int year))
{
info.ProductionYear = year;
}
}
- if (details.genres != null)
+ if (details.Genres != null)
{
- info.Genres = details.genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
- info.IsNews = details.genres.Contains("news", StringComparer.OrdinalIgnoreCase);
+ info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
+ info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase);
if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase))
{
@@ -395,11 +396,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return date;
}
- private string GetProgramImage(string apiUrl, IEnumerable<ScheduleDirect.ImageData> images, bool returnDefaultImage, double desiredAspect)
+ private string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, bool returnDefaultImage, double desiredAspect)
{
var match = images
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
- .ThenByDescending(GetSizeOrder)
+ .ThenByDescending(i => GetSizeOrder(i))
.FirstOrDefault();
if (match == null)
@@ -407,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return null;
}
- var uri = match.uri;
+ var uri = match.Uri;
if (string.IsNullOrWhiteSpace(uri))
{
@@ -423,19 +424,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- private static double GetAspectRatio(ScheduleDirect.ImageData i)
+ private static double GetAspectRatio(ImageDataDto i)
{
int width = 0;
int height = 0;
- if (!string.IsNullOrWhiteSpace(i.width))
+ if (!string.IsNullOrWhiteSpace(i.Width))
{
- int.TryParse(i.width, out width);
+ _ = int.TryParse(i.Width, out width);
}
- if (!string.IsNullOrWhiteSpace(i.height))
+ if (!string.IsNullOrWhiteSpace(i.Height))
{
- int.TryParse(i.height, out height);
+ _ = int.TryParse(i.Height, out height);
}
if (height == 0 || width == 0)
@@ -448,14 +449,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return result;
}
- private async Task<List<ScheduleDirect.ShowImages>> GetImageForPrograms(
+ private async Task<List<ShowImagesDto>> GetImageForPrograms(
ListingsProviderInfo info,
IReadOnlyList<string> programIds,
CancellationToken cancellationToken)
{
if (programIds.Count == 0)
{
- return new List<ScheduleDirect.ShowImages>();
+ return new List<ShowImagesDto>();
}
StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
@@ -479,13 +480,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ShowImages>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ return await JsonSerializer.DeserializeAsync<List<ShowImagesDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting image info from schedules direct");
- return new List<ScheduleDirect.ShowImages>();
+ return new List<ShowImagesDto>();
}
}
@@ -508,18 +509,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Headends>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync<List<HeadendsDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (root != null)
{
- foreach (ScheduleDirect.Headends headend in root)
+ foreach (HeadendsDto headend in root)
{
- foreach (ScheduleDirect.Lineup lineup in headend.lineups)
+ foreach (LineupDto lineup in headend.Lineups)
{
lineups.Add(new NameIdPair
{
- Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name,
- Id = lineup.uri.Substring(18)
+ Name = string.IsNullOrWhiteSpace(lineup.Name) ? lineup.Lineup : lineup.Name,
+ Id = lineup.Uri[18..]
});
}
}
@@ -649,14 +650,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Token>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- if (string.Equals(root.message, "OK", StringComparison.Ordinal))
+ var root = await JsonSerializer.DeserializeAsync<TokenDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ if (string.Equals(root.Message, "OK", StringComparison.Ordinal))
{
- _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
- return root.token;
+ _logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token);
+ return root.Token;
}
- throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
+ throw new Exception("Could not authenticate with Schedules Direct Error: " + root.Message);
}
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
@@ -705,9 +706,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
httpResponse.EnsureSuccessStatusCode();
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var response = httpResponse.Content;
- var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Lineups>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync<LineupsDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
+ return root.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase));
}
catch (HttpRequestException ex)
{
@@ -777,35 +778,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Channel>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
+ var root = await JsonSerializer.DeserializeAsync<ChannelDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.Map.Count);
_logger.LogInformation("Mapping Stations to Channel");
- var allStations = root.stations ?? new List<ScheduleDirect.Station>();
+ var allStations = root.Stations ?? new List<StationDto>();
- var map = root.map;
+ var map = root.Map;
var list = new List<ChannelInfo>(map.Count);
foreach (var channel in map)
{
var channelNumber = GetChannelNumber(channel);
- var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase))
- ?? new ScheduleDirect.Station
+ var station = allStations.Find(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase))
+ ?? new StationDto
{
- stationID = channel.stationID
+ StationId = channel.StationId
};
var channelInfo = new ChannelInfo
{
- Id = station.stationID,
- CallSign = station.callsign,
+ Id = station.StationId,
+ CallSign = station.Callsign,
Number = channelNumber,
- Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name
+ Name = string.IsNullOrWhiteSpace(station.Name) ? channelNumber : station.Name
};
- if (station.logo != null)
+ if (station.Logo != null)
{
- channelInfo.ImageUrl = station.logo.URL;
+ channelInfo.ImageUrl = station.Logo.Url;
}
list.Add(channelInfo);
@@ -818,402 +819,5 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
}
-
- public class ScheduleDirect
- {
- public class Token
- {
- public int code { get; set; }
-
- public string message { get; set; }
-
- public string serverID { get; set; }
-
- public string token { get; set; }
- }
-
- public class Lineup
- {
- public string lineup { get; set; }
-
- public string name { get; set; }
-
- public string transport { get; set; }
-
- public string location { get; set; }
-
- public string uri { get; set; }
- }
-
- public class Lineups
- {
- public int code { get; set; }
-
- public string serverID { get; set; }
-
- public string datetime { get; set; }
-
- public List<Lineup> lineups { get; set; }
- }
-
- public class Headends
- {
- public string headend { get; set; }
-
- public string transport { get; set; }
-
- public string location { get; set; }
-
- public List<Lineup> lineups { get; set; }
- }
-
- public class Map
- {
- public string stationID { get; set; }
-
- public string channel { get; set; }
-
- public string logicalChannelNumber { get; set; }
-
- public int uhfVhf { get; set; }
-
- public int atscMajor { get; set; }
-
- public int atscMinor { get; set; }
- }
-
- public class Broadcaster
- {
- public string city { get; set; }
-
- public string state { get; set; }
-
- public string postalcode { get; set; }
-
- public string country { get; set; }
- }
-
- public class Logo
- {
- public string URL { get; set; }
-
- public int height { get; set; }
-
- public int width { get; set; }
-
- public string md5 { get; set; }
- }
-
- public class Station
- {
- public string stationID { get; set; }
-
- public string name { get; set; }
-
- public string callsign { get; set; }
-
- public List<string> broadcastLanguage { get; set; }
-
- public List<string> descriptionLanguage { get; set; }
-
- public Broadcaster broadcaster { get; set; }
-
- public string affiliate { get; set; }
-
- public Logo logo { get; set; }
-
- public bool? isCommercialFree { get; set; }
- }
-
- public class Metadata
- {
- public string lineup { get; set; }
-
- public string modified { get; set; }
-
- public string transport { get; set; }
- }
-
- public class Channel
- {
- public List<Map> map { get; set; }
-
- public List<Station> stations { get; set; }
-
- public Metadata metadata { get; set; }
- }
-
- public class RequestScheduleForChannel
- {
- public string stationID { get; set; }
-
- public List<string> date { get; set; }
- }
-
- public class Rating
- {
- public string body { get; set; }
-
- public string code { get; set; }
- }
-
- public class Multipart
- {
- public int partNumber { get; set; }
-
- public int totalParts { get; set; }
- }
-
- public class Program
- {
- public string programID { get; set; }
-
- public string airDateTime { get; set; }
-
- public int duration { get; set; }
-
- public string md5 { get; set; }
-
- public List<string> audioProperties { get; set; }
-
- public List<string> videoProperties { get; set; }
-
- public List<Rating> ratings { get; set; }
-
- public bool? @new { get; set; }
-
- public Multipart multipart { get; set; }
-
- public string liveTapeDelay { get; set; }
-
- public bool premiere { get; set; }
-
- public bool repeat { get; set; }
-
- public string isPremiereOrFinale { get; set; }
- }
-
- public class MetadataSchedule
- {
- public string modified { get; set; }
-
- public string md5 { get; set; }
-
- public string startDate { get; set; }
-
- public string endDate { get; set; }
-
- public int days { get; set; }
- }
-
- public class Day
- {
- public string stationID { get; set; }
-
- public List<Program> programs { get; set; }
-
- public MetadataSchedule metadata { get; set; }
-
- public Day()
- {
- programs = new List<Program>();
- }
- }
-
- public class Title
- {
- public string title120 { get; set; }
- }
-
- public class EventDetails
- {
- public string subType { get; set; }
- }
-
- public class Description100
- {
- public string descriptionLanguage { get; set; }
-
- public string description { get; set; }
- }
-
- public class Description1000
- {
- public string descriptionLanguage { get; set; }
-
- public string description { get; set; }
- }
-
- public class DescriptionsProgram
- {
- public List<Description100> description100 { get; set; }
-
- public List<Description1000> description1000 { get; set; }
- }
-
- public class Gracenote
- {
- public int season { get; set; }
-
- public int episode { get; set; }
- }
-
- public class MetadataPrograms
- {
- public Gracenote Gracenote { get; set; }
- }
-
- public class ContentRating
- {
- public string body { get; set; }
-
- public string code { get; set; }
- }
-
- public class Cast
- {
- public string billingOrder { get; set; }
-
- public string role { get; set; }
-
- public string nameId { get; set; }
-
- public string personId { get; set; }
-
- public string name { get; set; }
-
- public string characterName { get; set; }
- }
-
- public class Crew
- {
- public string billingOrder { get; set; }
-
- public string role { get; set; }
-
- public string nameId { get; set; }
-
- public string personId { get; set; }
-
- public string name { get; set; }
- }
-
- public class QualityRating
- {
- public string ratingsBody { get; set; }
-
- public string rating { get; set; }
-
- public string minRating { get; set; }
-
- public string maxRating { get; set; }
-
- public string increment { get; set; }
- }
-
- public class Movie
- {
- public string year { get; set; }
-
- public int duration { get; set; }
-
- public List<QualityRating> qualityRating { get; set; }
- }
-
- public class Recommendation
- {
- public string programID { get; set; }
-
- public string title120 { get; set; }
- }
-
- public class ProgramDetails
- {
- public string audience { get; set; }
-
- public string programID { get; set; }
-
- public List<Title> titles { get; set; }
-
- public EventDetails eventDetails { get; set; }
-
- public DescriptionsProgram descriptions { get; set; }
-
- public string originalAirDate { get; set; }
-
- public List<string> genres { get; set; }
-
- public string episodeTitle150 { get; set; }
-
- public List<MetadataPrograms> metadata { get; set; }
-
- public List<ContentRating> contentRating { get; set; }
-
- public List<Cast> cast { get; set; }
-
- public List<Crew> crew { get; set; }
-
- public string entityType { get; set; }
-
- public string showType { get; set; }
-
- public bool hasImageArtwork { get; set; }
-
- public string primaryImage { get; set; }
-
- public string thumbImage { get; set; }
-
- public string backdropImage { get; set; }
-
- public string bannerImage { get; set; }
-
- public string imageID { get; set; }
-
- public string md5 { get; set; }
-
- public List<string> contentAdvisory { get; set; }
-
- public Movie movie { get; set; }
-
- public List<Recommendation> recommendations { get; set; }
- }
-
- public class Caption
- {
- public string content { get; set; }
-
- public string lang { get; set; }
- }
-
- public class ImageData
- {
- public string width { get; set; }
-
- public string height { get; set; }
-
- public string uri { get; set; }
-
- public string size { get; set; }
-
- public string aspect { get; set; }
-
- public string category { get; set; }
-
- public string text { get; set; }
-
- public string primary { get; set; }
-
- public string tier { get; set; }
-
- public Caption caption { get; set; }
- }
-
- public class ShowImages
- {
- public string programID { get; set; }
-
- public List<ImageData> data { get; set; }
- }
- }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
new file mode 100644
index 000000000..b881b307c
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
@@ -0,0 +1,36 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Broadcaster dto.
+ /// </summary>
+ public class BroadcasterDto
+ {
+ /// <summary>
+ /// Gets or sets the city.
+ /// </summary>
+ [JsonPropertyName("city")]
+ public string City { get; set; }
+
+ /// <summary>
+ /// Gets or sets the state.
+ /// </summary>
+ [JsonPropertyName("state")]
+ public string State { get; set; }
+
+ /// <summary>
+ /// Gets or sets the postal code.
+ /// </summary>
+ [JsonPropertyName("postalCode")]
+ public string Postalcode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the country.
+ /// </summary>
+ [JsonPropertyName("country")]
+ public string Country { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
new file mode 100644
index 000000000..96b67d1eb
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Caption dto.
+ /// </summary>
+ public class CaptionDto
+ {
+ /// <summary>
+ /// Gets or sets the content.
+ /// </summary>
+ [JsonPropertyName("content")]
+ public string Content { get; set; }
+
+ /// <summary>
+ /// Gets or sets the lang.
+ /// </summary>
+ [JsonPropertyName("lang")]
+ public string Lang { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
new file mode 100644
index 000000000..dac6f5f3e
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
@@ -0,0 +1,48 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Cast dto.
+ /// </summary>
+ public class CastDto
+ {
+ /// <summary>
+ /// Gets or sets the billing order.
+ /// </summary>
+ [JsonPropertyName("billingOrder")]
+ public string BillingOrder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the role.
+ /// </summary>
+ [JsonPropertyName("role")]
+ public string Role { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name id.
+ /// </summary>
+ [JsonPropertyName("nameId")]
+ public string NameId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the person id.
+ /// </summary>
+ [JsonPropertyName("personId")]
+ public string PersonId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the character name.
+ /// </summary>
+ [JsonPropertyName("characterName")]
+ public string CharacterName { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
new file mode 100644
index 000000000..8c9c2c1fc
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
@@ -0,0 +1,31 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Channel dto.
+ /// </summary>
+ public class ChannelDto
+ {
+ /// <summary>
+ /// Gets or sets the list of maps.
+ /// </summary>
+ [JsonPropertyName("map")]
+ public List<MapDto> Map { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of stations.
+ /// </summary>
+ [JsonPropertyName("stations")]
+ public List<StationDto> Stations { get; set; }
+
+ /// <summary>
+ /// Gets or sets the metadata.
+ /// </summary>
+ [JsonPropertyName("metadata")]
+ public MetadataDto Metadata { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
new file mode 100644
index 000000000..135b5bb08
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Content rating dto.
+ /// </summary>
+ public class ContentRatingDto
+ {
+ /// <summary>
+ /// Gets or sets the body.
+ /// </summary>
+ [JsonPropertyName("body")]
+ public string Body { get; set; }
+
+ /// <summary>
+ /// Gets or sets the code.
+ /// </summary>
+ [JsonPropertyName("code")]
+ public string Code { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
new file mode 100644
index 000000000..82d1001c8
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
@@ -0,0 +1,42 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Crew dto.
+ /// </summary>
+ public class CrewDto
+ {
+ /// <summary>
+ /// Gets or sets the billing order.
+ /// </summary>
+ [JsonPropertyName("billingOrder")]
+ public string BillingOrder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the role.
+ /// </summary>
+ [JsonPropertyName("role")]
+ public string Role { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name id.
+ /// </summary>
+ [JsonPropertyName("nameId")]
+ public string NameId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the person id.
+ /// </summary>
+ [JsonPropertyName("personId")]
+ public string PersonId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
new file mode 100644
index 000000000..68876b068
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
@@ -0,0 +1,39 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Day dto.
+ /// </summary>
+ public class DayDto
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DayDto"/> class.
+ /// </summary>
+ public DayDto()
+ {
+ Programs = new List<ProgramDto>();
+ }
+
+ /// <summary>
+ /// Gets or sets the station id.
+ /// </summary>
+ [JsonPropertyName("stationID")]
+ public string StationId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of programs.
+ /// </summary>
+ [JsonPropertyName("programs")]
+ public List<ProgramDto> Programs { get; set; }
+
+ /// <summary>
+ /// Gets or sets the metadata schedule.
+ /// </summary>
+ [JsonPropertyName("metadata")]
+ public MetadataScheduleDto Metadata { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
new file mode 100644
index 000000000..d3e6ff393
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Description 1_000 dto.
+ /// </summary>
+ public class Description1000Dto
+ {
+ /// <summary>
+ /// Gets or sets the description language.
+ /// </summary>
+ [JsonPropertyName("descriptionLanguage")]
+ public string DescriptionLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the description.
+ /// </summary>
+ [JsonPropertyName("description")]
+ public string Description { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
new file mode 100644
index 000000000..04360266c
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Description 100 dto.
+ /// </summary>
+ public class Description100Dto
+ {
+ /// <summary>
+ /// Gets or sets the description language.
+ /// </summary>
+ [JsonPropertyName("descriptionLanguage")]
+ public string DescriptionLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the description.
+ /// </summary>
+ [JsonPropertyName("description")]
+ public string Description { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
new file mode 100644
index 000000000..3af36ae96
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
@@ -0,0 +1,25 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Descriptions program dto.
+ /// </summary>
+ public class DescriptionsProgramDto
+ {
+ /// <summary>
+ /// Gets or sets the list of description 100.
+ /// </summary>
+ [JsonPropertyName("description100")]
+ public List<Description100Dto> Description100 { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of description1000.
+ /// </summary>
+ [JsonPropertyName("description1000")]
+ public List<Description1000Dto> Description1000 { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
new file mode 100644
index 000000000..c3b2bd9c1
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
@@ -0,0 +1,18 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Event details dto.
+ /// </summary>
+ public class EventDetailsDto
+ {
+ /// <summary>
+ /// Gets or sets the sub type.
+ /// </summary>
+ [JsonPropertyName("subType")]
+ public string SubType { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
new file mode 100644
index 000000000..3d8bea362
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Gracenote dto.
+ /// </summary>
+ public class GracenoteDto
+ {
+ /// <summary>
+ /// Gets or sets the season.
+ /// </summary>
+ [JsonPropertyName("season")]
+ public int Season { get; set; }
+
+ /// <summary>
+ /// Gets or sets the episode.
+ /// </summary>
+ [JsonPropertyName("episode")]
+ public int Episode { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
new file mode 100644
index 000000000..1fb3decb2
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
@@ -0,0 +1,37 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Headends dto.
+ /// </summary>
+ public class HeadendsDto
+ {
+ /// <summary>
+ /// Gets or sets the headend.
+ /// </summary>
+ [JsonPropertyName("headend")]
+ public string Headend { get; set; }
+
+ /// <summary>
+ /// Gets or sets the transport.
+ /// </summary>
+ [JsonPropertyName("transport")]
+ public string Transport { get; set; }
+
+ /// <summary>
+ /// Gets or sets the location.
+ /// </summary>
+ [JsonPropertyName("location")]
+ public string Location { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of lineups.
+ /// </summary>
+ [JsonPropertyName("lineups")]
+ public List<LineupDto> Lineups { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
new file mode 100644
index 000000000..912e680dd
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
@@ -0,0 +1,72 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Image data dto.
+ /// </summary>
+ public class ImageDataDto
+ {
+ /// <summary>
+ /// Gets or sets the width.
+ /// </summary>
+ [JsonPropertyName("width")]
+ public string Width { get; set; }
+
+ /// <summary>
+ /// Gets or sets the height.
+ /// </summary>
+ [JsonPropertyName("height")]
+ public string Height { get; set; }
+
+ /// <summary>
+ /// Gets or sets the uri.
+ /// </summary>
+ [JsonPropertyName("uri")]
+ public string Uri { get; set; }
+
+ /// <summary>
+ /// Gets or sets the size.
+ /// </summary>
+ [JsonPropertyName("size")]
+ public string Size { get; set; }
+
+ /// <summary>
+ /// Gets or sets the aspect.
+ /// </summary>
+ [JsonPropertyName("aspect")]
+ public string aspect { get; set; }
+
+ /// <summary>
+ /// Gets or sets the category.
+ /// </summary>
+ [JsonPropertyName("category")]
+ public string Category { get; set; }
+
+ /// <summary>
+ /// Gets or sets the text.
+ /// </summary>
+ [JsonPropertyName("text")]
+ public string Text { get; set; }
+
+ /// <summary>
+ /// Gets or sets the primary.
+ /// </summary>
+ [JsonPropertyName("primary")]
+ public string Primary { get; set; }
+
+ /// <summary>
+ /// Gets or sets the tier.
+ /// </summary>
+ [JsonPropertyName("tier")]
+ public string Tier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the caption.
+ /// </summary>
+ [JsonPropertyName("caption")]
+ public CaptionDto Caption { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
new file mode 100644
index 000000000..52e920aa6
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
@@ -0,0 +1,42 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// The lineup dto.
+ /// </summary>
+ public class LineupDto
+ {
+ /// <summary>
+ /// Gets or sets the linup.
+ /// </summary>
+ [JsonPropertyName("lineup")]
+ public string Lineup { get; set; }
+
+ /// <summary>
+ /// Gets or sets the lineup name.
+ /// </summary>
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the transport.
+ /// </summary>
+ [JsonPropertyName("transport")]
+ public string Transport { get; set; }
+
+ /// <summary>
+ /// Gets or sets the location.
+ /// </summary>
+ [JsonPropertyName("location")]
+ public string Location { get; set; }
+
+ /// <summary>
+ /// Gets or sets the uri.
+ /// </summary>
+ [JsonPropertyName("uri")]
+ public string Uri { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
new file mode 100644
index 000000000..15139ba3b
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
@@ -0,0 +1,37 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Lineups dto.
+ /// </summary>
+ public class LineupsDto
+ {
+ /// <summary>
+ /// Gets or sets the response code.
+ /// </summary>
+ [JsonPropertyName("code")]
+ public int Code { get; set; }
+
+ /// <summary>
+ /// Gets or sets the server id.
+ /// </summary>
+ [JsonPropertyName("serverID")]
+ public string ServerId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the datetime.
+ /// </summary>
+ [JsonPropertyName("datetime")]
+ public string Datetime { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of lineups.
+ /// </summary>
+ [JsonPropertyName("lineups")]
+ public List<LineupDto> Lineups { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
new file mode 100644
index 000000000..7b235ed7f
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
@@ -0,0 +1,36 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Logo dto.
+ /// </summary>
+ public class LogoDto
+ {
+ /// <summary>
+ /// Gets or sets the url.
+ /// </summary>
+ [JsonPropertyName("URL")]
+ public string Url { get; set; }
+
+ /// <summary>
+ /// Gets or sets the height.
+ /// </summary>
+ [JsonPropertyName("height")]
+ public int Height { get; set; }
+
+ /// <summary>
+ /// Gets or sets the width.
+ /// </summary>
+ [JsonPropertyName("width")]
+ public int Width { get; set; }
+
+ /// <summary>
+ /// Gets or sets the md5.
+ /// </summary>
+ [JsonPropertyName("md5")]
+ public string Md5 { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
new file mode 100644
index 000000000..5140277b2
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
@@ -0,0 +1,48 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Map dto.
+ /// </summary>
+ public class MapDto
+ {
+ /// <summary>
+ /// Gets or sets the station id.
+ /// </summary>
+ [JsonPropertyName("stationID")]
+ public string StationId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the channel.
+ /// </summary>
+ [JsonPropertyName("channel")]
+ public string Channel { get; set; }
+
+ /// <summary>
+ /// Gets or sets the logical channel number.
+ /// </summary>
+ [JsonPropertyName("logicalChannelNumber")]
+ public string LogicalChannelNumber { get; set; }
+
+ /// <summary>
+ /// Gets or sets the uhfvhf.
+ /// </summary>
+ [JsonPropertyName("uhfVhf")]
+ public int UhfVhf { get; set; }
+
+ /// <summary>
+ /// Gets or sets the atsc major.
+ /// </summary>
+ [JsonPropertyName("atscMajor")]
+ public int AtscMajor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the atsc minor.
+ /// </summary>
+ [JsonPropertyName("atscMinor")]
+ public int AtscMinor { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
new file mode 100644
index 000000000..5a3893a35
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
@@ -0,0 +1,30 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Metadata dto.
+ /// </summary>
+ public class MetadataDto
+ {
+ /// <summary>
+ /// Gets or sets the linup.
+ /// </summary>
+ [JsonPropertyName("lineup")]
+ public string Lineup { get; set; }
+
+ /// <summary>
+ /// Gets or sets the modified timestamp.
+ /// </summary>
+ [JsonPropertyName("modified")]
+ public string Modified { get; set; }
+
+ /// <summary>
+ /// Gets or sets the transport.
+ /// </summary>
+ [JsonPropertyName("transport")]
+ public string Transport { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
new file mode 100644
index 000000000..4057e9802
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
@@ -0,0 +1,18 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Metadata programs dto.
+ /// </summary>
+ public class MetadataProgramsDto
+ {
+ /// <summary>
+ /// Gets or sets the gracenote object.
+ /// </summary>
+ [JsonPropertyName("gracenote")]
+ public GracenoteDto Gracenote { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
new file mode 100644
index 000000000..4979296da
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
@@ -0,0 +1,42 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Metadata schedule dto.
+ /// </summary>
+ public class MetadataScheduleDto
+ {
+ /// <summary>
+ /// Gets or sets the modified timestamp.
+ /// </summary>
+ [JsonPropertyName("modified")]
+ public string Modified { get; set; }
+
+ /// <summary>
+ /// Gets or sets the md5.
+ /// </summary>
+ [JsonPropertyName("md5")]
+ public string Md5 { get; set; }
+
+ /// <summary>
+ /// Gets or sets the start date.
+ /// </summary>
+ [JsonPropertyName("startDate")]
+ public string StartDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the end date.
+ /// </summary>
+ [JsonPropertyName("endDate")]
+ public string EndDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the days count.
+ /// </summary>
+ [JsonPropertyName("days")]
+ public int Days { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
new file mode 100644
index 000000000..48d731d89
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
@@ -0,0 +1,31 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Movie dto.
+ /// </summary>
+ public class MovieDto
+ {
+ /// <summary>
+ /// Gets or sets the year.
+ /// </summary>
+ [JsonPropertyName("year")]
+ public string Year { get; set; }
+
+ /// <summary>
+ /// Gets or sets the duration.
+ /// </summary>
+ [JsonPropertyName("duration")]
+ public int Duration { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of quality rating.
+ /// </summary>
+ [JsonPropertyName("qualityRating")]
+ public List<QualityRatingDto> QualityRating { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
new file mode 100644
index 000000000..42eddfff2
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Multipart dto.
+ /// </summary>
+ public class MultipartDto
+ {
+ /// <summary>
+ /// Gets or sets the part number.
+ /// </summary>
+ [JsonPropertyName("partNumber")]
+ public int PartNumber { get; set; }
+
+ /// <summary>
+ /// Gets or sets the total parts.
+ /// </summary>
+ [JsonPropertyName("totalParts")]
+ public int TotalParts { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
new file mode 100644
index 000000000..a84c47c12
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
@@ -0,0 +1,157 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Program details dto.
+ /// </summary>
+ public class ProgramDetailsDto
+ {
+ /// <summary>
+ /// Gets or sets the audience.
+ /// </summary>
+ [JsonPropertyName("audience")]
+ public string Audience { get; set; }
+
+ /// <summary>
+ /// Gets or sets the program id.
+ /// </summary>
+ [JsonPropertyName("programID")]
+ public string ProgramId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of titles.
+ /// </summary>
+ [JsonPropertyName("titles")]
+ public List<TitleDto> Titles { get; set; }
+
+ /// <summary>
+ /// Gets or sets the event details object.
+ /// </summary>
+ [JsonPropertyName("eventDetails")]
+ public EventDetailsDto EventDetails { get; set; }
+
+ /// <summary>
+ /// Gets or sets the descriptions.
+ /// </summary>
+ [JsonPropertyName("descriptions")]
+ public DescriptionsProgramDto Descriptions { get; set; }
+
+ /// <summary>
+ /// Gets or sets the original air date.
+ /// </summary>
+ [JsonPropertyName("originalAirDate")]
+ public string OriginalAirDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of genres.
+ /// </summary>
+ [JsonPropertyName("genres")]
+ public List<string> Genres { get; set; }
+
+ /// <summary>
+ /// Gets or sets the episode title.
+ /// </summary>
+ [JsonPropertyName("episodeTitle150")]
+ public string EpisodeTitle150 { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of metadata.
+ /// </summary>
+ [JsonPropertyName("metadata")]
+ public List<MetadataProgramsDto> Metadata { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of content raitings.
+ /// </summary>
+ [JsonPropertyName("contentRating")]
+ public List<ContentRatingDto> ContentRating { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of cast.
+ /// </summary>
+ [JsonPropertyName("cast")]
+ public List<CastDto> Cast { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of crew.
+ /// </summary>
+ [JsonPropertyName("crew")]
+ public List<CrewDto> Crew { get; set; }
+
+ /// <summary>
+ /// Gets or sets the entity type.
+ /// </summary>
+ [JsonPropertyName("entityType")]
+ public string EntityType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the show type.
+ /// </summary>
+ [JsonPropertyName("showType")]
+ public string ShowType { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether there is image artwork.
+ /// </summary>
+ [JsonPropertyName("hasImageArtwork")]
+ public bool HasImageArtwork { get; set; }
+
+ /// <summary>
+ /// Gets or sets the primary image.
+ /// </summary>
+ [JsonPropertyName("primaryImage")]
+ public string PrimaryImage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the thumb image.
+ /// </summary>
+ [JsonPropertyName("thumbImage")]
+ public string ThumbImage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the backdrop image.
+ /// </summary>
+ [JsonPropertyName("backdropImage")]
+ public string BackdropImage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the banner image.
+ /// </summary>
+ [JsonPropertyName("bannerImage")]
+ public string BannerImage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the image id.
+ /// </summary>
+ [JsonPropertyName("imageID")]
+ public string ImageId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the md5.
+ /// </summary>
+ [JsonPropertyName("md5")]
+ public string Md5 { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of content advisory.
+ /// </summary>
+ [JsonPropertyName("contentAdvisory")]
+ public List<string> ContentAdvisory { get; set; }
+
+ /// <summary>
+ /// Gets or sets the movie object.
+ /// </summary>
+ [JsonPropertyName("movie")]
+ public MovieDto Movie { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of recommendations.
+ /// </summary>
+ [JsonPropertyName("recommendations")]
+ public List<RecommendationDto> Recommendations { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
new file mode 100644
index 000000000..ad5389100
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
@@ -0,0 +1,91 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Program dto.
+ /// </summary>
+ public class ProgramDto
+ {
+ /// <summary>
+ /// Gets or sets the program id.
+ /// </summary>
+ [JsonPropertyName("programID")]
+ public string ProgramId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the air date time.
+ /// </summary>
+ [JsonPropertyName("airDateTime")]
+ public string AirDateTime { get; set; }
+
+ /// <summary>
+ /// Gets or sets the duration.
+ /// </summary>
+ [JsonPropertyName("duration")]
+ public int Duration { get; set; }
+
+ /// <summary>
+ /// Gets or sets the md5.
+ /// </summary>
+ [JsonPropertyName("md5")]
+ public string Md5 { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of audio properties.
+ /// </summary>
+ [JsonPropertyName("audioProperties")]
+ public List<string> AudioProperties { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of video properties.
+ /// </summary>
+ [JsonPropertyName("videoProperties")]
+ public List<string> VideoProperties { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of ratings.
+ /// </summary>
+ [JsonPropertyName("ratings")]
+ public List<RatingDto> Ratings { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this program is new.
+ /// </summary>
+ [JsonPropertyName("new")]
+ public bool? New { get; set; }
+
+ /// <summary>
+ /// Gets or sets the multipart object.
+ /// </summary>
+ [JsonPropertyName("multipart")]
+ public MultipartDto Multipart { get; set; }
+
+ /// <summary>
+ /// Gets or sets the live tape delay.
+ /// </summary>
+ [JsonPropertyName("liveTapeDelay")]
+ public string LiveTapeDelay { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this is the premiere.
+ /// </summary>
+ [JsonPropertyName("premiere")]
+ public bool Premiere { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this is a repeat.
+ /// </summary>
+ [JsonPropertyName("repeat")]
+ public bool Repeat { get; set; }
+
+ /// <summary>
+ /// Gets or sets the premiere or finale.
+ /// </summary>
+ [JsonPropertyName("isPremiereOrFinale")]
+ public string IsPremiereOrFinale { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
new file mode 100644
index 000000000..5cd0a7459
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
@@ -0,0 +1,42 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Quality rating dto.
+ /// </summary>
+ public class QualityRatingDto
+ {
+ /// <summary>
+ /// Gets or sets the ratings body.
+ /// </summary>
+ [JsonPropertyName("ratingsBody")]
+ public string RatingsBody { get; set; }
+
+ /// <summary>
+ /// Gets or sets the rating.
+ /// </summary>
+ [JsonPropertyName("rating")]
+ public string Rating { get; set; }
+
+ /// <summary>
+ /// Gets or sets the min rating.
+ /// </summary>
+ [JsonPropertyName("minRating")]
+ public string MinRating { get; set; }
+
+ /// <summary>
+ /// Gets or sets the max rating.
+ /// </summary>
+ [JsonPropertyName("maxRating")]
+ public string MaxRating { get; set; }
+
+ /// <summary>
+ /// Gets or sets the increment.
+ /// </summary>
+ [JsonPropertyName("increment")]
+ public string Increment { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
new file mode 100644
index 000000000..948b83144
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Rating dto.
+ /// </summary>
+ public class RatingDto
+ {
+ /// <summary>
+ /// Gets or sets the body.
+ /// </summary>
+ [JsonPropertyName("body")]
+ public string Body { get; set; }
+
+ /// <summary>
+ /// Gets or sets the code.
+ /// </summary>
+ [JsonPropertyName("code")]
+ public string Code { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
new file mode 100644
index 000000000..1308f45ce
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
@@ -0,0 +1,24 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Recommendation dto.
+ /// </summary>
+ public class RecommendationDto
+ {
+ /// <summary>
+ /// Gets or sets the program id.
+ /// </summary>
+ [JsonPropertyName("programID")]
+ public string ProgramId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the title.
+ /// </summary>
+ [JsonPropertyName("title120")]
+ public string Title120 { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
new file mode 100644
index 000000000..fb7a31ac8
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
@@ -0,0 +1,25 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Request schedule for channel dto.
+ /// </summary>
+ public class RequestScheduleForChannelDto
+ {
+ /// <summary>
+ /// Gets or sets the station id.
+ /// </summary>
+ [JsonPropertyName("stationID")]
+ public string StationId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of dates.
+ /// </summary>
+ [JsonPropertyName("date")]
+ public List<string> Date { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
new file mode 100644
index 000000000..34302370d
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
@@ -0,0 +1,25 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Show image dto.
+ /// </summary>
+ public class ShowImagesDto
+ {
+ /// <summary>
+ /// Gets or sets the program id.
+ /// </summary>
+ [JsonPropertyName("programID")]
+ public string ProgramId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the list of data.
+ /// </summary>
+ [JsonPropertyName("data")]
+ public List<ImageDataDto> Data { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
new file mode 100644
index 000000000..12f3576c6
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
@@ -0,0 +1,67 @@
+#nullable disable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Station dto.
+ /// </summary>
+ public class StationDto
+ {
+ /// <summary>
+ /// Gets or sets the station id.
+ /// </summary>
+ [JsonPropertyName("stationID")]
+ public string StationId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the callsign.
+ /// </summary>
+ [JsonPropertyName("callsign")]
+ public string Callsign { get; set; }
+
+ /// <summary>
+ /// Gets or sets the broadcast language.
+ /// </summary>
+ [JsonPropertyName("broadcastLanguage")]
+ public List<string> BroadcastLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the description language.
+ /// </summary>
+ [JsonPropertyName("descriptionLanguage")]
+ public List<string> DescriptionLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the broadcaster.
+ /// </summary>
+ [JsonPropertyName("broadcaster")]
+ public BroadcasterDto Broadcaster { get; set; }
+
+ /// <summary>
+ /// Gets or sets the affiliate.
+ /// </summary>
+ [JsonPropertyName("affiliate")]
+ public string Affiliate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the logo.
+ /// </summary>
+ [JsonPropertyName("logo")]
+ public LogoDto Logo { get; set; }
+
+ /// <summary>
+ /// Gets or set a value indicating whether it is commercial free.
+ /// </summary>
+ [JsonPropertyName("isCommercialFree")]
+ public bool? IsCommercialFree { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
new file mode 100644
index 000000000..06c95524b
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
@@ -0,0 +1,18 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// Title dto.
+ /// </summary>
+ public class TitleDto
+ {
+ /// <summary>
+ /// Gets or sets the title.
+ /// </summary>
+ [JsonPropertyName("title120")]
+ public string Title120 { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
new file mode 100644
index 000000000..c3ec1c7d6
--- /dev/null
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
@@ -0,0 +1,36 @@
+#nullable disable
+
+using System.Text.Json.Serialization;
+
+namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+{
+ /// <summary>
+ /// The token dto.
+ /// </summary>
+ public class TokenDto
+ {
+ /// <summary>
+ /// Gets or sets the response code.
+ /// </summary>
+ [JsonPropertyName("code")]
+ public int Code { get; set; }
+
+ /// <summary>
+ /// Gets or sets the response message.
+ /// </summary>
+ [JsonPropertyName("message")]
+ public string Message { get; set; }
+
+ /// <summary>
+ /// Gets or sets the server id.
+ /// </summary>
+ [JsonPropertyName("serverID")]
+ public string ServerId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the token.
+ /// </summary>
+ [JsonPropertyName("token")]
+ public string Token { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index ebad4eddf..cb9801c17 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -10,6 +10,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions;
using Jellyfin.XmlTv;
using Jellyfin.XmlTv.Entities;
using MediaBrowser.Common.Extensions;
@@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(path, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew))
+ await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, AsyncFile.UseAsyncIO))
{
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
@@ -89,11 +90,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return UnzipIfNeeded(path, cacheFile);
}
- private string UnzipIfNeeded(string originalUrl, string file)
+ private string UnzipIfNeeded(ReadOnlySpan<char> originalUrl, string file)
{
- string ext = Path.GetExtension(originalUrl.Split('?')[0]);
+ ReadOnlySpan<char> ext = Path.GetExtension(originalUrl.LeftPart('?'));
- if (string.Equals(ext, ".gz", StringComparison.OrdinalIgnoreCase))
+ if (ext.Equals(".gz", StringComparison.OrdinalIgnoreCase))
{
try
{
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index d964769b5..ea1a28fe8 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -65,6 +65,8 @@ namespace Emby.Server.Implementations.LiveTv
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
+ private bool _disposed = false;
+
public LiveTvManager(
IServerConfigurationManager config,
ILogger<LiveTvManager> logger,
@@ -403,7 +405,7 @@ namespace Emby.Server.Implementations.LiveTv
// Set the total bitrate if not already supplied
mediaSource.InferTotalBitrate();
- if (!(service is EmbyTV.EmbyTV))
+ if (service is not EmbyTV.EmbyTV)
{
// We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
// mediaSource.SupportsDirectPlay = false;
@@ -520,7 +522,7 @@ namespace Emby.Server.Implementations.LiveTv
return item;
}
- private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
+ private (LiveTvProgram item, bool isNew, bool isUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
{
var id = _tvDtoService.GetInternalProgramId(info.Id);
@@ -559,8 +561,6 @@ namespace Emby.Server.Implementations.LiveTv
item.ParentId = channel.Id;
- // item.ChannelType = channelType;
-
item.Audio = info.Audio;
item.ChannelId = channel.Id;
item.CommunityRating ??= info.CommunityRating;
@@ -772,7 +772,7 @@ namespace Emby.Server.Implementations.LiveTv
item.OnMetadataChanged();
}
- return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated);
+ return (item, isNew, isUpdated);
}
public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
@@ -1187,14 +1187,14 @@ namespace Emby.Server.Implementations.LiveTv
foreach (var program in channelPrograms)
{
- var programTuple = GetProgram(program, existingPrograms, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken);
- var programItem = programTuple.Item1;
+ var programTuple = GetProgram(program, existingPrograms, currentChannel);
+ var programItem = programTuple.item;
- if (programTuple.Item2)
+ if (programTuple.isNew)
{
newPrograms.Add(programItem);
}
- else if (programTuple.Item3)
+ else if (programTuple.isUpdated)
{
updatedPrograms.Add(programItem);
}
@@ -1385,10 +1385,10 @@ namespace Emby.Server.Implementations.LiveTv
// var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
// return new QueryResult<BaseItem>
- //{
+ // {
// Items = items,
// TotalRecordCount = items.Length
- //};
+ // };
dtoOptions.Fields = dtoOptions.Fields.Concat(new[] { ItemFields.Tags }).Distinct().ToArray();
}
@@ -1425,16 +1425,15 @@ namespace Emby.Server.Implementations.LiveTv
return result;
}
- public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> tuples, IReadOnlyList<ItemFields> fields, User user = null)
+ public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
{
var programTuples = new List<Tuple<BaseItemDto, string, string>>();
var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
- foreach (var tuple in tuples)
+ foreach (var (item, dto) in programs)
{
- var program = (LiveTvProgram)tuple.Item1;
- var dto = tuple.Item2;
+ var program = (LiveTvProgram)item;
dto.StartDate = program.StartDate;
dto.EpisodeTitle = program.EpisodeTitle;
@@ -1724,7 +1723,7 @@ namespace Emby.Server.Implementations.LiveTv
await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
- if (!(service is EmbyTV.EmbyTV))
+ if (service is not EmbyTV.EmbyTV)
{
TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
}
@@ -1871,11 +1870,11 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetItemById(internalChannelId);
}
- public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> tuples, DtoOptions options, User user)
+ public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user)
{
var now = DateTime.UtcNow;
- var channelIds = tuples.Select(i => i.Item2.Id).Distinct().ToArray();
+ var channelIds = items.Select(i => i.Item2.Id).Distinct().ToArray();
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
{
@@ -1896,7 +1895,7 @@ namespace Emby.Server.Implementations.LiveTv
var addCurrentProgram = options.AddCurrentProgram;
- foreach (var tuple in tuples)
+ foreach (var tuple in items)
{
var dto = tuple.Item1;
var channel = tuple.Item2;
@@ -2050,7 +2049,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogInformation("New recording scheduled");
- if (!(service is EmbyTV.EmbyTV))
+ if (service is not EmbyTV.EmbyTV)
{
TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
new TimerEventInfo(newTimerId)
@@ -2118,17 +2117,13 @@ namespace Emby.Server.Implementations.LiveTv
};
}
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
+ /// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
- private bool _disposed = false;
-
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
@@ -2324,20 +2319,20 @@ namespace Emby.Server.Implementations.LiveTv
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
}
- public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelId, string providerChannelId)
+ public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
{
var config = GetConfiguration();
var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
- listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelId, StringComparison.OrdinalIgnoreCase)).ToArray();
+ listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
- if (!string.Equals(tunerChannelId, providerChannelId, StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
{
var list = listingsProviderInfo.ChannelMappings.ToList();
list.Add(new NameValuePair
{
- Name = tunerChannelId,
- Value = providerChannelId
+ Name = tunerChannelNumber,
+ Value = providerChannelNumber
});
listingsProviderInfo.ChannelMappings = list.ToArray();
}
@@ -2357,10 +2352,10 @@ namespace Emby.Server.Implementations.LiveTv
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
- return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase));
+ return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelNumber, StringComparison.OrdinalIgnoreCase));
}
- public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> epgChannels)
+ public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> providerChannels)
{
var result = new TunerChannelMapping
{
@@ -2373,7 +2368,7 @@ namespace Emby.Server.Implementations.LiveTv
result.Name = tunerChannel.Number + " " + result.Name;
}
- var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, epgChannels);
+ var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, providerChannels);
if (providerChannel != null)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 5941613cf..096b7f045 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
try
{
Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile));
- await using var writeStream = File.OpenWrite(channelCacheFile);
+ await using var writeStream = AsyncFile.OpenWrite(channelCacheFile);
await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch (IOException)
@@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
try
{
- await using var readStream = File.OpenRead(channelCacheFile);
+ await using var readStream = AsyncFile.OpenRead(channelCacheFile);
var channels = await JsonSerializer.DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToken)
.ConfigureAwait(false);
list.AddRange(channels);
@@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return new List<MediaSourceInfo>();
}
- protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tuner, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
+ protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 011748d1d..4d538c604 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -36,7 +36,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
- private readonly INetworkManager _networkManager;
private readonly IStreamHelper _streamHelper;
private readonly JsonSerializerOptions _jsonOptions;
@@ -50,7 +49,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IHttpClientFactory httpClientFactory,
IServerApplicationHost appHost,
ISocketFactory socketFactory,
- INetworkManager networkManager,
IStreamHelper streamHelper,
IMemoryCache memoryCache)
: base(config, logger, fileSystem, memoryCache)
@@ -58,7 +56,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_httpClientFactory = httpClientFactory;
_appHost = appHost;
_socketFactory = socketFactory;
- _networkManager = networkManager;
_streamHelper = streamHelper;
_jsonOptions = JsonDefaults.Options;
@@ -70,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
protected override string ChannelIdPrefix => "hdhr_";
- private string GetChannelId(TunerHostInfo info, Channels i)
+ private string GetChannelId(Channels i)
=> ChannelIdPrefix + i.GuideNumber;
internal async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
@@ -95,17 +92,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public bool IsLegacyTuner { get; set; }
}
- protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
+ protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
{
- var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false);
+ var lineup = await GetLineup(tuner, cancellationToken).ConfigureAwait(false);
return lineup.Select(i => new HdHomerunChannelInfo
{
Name = i.GuideName,
Number = i.GuideNumber,
- Id = GetChannelId(info, i),
+ Id = GetChannelId(i),
IsFavorite = i.Favorite,
- TunerHostId = info.Id,
+ TunerHostId = tuner.Id,
IsHD = i.HD,
AudioCodec = i.AudioCodec,
VideoCodec = i.VideoCodec,
@@ -255,7 +252,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- var tuners = new List<LiveTvTunerInfo>();
+ var tuners = new List<LiveTvTunerInfo>(model.TunerCount);
var uri = new Uri(GetApiUrl(info));
@@ -264,10 +261,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// Legacy HdHomeruns are IPv4 only
var ipInfo = IPAddress.Parse(uri.Host);
- for (int i = 0; i < model.TunerCount; ++i)
+ for (int i = 0; i < model.TunerCount; i++)
{
var name = string.Format(CultureInfo.InvariantCulture, "Tuner {0}", i + 1);
- var currentChannel = "none"; // @todo Get current channel and map back to Station Id
+ var currentChannel = "none"; // TODO: Get current channel and map back to Station Id
var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
tuners.Add(new LiveTvTunerInfo
@@ -455,28 +452,28 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Path = url,
Protocol = MediaProtocol.Udp,
MediaStreams = new List<MediaStream>
- {
- new MediaStream
- {
- Type = MediaStreamType.Video,
- // Set the index to -1 because we don't know the exact index of the video stream within the container
- Index = -1,
- IsInterlaced = isInterlaced,
- Codec = videoCodec,
- Width = width,
- Height = height,
- BitRate = videoBitrate,
- NalLengthSize = nal
- },
- new MediaStream
- {
- Type = MediaStreamType.Audio,
- // Set the index to -1 because we don't know the exact index of the audio stream within the container
- Index = -1,
- Codec = audioCodec,
- BitRate = audioBitrate
- }
- },
+ {
+ new MediaStream
+ {
+ Type = MediaStreamType.Video,
+ // Set the index to -1 because we don't know the exact index of the video stream within the container
+ Index = -1,
+ IsInterlaced = isInterlaced,
+ Codec = videoCodec,
+ Width = width,
+ Height = height,
+ BitRate = videoBitrate,
+ NalLengthSize = nal
+ },
+ new MediaStream
+ {
+ Type = MediaStreamType.Audio,
+ // Set the index to -1 because we don't know the exact index of the audio stream within the container
+ Index = -1,
+ Codec = audioCodec,
+ BitRate = audioBitrate
+ }
+ },
RequiresOpening = true,
RequiresClosing = true,
BufferMs = 0,
@@ -496,57 +493,53 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return mediaSource;
}
- protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
+ protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken)
{
var list = new List<MediaSourceInfo>();
- var channelId = channelInfo.Id;
+ var channelId = channel.Id;
var hdhrId = GetHdHrIdFromChannelId(channelId);
- var hdHomerunChannelInfo = channelInfo as HdHomerunChannelInfo;
-
- var isLegacyTuner = hdHomerunChannelInfo != null && hdHomerunChannelInfo.IsLegacyTuner;
-
- if (isLegacyTuner)
+ if (channel is HdHomerunChannelInfo hdHomerunChannelInfo && hdHomerunChannelInfo.IsLegacyTuner)
{
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+ list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
}
else
{
- var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ var modelInfo = await GetModelInfo(tuner, false, cancellationToken).ConfigureAwait(false);
if (modelInfo != null && modelInfo.SupportsTranscoding)
{
- if (info.AllowHWTranscoding)
+ if (tuner.AllowHWTranscoding)
{
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
+ list.Add(GetMediaSource(tuner, hdhrId, channel, "heavy"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
+ list.Add(GetMediaSource(tuner, hdhrId, channel, "internet540"));
+ list.Add(GetMediaSource(tuner, hdhrId, channel, "internet480"));
+ list.Add(GetMediaSource(tuner, hdhrId, channel, "internet360"));
+ list.Add(GetMediaSource(tuner, hdhrId, channel, "internet240"));
+ list.Add(GetMediaSource(tuner, hdhrId, channel, "mobile"));
}
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+ list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
}
if (list.Count == 0)
{
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+ list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
}
}
return list;
}
- protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
+ protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
- var tunerCount = info.TunerCount;
+ var tunerCount = tunerHost.TunerCount;
if (tunerCount > 0)
{
- var tunerHostId = info.Id;
+ var tunerHostId = tunerHost.Id;
var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
if (liveStreams.Count() >= tunerCount)
@@ -555,28 +548,28 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- var profile = streamId.Split('_')[0];
+ var profile = streamId.AsSpan().LeftPart('_').ToString();
- Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelInfo.Id, streamId, profile);
+ Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channel.Id, streamId, profile);
- var hdhrId = GetHdHrIdFromChannelId(channelInfo.Id);
+ var hdhrId = GetHdHrIdFromChannelId(channel.Id);
- var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
+ var hdhomerunChannel = channel as HdHomerunChannelInfo;
- var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ var modelInfo = await GetModelInfo(tunerHost, false, cancellationToken).ConfigureAwait(false);
if (!modelInfo.SupportsTranscoding)
{
profile = "native";
}
- var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
+ var mediaSource = GetMediaSource(tunerHost, hdhrId, channel, profile);
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
return new HdHomerunUdpStream(
mediaSource,
- info,
+ tunerHost,
streamId,
new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path),
modelInfo.TunerCount,
@@ -592,7 +585,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
mediaSource.Protocol = MediaProtocol.Http;
- var httpUrl = channelInfo.Path;
+ var httpUrl = channel.Path;
// If raw was used, the tuner doesn't support params
if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
@@ -604,7 +597,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return new SharedHttpStream(
mediaSource,
- info,
+ tunerHost,
streamId,
FileSystem,
_httpClientFactory,
@@ -616,7 +609,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return new HdHomerunUdpStream(
mediaSource,
- info,
+ tunerHost,
streamId,
new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
modelInfo.TunerCount,
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 3016eeda2..b2e555c7d 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -27,6 +27,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
private string _channel;
private string _program;
+
public LegacyHdHomerunChannelCommands(string url)
{
// parse url for channel and program
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 58e0c7448..a8f761fde 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -156,11 +156,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
await taskCompletionSource.Task.ConfigureAwait(false);
}
- public string GetFilePath()
- {
- return TempFilePath;
- }
-
private async Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
using (udpClient)
@@ -184,7 +179,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
EnableStreamSharing = false;
}
- await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
+ await DeleteTempFiles(TempFilePath).ConfigureAwait(false);
}
private async Task CopyTo(UdpClient udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
index 96a678c1d..817b1f804 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
@@ -3,10 +3,8 @@
#pragma warning disable CS1591
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.IO;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
@@ -97,6 +95,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return Task.CompletedTask;
}
+ public Stream GetStream()
+ {
+ var stream = GetInputStream(TempFilePath, AsyncFile.UseAsyncIO);
+ bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
+ if (seekFile)
+ {
+ TrySeek(stream, -20000);
+ }
+
+ return stream;
+ }
+
protected FileStream GetInputStream(string path, bool allowAsyncFileRead)
=> new FileStream(
path,
@@ -106,114 +116,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
IODefaults.FileStreamBufferSize,
allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
- public Task DeleteTempFiles()
- {
- return DeleteTempFiles(GetStreamFilePaths());
- }
-
- protected async Task DeleteTempFiles(IEnumerable<string> paths, int retryCount = 0)
+ protected async Task DeleteTempFiles(string path, int retryCount = 0)
{
if (retryCount == 0)
{
- Logger.LogInformation("Deleting temp files {0}", paths);
- }
-
- var failedFiles = new List<string>();
-
- foreach (var path in paths)
- {
- if (!File.Exists(path))
- {
- continue;
- }
-
- try
- {
- FileSystem.DeleteFile(path);
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error deleting file {path}", path);
- failedFiles.Add(path);
- }
- }
-
- if (failedFiles.Count > 0 && retryCount <= 40)
- {
- await Task.Delay(500).ConfigureAwait(false);
- await DeleteTempFiles(failedFiles, retryCount + 1).ConfigureAwait(false);
- }
- }
-
- protected virtual List<string> GetStreamFilePaths()
- {
- return new List<string> { TempFilePath };
- }
-
- public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
- {
- using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token);
- cancellationToken = linkedCancellationTokenSource.Token;
-
- // use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039
- var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT;
-
- bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
-
- var nextFileInfo = GetNextFile(null);
- var nextFile = nextFileInfo.file;
- var isLastFile = nextFileInfo.isLastFile;
-
- while (!string.IsNullOrEmpty(nextFile))
- {
- var emptyReadLimit = isLastFile ? EmptyReadLimit : 1;
-
- await CopyFile(nextFile, seekFile, emptyReadLimit, allowAsync, stream, cancellationToken).ConfigureAwait(false);
-
- seekFile = false;
- nextFileInfo = GetNextFile(nextFile);
- nextFile = nextFileInfo.file;
- isLastFile = nextFileInfo.isLastFile;
+ Logger.LogInformation("Deleting temp file {FilePath}", path);
}
- Logger.LogInformation("Live Stream ended.");
- }
-
- private (string file, bool isLastFile) GetNextFile(string currentFile)
- {
- var files = GetStreamFilePaths();
-
- if (string.IsNullOrEmpty(currentFile))
+ try
{
- return (files[^1], true);
+ FileSystem.DeleteFile(path);
}
-
- var nextIndex = files.FindIndex(i => string.Equals(i, currentFile, StringComparison.OrdinalIgnoreCase)) + 1;
-
- var isLastFile = nextIndex == files.Count - 1;
-
- return (files.ElementAtOrDefault(nextIndex), isLastFile);
- }
-
- private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
- {
- using (var inputStream = GetInputStream(path, allowAsync))
+ catch (Exception ex)
{
- if (seekFile)
+ Logger.LogError(ex, "Error deleting file {FilePath}", path);
+ if (retryCount <= 40)
{
- TrySeek(inputStream, -20000);
+ await Task.Delay(500).ConfigureAwait(false);
+ await DeleteTempFiles(path, retryCount + 1).ConfigureAwait(false);
}
-
- await StreamHelper.CopyToAsync(
- inputStream,
- stream,
- IODefaults.CopyToBufferSize,
- emptyReadLimit,
- cancellationToken).ConfigureAwait(false);
}
}
- private void TrySeek(FileStream stream, long offset)
+ private void TrySeek(Stream stream, long offset)
{
if (!stream.CanSeek)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 8fa6f5ad6..08b9260b9 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -71,12 +71,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
- protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
+ protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
{
- var channelIdPrefix = GetFullChannelIdPrefix(info);
+ var channelIdPrefix = GetFullChannelIdPrefix(tuner);
return await new M3uParser(Logger, _httpClientFactory)
- .Parse(info, channelIdPrefix, cancellationToken)
+ .Parse(tuner, channelIdPrefix, cancellationToken)
.ConfigureAwait(false);
}
@@ -96,13 +96,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return Task.FromResult(list);
}
- protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
+ protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
- var tunerCount = info.TunerCount;
+ var tunerCount = tunerHost.TunerCount;
if (tunerCount > 0)
{
- var tunerHostId = info.Id;
+ var tunerHostId = tunerHost.Id;
var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
if (liveStreams.Count() >= tunerCount)
@@ -111,7 +111,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- var sources = await GetChannelStreamMediaSources(info, channelInfo, cancellationToken).ConfigureAwait(false);
+ var sources = await GetChannelStreamMediaSources(tunerHost, channel, cancellationToken).ConfigureAwait(false);
var mediaSource = sources[0];
@@ -121,11 +121,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
+ return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
}
- return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper);
+ return new LiveStream(mediaSource, tunerHost, FileSystem, Logger, Config, _streamHelper);
}
public async Task Validate(TunerHostInfo info)
@@ -135,9 +135,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
+ protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken)
{
- return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(info, channelInfo) });
+ return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(tuner, channel) });
}
protected virtual MediaSourceInfo CreateMediaSourceInfo(TunerHostInfo info, ChannelInfo channel)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 16ff98a7d..506ef5548 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -14,6 +14,7 @@ using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
@@ -50,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
- return File.OpenRead(info.Url);
+ return AsyncFile.OpenRead(info.Url);
}
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
@@ -237,7 +238,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
try
{
- numberString = Path.GetFileNameWithoutExtension(mediaUrl.Split('/')[^1]);
+ numberString = Path.GetFileNameWithoutExtension(mediaUrl.AsSpan().RightPart('/')).ToString();
if (!IsValidChannelNumber(numberString))
{
@@ -295,11 +296,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- attributes.TryGetValue("tvg-name", out string name);
+ string name = nameInExtInf;
if (string.IsNullOrWhiteSpace(name))
{
- name = nameInExtInf;
+ attributes.TryGetValue("tvg-name", out name);
}
if (string.IsNullOrWhiteSpace(name))
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index f572151b8..84a878e51 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -3,7 +3,6 @@
#pragma warning disable CS1591
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Http;
@@ -55,39 +54,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
var typeName = GetType().Name;
- Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url);
+ Logger.LogInformation("Opening {StreamType} Live stream from {Url}", typeName, url);
// Response stream is disposed manually.
var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None)
.ConfigureAwait(false);
- var extension = "ts";
- var requiresRemux = false;
-
var contentType = response.Content.Headers.ContentType?.ToString() ?? string.Empty;
- if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1)
- {
- requiresRemux = true;
- }
- else if (contentType.IndexOf("mp4", StringComparison.OrdinalIgnoreCase) != -1 ||
- contentType.IndexOf("dash", StringComparison.OrdinalIgnoreCase) != -1 ||
- contentType.IndexOf("mpegURL", StringComparison.OrdinalIgnoreCase) != -1 ||
- contentType.IndexOf("text/", StringComparison.OrdinalIgnoreCase) != -1)
- {
- requiresRemux = true;
- }
-
- // Close the stream without any sharing features
- if (requiresRemux)
+ if (contentType.Contains("matroska", StringComparison.OrdinalIgnoreCase)
+ || contentType.Contains("mp4", StringComparison.OrdinalIgnoreCase)
+ || contentType.Contains("dash", StringComparison.OrdinalIgnoreCase)
+ || contentType.Contains("mpegURL", StringComparison.OrdinalIgnoreCase)
+ || contentType.Contains("text/", StringComparison.OrdinalIgnoreCase))
{
- using (response)
- {
- return;
- }
+ // Close the stream without any sharing features
+ response.Dispose();
+ return;
}
- SetTempFilePath(extension);
+ SetTempFilePath("ts");
var taskCompletionSource = new TaskCompletionSource<bool>();
@@ -117,49 +103,46 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!taskCompletionSource.Task.Result)
{
- Logger.LogWarning("Zero bytes copied from stream {0} to {1} but no exception raised", GetType().Name, TempFilePath);
+ Logger.LogWarning("Zero bytes copied from stream {StreamType} to {FilePath} but no exception raised", GetType().Name, TempFilePath);
throw new EndOfStreamException(string.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name));
}
}
- public string GetFilePath()
- {
- return TempFilePath;
- }
-
private Task StartStreaming(HttpResponseMessage response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
- return Task.Run(async () =>
- {
- try
- {
- Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
- using var message = response;
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read);
- await StreamHelper.CopyToAsync(
- stream,
- fileStream,
- IODefaults.CopyToBufferSize,
- () => Resolve(openTaskCompletionSource),
- cancellationToken).ConfigureAwait(false);
- }
- catch (OperationCanceledException ex)
+ return Task.Run(
+ async () =>
{
- Logger.LogInformation("Copying of {0} to {1} was canceled", GetType().Name, TempFilePath);
- openTaskCompletionSource.TrySetException(ex);
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error copying live stream {0} to {1}.", GetType().Name, TempFilePath);
- openTaskCompletionSource.TrySetException(ex);
- }
-
- openTaskCompletionSource.TrySetResult(false);
-
- EnableStreamSharing = false;
- await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
- }, CancellationToken.None);
+ try
+ {
+ Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath);
+ using var message = response;
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, AsyncFile.UseAsyncIO);
+ await StreamHelper.CopyToAsync(
+ stream,
+ fileStream,
+ IODefaults.CopyToBufferSize,
+ () => Resolve(openTaskCompletionSource),
+ cancellationToken).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException ex)
+ {
+ Logger.LogInformation("Copying of {StreamType} to {FilePath} was canceled", GetType().Name, TempFilePath);
+ openTaskCompletionSource.TrySetException(ex);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error copying live stream {StreamType} to {FilePath}", GetType().Name, TempFilePath);
+ openTaskCompletionSource.TrySetException(ex);
+ }
+
+ openTaskCompletionSource.TrySetResult(false);
+
+ EnableStreamSharing = false;
+ await DeleteTempFiles(TempFilePath).ConfigureAwait(false);
+ },
+ CancellationToken.None);
}
private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json
index f55287d15..e1c923308 100644
--- a/Emby.Server.Implementations/Localization/Core/bg-BG.json
+++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Телевизия на живо",
"HeaderNextUp": "Следва",
"HeaderRecordingGroups": "Запис групи",
- "HomeVideos": "Домашни клипове",
+ "HomeVideos": "Домашни Клипове",
"Inherit": "Наследяване",
"ItemAddedWithName": "{0} е добавено към библиотеката",
"ItemRemovedWithName": "{0} е премахнато от библиотеката",
@@ -39,7 +39,7 @@
"MixedContent": "Смесено съдържание",
"Movies": "Филми",
"Music": "Музика",
- "MusicVideos": "Музикални видеа",
+ "MusicVideos": "Музикални Видеа",
"NameInstallFailed": "{0} не можа да се инсталира",
"NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Неразпознат сезон",
diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json
index 62b2b6328..1ea378321 100644
--- a/Emby.Server.Implementations/Localization/Core/cs.json
+++ b/Emby.Server.Implementations/Localization/Core/cs.json
@@ -15,7 +15,7 @@
"Favorites": "Oblíbené",
"Folders": "Složky",
"Genres": "Žánry",
- "HeaderAlbumArtists": "Umělci alba",
+ "HeaderAlbumArtists": "Album umělce",
"HeaderContinueWatching": "Pokračovat ve sledování",
"HeaderFavoriteAlbums": "Oblíbená alba",
"HeaderFavoriteArtists": "Oblíbení interpreti",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Televize",
"HeaderNextUp": "Nadcházející",
"HeaderRecordingGroups": "Skupiny nahrávek",
- "HomeVideos": "Domáci videa",
+ "HomeVideos": "Domácí videa",
"Inherit": "Zdědit",
"ItemAddedWithName": "{0} byl přidán do knihovny",
"ItemRemovedWithName": "{0} byl odstraněn z knihovny",
diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json
index 3c51d64e0..2a56d0745 100644
--- a/Emby.Server.Implementations/Localization/Core/fr-CA.json
+++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json
@@ -118,5 +118,7 @@
"TaskCleanActivityLogDescription": "Éfface les entrées du journal plus anciennes que l'âge configuré.",
"TaskCleanActivityLog": "Nettoyer le journal d'activité",
"Undefined": "Indéfini",
- "Forced": "Forcé"
+ "Forced": "Forcé",
+ "TaskOptimizeDatabaseDescription": "Compacte la base de données et tronque l'espace libre. Lancer cette tâche après avoir scanné la bibliothèque ou faire d'autres changements impliquant des modifications de la base peuvent ameliorer les performances.",
+ "TaskOptimizeDatabase": "Optimiser la base de données"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index 0e4c38425..c3e52eb81 100644
--- a/Emby.Server.Implementations/Localization/Core/fr.json
+++ b/Emby.Server.Implementations/Localization/Core/fr.json
@@ -105,8 +105,8 @@
"TaskRefreshPeople": "Rafraîchir les acteurs",
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
"TaskCleanLogs": "Nettoyer le répertoire des journaux",
- "TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
- "TaskRefreshLibrary": "Scanner toutes les Bibliothèques",
+ "TaskRefreshLibraryDescription": "Scanne votre médiathèque pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
+ "TaskRefreshLibrary": "Scanner la médiathèque",
"TaskRefreshChapterImagesDescription": "Crée des vignettes pour les vidéos ayant des chapitres.",
"TaskRefreshChapterImages": "Extraire les images de chapitre",
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json
index afb22ab47..b433c6f68 100644
--- a/Emby.Server.Implementations/Localization/Core/gl.json
+++ b/Emby.Server.Implementations/Localization/Core/gl.json
@@ -48,7 +48,7 @@
"HeaderFavoriteArtists": "Artistas Favoritos",
"HeaderFavoriteAlbums": "Álbunes Favoritos",
"HeaderContinueWatching": "Seguir mirando",
- "HeaderAlbumArtists": "Artistas de Album",
+ "HeaderAlbumArtists": "Artistas do Album",
"Genres": "Xéneros",
"Forced": "Forzado",
"Folders": "Cartafoles",
@@ -117,5 +117,7 @@
"UserPolicyUpdatedWithName": "A política de usuario foi actualizada para {0}",
"UserPasswordChangedWithName": "Cambiouse o contrasinal para o usuario {0}",
"UserOnlineFromDevice": "{0} está en liña desde {1}",
- "UserOfflineFromDevice": "{0} desconectouse desde {1}"
+ "UserOfflineFromDevice": "{0} desconectouse desde {1}",
+ "TaskOptimizeDatabaseDescription": "Compacta e libera o espazo libre da base de datos. Executar esta tarefa logo de realizar mudanzas que impliquen modificacións da base de datos ou despois de escanear a biblioteca pode traer mellorías de desempeño.",
+ "TaskOptimizeDatabase": "Optimizar base de datos"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index 0d90ad31c..c689bc58a 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -15,7 +15,7 @@
"Favorites": "お気に入り",
"Folders": "フォルダー",
"Genres": "ジャンル",
- "HeaderAlbumArtists": "アルバムアーティスト",
+ "HeaderAlbumArtists": "アーティストのアルバム",
"HeaderContinueWatching": "視聴を続ける",
"HeaderFavoriteAlbums": "お気に入りのアルバム",
"HeaderFavoriteArtists": "お気に入りのアーティスト",
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index 409b4d26b..a37de0748 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -15,7 +15,7 @@
"Favorites": "즐겨찾기",
"Folders": "폴더",
"Genres": "장르",
- "HeaderAlbumArtists": "앨범 아티스트",
+ "HeaderAlbumArtists": "아티스트의 앨범",
"HeaderContinueWatching": "계속 시청하기",
"HeaderFavoriteAlbums": "즐겨찾는 앨범",
"HeaderFavoriteArtists": "즐겨찾는 아티스트",
diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json
index 275bdec6e..e8a32a13e 100644
--- a/Emby.Server.Implementations/Localization/Core/pl.json
+++ b/Emby.Server.Implementations/Localization/Core/pl.json
@@ -15,7 +15,7 @@
"Favorites": "Ulubione",
"Folders": "Foldery",
"Genres": "Gatunki",
- "HeaderAlbumArtists": "Wykonawcy albumów",
+ "HeaderAlbumArtists": "Album artysty",
"HeaderContinueWatching": "Kontynuuj odtwarzanie",
"HeaderFavoriteAlbums": "Ulubione albumy",
"HeaderFavoriteArtists": "Ulubieni wykonawcy",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Telewizja",
"HeaderNextUp": "Do obejrzenia",
"HeaderRecordingGroups": "Grupy nagrań",
- "HomeVideos": "Nagrania prywatne",
+ "HomeVideos": "Nagrania domowe",
"Inherit": "Dziedzicz",
"ItemAddedWithName": "{0} zostało dodane do biblioteki",
"ItemRemovedWithName": "{0} zostało usunięte z biblioteki",
@@ -119,5 +119,6 @@
"Undefined": "Nieustalony",
"Forced": "Wymuszony",
"Default": "Domyślne",
- "TaskOptimizeDatabase": "Optymalizuj bazę danych"
+ "TaskOptimizeDatabase": "Optymalizuj bazę danych",
+ "TaskOptimizeDatabaseDescription": "Kompaktuje bazę danych i obcina wolne miejsce. Uruchomienie tego zadania po przeskanowaniu biblioteki lub dokonaniu innych zmian, które pociągają za sobą modyfikacje bazy danych, może poprawić wydajność."
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json
index 323dcced0..be71289b1 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-BR.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json
@@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Limpar Registro de Atividades",
"Undefined": "Indefinido",
"Forced": "Forçado",
- "Default": "Padrão"
+ "Default": "Padrão",
+ "TaskOptimizeDatabaseDescription": "Compactar base de dados e liberar espaço livre. Executar esta tarefa após realizar mudanças que impliquem em modificações da base de dados pode trazer melhorias de desempenho.",
+ "TaskOptimizeDatabase": "Otimizar base de dados"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index b435672ad..474dacd7c 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -117,5 +117,6 @@
"Undefined": "Indefinido",
"Forced": "Forçado",
"Default": "Predefinição",
- "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado."
+ "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado.",
+ "TaskOptimizeDatabase": "Otimizar base de dados"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index 88b182f8d..6c772c6a2 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -15,7 +15,7 @@
"Favorites": "Favoriter",
"Folders": "Mappar",
"Genres": "Genrer",
- "HeaderAlbumArtists": "Albumartister",
+ "HeaderAlbumArtists": "Artistens album",
"HeaderContinueWatching": "Fortsätt kolla",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Live-TV",
"HeaderNextUp": "Nästa",
"HeaderRecordingGroups": "Inspelningsgrupper",
- "HomeVideos": "Hemvideor",
+ "HomeVideos": "Hemmavideor",
"Inherit": "Ärv",
"ItemAddedWithName": "{0} lades till i biblioteket",
"ItemRemovedWithName": "{0} togs bort från biblioteket",
@@ -119,5 +119,6 @@
"Undefined": "odefinierad",
"Forced": "Tvingad",
"Default": "Standard",
- "TaskOptimizeDatabase": "Optimera databasen"
+ "TaskOptimizeDatabase": "Optimera databasen",
+ "TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna task efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats."
}
diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json
index 771c91d59..e661299c4 100644
--- a/Emby.Server.Implementations/Localization/Core/tr.json
+++ b/Emby.Server.Implementations/Localization/Core/tr.json
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Canlı TV",
"HeaderNextUp": "Gelecek Hafta",
"HeaderRecordingGroups": "Kayıt Grupları",
- "HomeVideos": "Ev videoları",
+ "HomeVideos": "Ana sayfa videoları",
"Inherit": "Devral",
"ItemAddedWithName": "{0} kütüphaneye eklendi",
"ItemRemovedWithName": "{0} kütüphaneden silindi",
diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json
index 5d6d0775c..11af9fc98 100644
--- a/Emby.Server.Implementations/Localization/Core/ur_PK.json
+++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json
@@ -90,7 +90,7 @@
"NameSeasonUnknown": "نامعلوم باب",
"NameSeasonNumber": "باب {0}",
"NameInstallFailed": "{0} تنصیب ناکام ہوگئی",
- "MusicVideos": "موسیقی ویڈیو",
+ "MusicVideos": "ویڈیو موسیقی",
"Music": "موسیقی",
"MixedContent": "مخلوط مواد",
"MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے",
@@ -99,18 +99,19 @@
"MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے",
"Latest": "تازہ ترین",
"LabelRunningTimeValue": "چلانے کی مدت",
- "LabelIpAddressValue": "ای پی پتے {0}",
+ "LabelIpAddressValue": "آئ پی ایڈریس {0}",
"ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے",
"ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے",
"Inherit": "وراثت میں",
"HomeVideos": "ہوم ویڈیو",
"HeaderRecordingGroups": "ریکارڈنگ گروپس",
- "FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}",
+ "FailedLoginAttemptWithUserName": "{0} سے لاگ ان کی ناکام کوشش",
"DeviceOnlineWithName": "{0} متصل ھو چکا ھے",
"DeviceOfflineWithName": "{0} منقطع ھو چکا ھے",
"ChapterNameValue": "باب",
"AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے",
"CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
"Application": "پروگرام",
- "AppDeviceValues": "پروگرام:{0}, آلہ:{1}"
+ "AppDeviceValues": "پروگرام:{0}, ڈیوائس:{1}",
+ "Forced": "جَبری"
}
diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json
index 3d69e418b..33aa0eea0 100644
--- a/Emby.Server.Implementations/Localization/Core/vi.json
+++ b/Emby.Server.Implementations/Localization/Core/vi.json
@@ -10,11 +10,11 @@
"Photos": "Ảnh",
"Playlists": "Danh sách phát",
"Shows": "Chương Trình TV",
- "Songs": "Các Bài Hát",
+ "Songs": "Bài Hát",
"Sync": "Đồng Bộ",
"ValueSpecialEpisodeName": "Đặc Biệt - {0}",
"Albums": "Tuyển Tập",
- "Artists": "Các Nghệ Sĩ",
+ "Artists": "Ca Sĩ",
"TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.",
"TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu",
"TaskRefreshChannelsDescription": "Làm mới thông tin kênh internet.",
@@ -32,7 +32,7 @@
"TaskRefreshChapterImagesDescription": "Tạo hình thu nhỏ cho video có các phân cảnh.",
"TaskRefreshChapterImages": "Trích Xuất Ảnh Phân Cảnh",
"TaskCleanCacheDescription": "Xóa các tệp cache không còn cần thiết của hệ thống.",
- "TaskCleanCache": "Làm Sạch Thư Mục Cache",
+ "TaskCleanCache": "Làm Sạch Thư Mục Bộ Nhớ Đệm",
"TasksChannelsCategory": "Kênh Internet",
"TasksApplicationCategory": "Ứng Dụng",
"TasksLibraryCategory": "Thư Viện",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json
index faa9c40e2..f9df62724 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-CN.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json
@@ -7,7 +7,7 @@
"Books": "书籍",
"CameraImageUploadedFrom": "新的相机图像已从 {0} 上传",
"Channels": "频道",
- "ChapterNameValue": "第 {0} 集",
+ "ChapterNameValue": "章节 {0}",
"Collections": "合集",
"DeviceOfflineWithName": "{0} 已断开",
"DeviceOnlineWithName": "{0} 已连接",
@@ -15,8 +15,8 @@
"Favorites": "我的最爱",
"Folders": "文件夹",
"Genres": "风格",
- "HeaderAlbumArtists": "专辑作家",
- "HeaderContinueWatching": "继续观影",
+ "HeaderAlbumArtists": "专辑艺术家",
+ "HeaderContinueWatching": "继续观看",
"HeaderFavoriteAlbums": "收藏的专辑",
"HeaderFavoriteArtists": "最爱的艺术家",
"HeaderFavoriteEpisodes": "最爱的剧集",
@@ -108,8 +108,8 @@
"TaskCleanLogs": "清理日志目录",
"TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。",
"TaskRefreshLibrary": "扫描媒体库",
- "TaskRefreshChapterImagesDescription": "为包含剧集的视频提取缩略图。",
- "TaskRefreshChapterImages": "提取剧集图片",
+ "TaskRefreshChapterImagesDescription": "为包含章节的视频提取缩略图。",
+ "TaskRefreshChapterImages": "提取章节图片",
"TaskCleanCacheDescription": "删除系统不再需要的缓存文件。",
"TaskCleanCache": "清理缓存目录",
"TasksApplicationCategory": "应用程序",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json
index 3dad21dcb..1cc97bc27 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-HK.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json
@@ -13,7 +13,7 @@
"DeviceOnlineWithName": "{0} 已經連接",
"FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗",
"Favorites": "我的最愛",
- "Folders": "檔案夾",
+ "Folders": "資料夾",
"Genres": "風格",
"HeaderAlbumArtists": "專輯藝人",
"HeaderContinueWatching": "繼續觀看",
@@ -39,7 +39,7 @@
"MixedContent": "混合內容",
"Movies": "電影",
"Music": "音樂",
- "MusicVideos": "音樂視頻",
+ "MusicVideos": "音樂影片",
"NameInstallFailed": "{0} 安裝失敗",
"NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知季數",
@@ -117,5 +117,8 @@
"TaskCleanActivityLog": "清理活動記錄",
"Undefined": "未定義",
"Forced": "強制",
- "Default": "預設"
+ "Default": "預設",
+ "TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。",
+ "TaskOptimizeDatabase": "最佳化數據庫",
+ "TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。"
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index c3b223f63..585d81450 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -24,7 +24,7 @@
"HeaderFavoriteSongs": "最愛歌曲",
"HeaderLiveTV": "電視直播",
"HeaderNextUp": "接下來",
- "HomeVideos": "自製影片",
+ "HomeVideos": "家庭影片",
"ItemAddedWithName": "{0} 已新增至媒體庫",
"ItemRemovedWithName": "{0} 已從媒體庫移除",
"LabelIpAddressValue": "IP 位址:{0}",
@@ -117,5 +117,7 @@
"TaskCleanActivityLog": "清除活動紀錄",
"Undefined": "未定義的",
"Forced": "強制",
- "Default": "原本"
+ "Default": "原本",
+ "TaskOptimizeDatabaseDescription": "縮小資料庫並釋放可用空間。在掃描資料庫或進行資料庫相關的更動後使用此功能會增加效能。",
+ "TaskOptimizeDatabase": "最佳化資料庫"
}
diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs
index 137728616..6d0c8731e 100644
--- a/Emby.Server.Implementations/Net/SocketFactory.cs
+++ b/Emby.Server.Implementations/Net/SocketFactory.cs
@@ -11,6 +11,7 @@ namespace Emby.Server.Implementations.Net
{
public class SocketFactory : ISocketFactory
{
+ /// <inheritdoc />
public ISocket CreateUdpBroadcastSocket(int localPort)
{
if (localPort < 0)
@@ -35,11 +36,8 @@ namespace Emby.Server.Implementations.Net
}
}
- /// <summary>
- /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
- /// </summary>
- /// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
- public ISocket CreateSsdpUdpSocket(IPAddress localIpAddress, int localPort)
+ /// <inheritdoc />
+ public ISocket CreateSsdpUdpSocket(IPAddress localIp, int localPort)
{
if (localPort < 0)
{
@@ -53,8 +51,8 @@ namespace Emby.Server.Implementations.Net
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
- retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress));
- return new UdpSocket(retVal, localPort, localIpAddress);
+ retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp));
+ return new UdpSocket(retVal, localPort, localIp);
}
catch
{
@@ -64,13 +62,7 @@ namespace Emby.Server.Implementations.Net
}
}
- /// <summary>
- /// Creates a new UDP acceptSocket that is a member of the specified multicast IP address, and binds it to the specified local port.
- /// </summary>
- /// <param name="ipAddress">The multicast IP address to make the acceptSocket a member of.</param>
- /// <param name="multicastTimeToLive">The multicast time to live value for the acceptSocket.</param>
- /// <param name="localPort">The number of the local port to bind to.</param>
- /// <returns></returns>
+ /// <inheritdoc />
public ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort)
{
if (ipAddress == null)
diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs
index a8b18d292..9b799e854 100644
--- a/Emby.Server.Implementations/Net/UdpSocket.cs
+++ b/Emby.Server.Implementations/Net/UdpSocket.cs
@@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.Net
return taskCompletion.Task;
}
- public Task SendToAsync(byte[] buffer, int offset, int size, IPEndPoint endPoint, CancellationToken cancellationToken)
+ public Task SendToAsync(byte[] buffer, int offset, int bytes, IPEndPoint endPoint, CancellationToken cancellationToken)
{
ThrowIfDisposed();
@@ -214,7 +214,7 @@ namespace Emby.Server.Implementations.Net
}
};
- var result = BeginSendTo(buffer, offset, size, endPoint, new AsyncCallback(callback), null);
+ var result = BeginSendTo(buffer, offset, bytes, endPoint, new AsyncCallback(callback), null);
if (result.CompletedSynchronously)
{
diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
index 358606b0d..4160f3a50 100644
--- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
+++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
@@ -49,5 +49,10 @@ namespace Emby.Server.Implementations.Playlists
query.Parent = null;
return LibraryManager.GetItemsResult(query);
}
+
+ public override string GetClientTypeName()
+ {
+ return "ManualPlaylistsFolder";
+ }
}
}
diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
index 8cafde38e..b07798fa4 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
@@ -260,7 +260,7 @@ namespace Emby.Server.Implementations.Playlists
public async Task RemoveFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds)
{
- if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
+ if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
{
throw new ArgumentException("No Playlist exists with the supplied Id");
}
@@ -293,7 +293,7 @@ namespace Emby.Server.Implementations.Playlists
public async Task MoveItemAsync(string playlistId, string entryId, int newIndex)
{
- if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
+ if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
{
throw new ArgumentException("No Playlist exists with the supplied Id");
}
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index fc0920edf..b8e1dc2c0 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -15,6 +15,7 @@ using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Updates;
using Microsoft.Extensions.DependencyInjection;
@@ -371,7 +372,7 @@ namespace Emby.Server.Implementations.Plugins
var url = new Uri(packageInfo.ImageUrl);
imagePath = Path.Join(path, url.Segments[^1]);
- await using var fileStream = File.OpenWrite(imagePath);
+ await using var fileStream = AsyncFile.OpenWrite(imagePath);
try
{
diff --git a/Emby.Server.Implementations/Properties/AssemblyInfo.cs b/Emby.Server.Implementations/Properties/AssemblyInfo.cs
index cb7972173..41c396ac1 100644
--- a/Emby.Server.Implementations/Properties/AssemblyInfo.cs
+++ b/Emby.Server.Implementations/Properties/AssemblyInfo.cs
@@ -16,6 +16,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
[assembly: InternalsVisibleTo("Jellyfin.Server.Implementations.Tests")]
+[assembly: InternalsVisibleTo("Emby.Server.Implementations.Fuzz")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
index 898cbedbb..ae773c658 100644
--- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
+++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
@@ -3,12 +3,13 @@ using System.Collections.Concurrent;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
+using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.QuickConnect;
-using MediaBrowser.Controller.Security;
+using MediaBrowser.Controller.Session;
using MediaBrowser.Model.QuickConnect;
using Microsoft.Extensions.Logging;
@@ -20,11 +21,6 @@ namespace Emby.Server.Implementations.QuickConnect
public class QuickConnectManager : IQuickConnect, IDisposable
{
/// <summary>
- /// The name of internal access tokens.
- /// </summary>
- private const string TokenName = "QuickConnect";
-
- /// <summary>
/// The length of user facing codes.
/// </summary>
private const int CodeLength = 6;
@@ -34,13 +30,13 @@ namespace Emby.Server.Implementations.QuickConnect
/// </summary>
private const int Timeout = 10;
- private readonly RNGCryptoServiceProvider _rng = new();
- private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new();
+ private readonly RNGCryptoServiceProvider _rng = new ();
+ private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new ();
+ private readonly ConcurrentDictionary<string, (DateTime Timestamp, AuthenticationResult AuthenticationResult)> _authorizedSecrets = new ();
private readonly IServerConfigurationManager _config;
private readonly ILogger<QuickConnectManager> _logger;
- private readonly IServerApplicationHost _appHost;
- private readonly IAuthenticationRepository _authenticationRepository;
+ private readonly ISessionManager _sessionManager;
/// <summary>
/// Initializes a new instance of the <see cref="QuickConnectManager"/> class.
@@ -48,18 +44,15 @@ namespace Emby.Server.Implementations.QuickConnect
/// </summary>
/// <param name="config">Configuration.</param>
/// <param name="logger">Logger.</param>
- /// <param name="appHost">Application host.</param>
- /// <param name="authenticationRepository">Authentication repository.</param>
+ /// <param name="sessionManager">Session Manager.</param>
public QuickConnectManager(
IServerConfigurationManager config,
ILogger<QuickConnectManager> logger,
- IServerApplicationHost appHost,
- IAuthenticationRepository authenticationRepository)
+ ISessionManager sessionManager)
{
_config = config;
_logger = logger;
- _appHost = appHost;
- _authenticationRepository = authenticationRepository;
+ _sessionManager = sessionManager;
}
/// <inheritdoc />
@@ -77,14 +70,41 @@ namespace Emby.Server.Implementations.QuickConnect
}
/// <inheritdoc/>
- public QuickConnectResult TryConnect()
+ public QuickConnectResult TryConnect(AuthorizationInfo authorizationInfo)
{
+ if (string.IsNullOrEmpty(authorizationInfo.DeviceId))
+ {
+ throw new ArgumentException(nameof(authorizationInfo.DeviceId) + " is required");
+ }
+
+ if (string.IsNullOrEmpty(authorizationInfo.Device))
+ {
+ throw new ArgumentException(nameof(authorizationInfo.Device) + " is required");
+ }
+
+ if (string.IsNullOrEmpty(authorizationInfo.Client))
+ {
+ throw new ArgumentException(nameof(authorizationInfo.Client) + " is required");
+ }
+
+ if (string.IsNullOrEmpty(authorizationInfo.Version))
+ {
+ throw new ArgumentException(nameof(authorizationInfo.Version) + "is required");
+ }
+
AssertActive();
ExpireRequests();
var secret = GenerateSecureRandom();
var code = GenerateCode();
- var result = new QuickConnectResult(secret, code, DateTime.UtcNow);
+ var result = new QuickConnectResult(
+ secret,
+ code,
+ DateTime.UtcNow,
+ authorizationInfo.DeviceId,
+ authorizationInfo.Device,
+ authorizationInfo.Client,
+ authorizationInfo.Version);
_currentRequests[code] = result;
return result;
@@ -129,7 +149,7 @@ namespace Emby.Server.Implementations.QuickConnect
}
/// <inheritdoc/>
- public bool AuthorizeRequest(Guid userId, string code)
+ public async Task<bool> AuthorizeRequest(Guid userId, string code)
{
AssertActive();
ExpireRequests();
@@ -144,28 +164,41 @@ namespace Emby.Server.Implementations.QuickConnect
throw new InvalidOperationException("Request is already authorized");
}
- var token = Guid.NewGuid();
- result.Authentication = token;
-
// Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated.
- result.DateAdded = DateTime.Now.Add(TimeSpan.FromMinutes(1));
+ result.DateAdded = DateTime.UtcNow.Add(TimeSpan.FromMinutes(1));
- _authenticationRepository.Create(new AuthenticationInfo
+ var authenticationResult = await _sessionManager.AuthenticateDirect(new AuthenticationRequest
{
- AppName = TokenName,
- AccessToken = token.ToString("N", CultureInfo.InvariantCulture),
- DateCreated = DateTime.UtcNow,
- DeviceId = _appHost.SystemId,
- DeviceName = _appHost.FriendlyName,
- AppVersion = _appHost.ApplicationVersionString,
- UserId = userId
- });
+ UserId = userId,
+ DeviceId = result.DeviceId,
+ DeviceName = result.DeviceName,
+ App = result.AppName,
+ AppVersion = result.AppVersion
+ }).ConfigureAwait(false);
+
+ _authorizedSecrets[result.Secret] = (DateTime.UtcNow, authenticationResult);
+ result.Authenticated = true;
+ _currentRequests[code] = result;
- _logger.LogDebug("Authorizing device with code {Code} to login as user {userId}", code, userId);
+ _logger.LogDebug("Authorizing device with code {Code} to login as user {UserId}", code, userId);
return true;
}
+ /// <inheritdoc/>
+ public AuthenticationResult GetAuthorizedRequest(string secret)
+ {
+ AssertActive();
+ ExpireRequests();
+
+ if (!_authorizedSecrets.TryGetValue(secret, out var result))
+ {
+ throw new ResourceNotFoundException("Unable to find request");
+ }
+
+ return result.AuthenticationResult;
+ }
+
/// <summary>
/// Dispose.
/// </summary>
@@ -218,6 +251,18 @@ namespace Emby.Server.Implementations.QuickConnect
}
}
}
+
+ foreach (var (secret, (timestamp, _)) in _authorizedSecrets)
+ {
+ if (expireAll || timestamp < minTime)
+ {
+ _logger.LogDebug("Removing expired secret {Secret}", secret);
+ if (!_authorizedSecrets.TryRemove(secret, out _))
+ {
+ _logger.LogWarning("Secret {Secret} already expired", secret);
+ }
+ }
+ }
}
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index b34325041..fb93c375d 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -24,7 +24,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary>
public class ScheduledTaskWorker : IScheduledTaskWorker
{
-
/// <summary>
/// Gets or sets the application paths.
/// </summary>
@@ -267,7 +266,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Gets the triggers that define when the task will run.
+ /// Gets or sets the triggers that define when the task will run.
/// </summary>
/// <value>The triggers.</value>
/// <exception cref="ArgumentNullException"><c>value</c> is <c>null</c>.</exception>
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs
index 19600b1e6..79886cb52 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays;
- if (!retentionDays.HasValue || retentionDays <= 0)
+ if (!retentionDays.HasValue || retentionDays < 0)
{
throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}");
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index 692d1667d..a575b260c 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -29,6 +29,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
/// <summary>
/// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
/// </summary>
+ /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
public DeleteCacheFileTask(
IApplicationPaths appPaths,
ILogger<DeleteCacheFileTask> logger,
diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs
deleted file mode 100644
index e8eac315b..000000000
--- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs
+++ /dev/null
@@ -1,408 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using Emby.Server.Implementations.Data;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.Devices;
-using MediaBrowser.Model.Querying;
-using Microsoft.Extensions.Logging;
-using SQLitePCL.pretty;
-
-namespace Emby.Server.Implementations.Security
-{
- public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository
- {
- public AuthenticationRepository(ILogger<AuthenticationRepository> logger, IServerConfigurationManager config)
- : base(logger)
- {
- DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db");
- }
-
- public void Initialize()
- {
- string[] queries =
- {
- "create table if not exists Tokens (Id INTEGER PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT NOT NULL, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, DeviceName TEXT NOT NULL, UserId TEXT, UserName TEXT, IsActive BIT NOT NULL, DateCreated DATETIME NOT NULL, DateLastActivity DATETIME NOT NULL)",
- "create table if not exists Devices (Id TEXT NOT NULL PRIMARY KEY, CustomName TEXT, Capabilities TEXT)",
- "drop index if exists idx_AccessTokens",
- "drop index if exists Tokens1",
- "drop index if exists Tokens2",
-
- "create index if not exists Tokens3 on Tokens (AccessToken, DateLastActivity)",
- "create index if not exists Tokens4 on Tokens (Id, DateLastActivity)",
- "create index if not exists Devices1 on Devices (Id)"
- };
-
- using (var connection = GetConnection())
- {
- var tableNewlyCreated = !TableExists(connection, "Tokens");
-
- connection.RunQueries(queries);
-
- TryMigrate(connection, tableNewlyCreated);
- }
- }
-
- private void TryMigrate(ManagedConnection connection, bool tableNewlyCreated)
- {
- try
- {
- if (tableNewlyCreated && TableExists(connection, "AccessTokens"))
- {
- connection.RunInTransaction(
- db =>
- {
- var existingColumnNames = GetColumnNames(db, "AccessTokens");
-
- AddColumn(db, "AccessTokens", "UserName", "TEXT", existingColumnNames);
- AddColumn(db, "AccessTokens", "DateLastActivity", "DATETIME", existingColumnNames);
- AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames);
- }, TransactionMode);
-
- connection.RunQueries(new[]
- {
- "update accesstokens set DateLastActivity=DateCreated where DateLastActivity is null",
- "update accesstokens set DeviceName='Unknown' where DeviceName is null",
- "update accesstokens set AppName='Unknown' where AppName is null",
- "update accesstokens set AppVersion='1' where AppVersion is null",
- "INSERT INTO Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) SELECT AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity FROM AccessTokens where deviceid not null and devicename not null and appname not null and isactive=1"
- });
- }
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error migrating authentication database");
- }
- }
-
- public void Create(AuthenticationInfo info)
- {
- if (info == null)
- {
- throw new ArgumentNullException(nameof(info));
- }
-
- using (var connection = GetConnection())
- {
- connection.RunInTransaction(
- db =>
- {
- using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)"))
- {
- statement.TryBind("@AccessToken", info.AccessToken);
-
- statement.TryBind("@DeviceId", info.DeviceId);
- statement.TryBind("@AppName", info.AppName);
- statement.TryBind("@AppVersion", info.AppVersion);
- statement.TryBind("@DeviceName", info.DeviceName);
- statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture));
- statement.TryBind("@UserName", info.UserName);
- statement.TryBind("@IsActive", true);
- statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
- statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
-
- statement.MoveNext();
- }
- }, TransactionMode);
- }
- }
-
- public void Update(AuthenticationInfo info)
- {
- if (info == null)
- {
- throw new ArgumentNullException(nameof(info));
- }
-
- using (var connection = GetConnection())
- {
- connection.RunInTransaction(
- db =>
- {
- using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id"))
- {
- statement.TryBind("@Id", info.Id);
-
- statement.TryBind("@AccessToken", info.AccessToken);
-
- statement.TryBind("@DeviceId", info.DeviceId);
- statement.TryBind("@AppName", info.AppName);
- statement.TryBind("@AppVersion", info.AppVersion);
- statement.TryBind("@DeviceName", info.DeviceName);
- statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture));
- statement.TryBind("@UserName", info.UserName);
- statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
- statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
-
- statement.MoveNext();
- }
- }, TransactionMode);
- }
- }
-
- public void Delete(AuthenticationInfo info)
- {
- if (info == null)
- {
- throw new ArgumentNullException(nameof(info));
- }
-
- using (var connection = GetConnection())
- {
- connection.RunInTransaction(
- db =>
- {
- using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id"))
- {
- statement.TryBind("@Id", info.Id);
-
- statement.MoveNext();
- }
- }, TransactionMode);
- }
- }
-
- private const string BaseSelectText = "select Tokens.Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, DateCreated, DateLastActivity, Devices.CustomName from Tokens left join Devices on Tokens.DeviceId=Devices.Id";
-
- private static void BindAuthenticationQueryParams(AuthenticationInfoQuery query, IStatement statement)
- {
- if (!string.IsNullOrEmpty(query.AccessToken))
- {
- statement.TryBind("@AccessToken", query.AccessToken);
- }
-
- if (!query.UserId.Equals(Guid.Empty))
- {
- statement.TryBind("@UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture));
- }
-
- if (!string.IsNullOrEmpty(query.DeviceId))
- {
- statement.TryBind("@DeviceId", query.DeviceId);
- }
- }
-
- public QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query)
- {
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
-
- var commandText = BaseSelectText;
-
- var whereClauses = new List<string>();
-
- if (!string.IsNullOrEmpty(query.AccessToken))
- {
- whereClauses.Add("AccessToken=@AccessToken");
- }
-
- if (!string.IsNullOrEmpty(query.DeviceId))
- {
- whereClauses.Add("DeviceId=@DeviceId");
- }
-
- if (!query.UserId.Equals(Guid.Empty))
- {
- whereClauses.Add("UserId=@UserId");
- }
-
- if (query.HasUser.HasValue)
- {
- if (query.HasUser.Value)
- {
- whereClauses.Add("UserId not null");
- }
- else
- {
- whereClauses.Add("UserId is null");
- }
- }
-
- var whereTextWithoutPaging = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
-
- commandText += whereTextWithoutPaging;
-
- commandText += " ORDER BY DateLastActivity desc";
-
- if (query.Limit.HasValue || query.StartIndex.HasValue)
- {
- var offset = query.StartIndex ?? 0;
-
- if (query.Limit.HasValue || offset > 0)
- {
- commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
- }
-
- if (offset > 0)
- {
- commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
- }
- }
-
- var statementTexts = new[]
- {
- commandText,
- "select count (Id) from Tokens" + whereTextWithoutPaging
- };
-
- var list = new List<AuthenticationInfo>();
- var result = new QueryResult<AuthenticationInfo>();
- using (var connection = GetConnection(true))
- {
- connection.RunInTransaction(
- db =>
- {
- var statements = PrepareAll(db, statementTexts);
-
- using (var statement = statements[0])
- {
- BindAuthenticationQueryParams(query, statement);
-
- foreach (var row in statement.ExecuteQuery())
- {
- list.Add(Get(row));
- }
-
- using (var totalCountStatement = statements[1])
- {
- BindAuthenticationQueryParams(query, totalCountStatement);
-
- result.TotalRecordCount = totalCountStatement.ExecuteQuery()
- .SelectScalarInt()
- .First();
- }
- }
- },
- ReadTransactionMode);
- }
-
- result.Items = list;
- return result;
- }
-
- private static AuthenticationInfo Get(IReadOnlyList<ResultSetValue> reader)
- {
- var info = new AuthenticationInfo
- {
- Id = reader[0].ToInt64(),
- AccessToken = reader[1].ToString()
- };
-
- if (reader.TryGetString(2, out var deviceId))
- {
- info.DeviceId = deviceId;
- }
-
- if (reader.TryGetString(3, out var appName))
- {
- info.AppName = appName;
- }
-
- if (reader.TryGetString(4, out var appVersion))
- {
- info.AppVersion = appVersion;
- }
-
- if (reader.TryGetString(6, out var userId))
- {
- info.UserId = new Guid(userId);
- }
-
- if (reader.TryGetString(7, out var userName))
- {
- info.UserName = userName;
- }
-
- info.DateCreated = reader[8].ReadDateTime();
-
- if (reader.TryReadDateTime(9, out var dateLastActivity))
- {
- info.DateLastActivity = dateLastActivity;
- }
- else
- {
- info.DateLastActivity = info.DateCreated;
- }
-
- if (reader.TryGetString(10, out var customName))
- {
- info.DeviceName = customName;
- }
- else if (reader.TryGetString(5, out var deviceName))
- {
- info.DeviceName = deviceName;
- }
-
- return info;
- }
-
- public DeviceOptions GetDeviceOptions(string deviceId)
- {
- using (var connection = GetConnection(true))
- {
- return connection.RunInTransaction(
- db =>
- {
- using (var statement = base.PrepareStatement(db, "select CustomName from Devices where Id=@DeviceId"))
- {
- statement.TryBind("@DeviceId", deviceId);
-
- var result = new DeviceOptions();
-
- foreach (var row in statement.ExecuteQuery())
- {
- if (row.TryGetString(0, out var customName))
- {
- result.CustomName = customName;
- }
- }
-
- return result;
- }
- }, ReadTransactionMode);
- }
- }
-
- public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
- {
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
-
- using (var connection = GetConnection())
- {
- connection.RunInTransaction(
- db =>
- {
- using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))"))
- {
- statement.TryBind("@Id", deviceId);
-
- if (string.IsNullOrWhiteSpace(options.CustomName))
- {
- statement.TryBindNull("@CustomName");
- }
- else
- {
- statement.TryBind("@CustomName", options.CustomName);
- }
-
- statement.MoveNext();
- }
- }, TransactionMode);
- }
- }
- }
-}
diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs
index 5ff73de81..059211a0b 100644
--- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs
+++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs
@@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.Serialization
/// <param name="file">The file.</param>
public void SerializeToFile(object obj, string file)
{
- using (var stream = new FileStream(file, FileMode.Create))
+ using (var stream = new FileStream(file, FileMode.Create, FileAccess.Write))
{
SerializeToStream(obj, stream);
}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index c4b19f417..334ce5c9d 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -10,8 +10,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
+using Jellyfin.Data.Entities.Security;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
+using Jellyfin.Data.Queries;
using Jellyfin.Extensions;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
@@ -25,9 +27,7 @@ using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Events.Session;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Library;
@@ -55,7 +55,6 @@ namespace Emby.Server.Implementations.Session
private readonly IImageProcessor _imageProcessor;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerApplicationHost _appHost;
- private readonly IAuthenticationRepository _authRepo;
private readonly IDeviceManager _deviceManager;
/// <summary>
@@ -78,7 +77,6 @@ namespace Emby.Server.Implementations.Session
IDtoService dtoService,
IImageProcessor imageProcessor,
IServerApplicationHost appHost,
- IAuthenticationRepository authRepo,
IDeviceManager deviceManager,
IMediaSourceManager mediaSourceManager)
{
@@ -91,7 +89,6 @@ namespace Emby.Server.Implementations.Session
_dtoService = dtoService;
_imageProcessor = imageProcessor;
_appHost = appHost;
- _authRepo = authRepo;
_deviceManager = deviceManager;
_mediaSourceManager = mediaSourceManager;
@@ -238,12 +235,12 @@ namespace Emby.Server.Implementations.Session
}
/// <inheritdoc />
- public void UpdateDeviceName(string sessionId, string deviceName)
+ public void UpdateDeviceName(string sessionId, string reportedDeviceName)
{
var session = GetSession(sessionId);
if (session != null)
{
- session.DeviceName = deviceName;
+ session.DeviceName = reportedDeviceName;
}
}
@@ -257,7 +254,7 @@ namespace Emby.Server.Implementations.Session
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>SessionInfo.</returns>
- public SessionInfo LogSessionActivity(
+ public async Task<SessionInfo> LogSessionActivity(
string appName,
string appVersion,
string deviceId,
@@ -283,7 +280,7 @@ namespace Emby.Server.Implementations.Session
}
var activityDate = DateTime.UtcNow;
- var session = GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user);
+ var session = await GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false);
var lastActivityDate = session.LastActivityDate;
session.LastActivityDate = activityDate;
@@ -296,7 +293,7 @@ namespace Emby.Server.Implementations.Session
try
{
user.LastActivityDate = activityDate;
- _userManager.UpdateUser(user);
+ await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
}
catch (DbUpdateConcurrencyException e)
{
@@ -319,14 +316,14 @@ namespace Emby.Server.Implementations.Session
}
/// <inheritdoc />
- public void OnSessionControllerConnected(SessionInfo info)
+ public void OnSessionControllerConnected(SessionInfo session)
{
EventHelper.QueueEventIfNotNull(
SessionControllerConnected,
this,
new SessionEventArgs
{
- SessionInfo = info
+ SessionInfo = session
},
_logger);
}
@@ -461,7 +458,7 @@ namespace Emby.Server.Implementations.Session
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>SessionInfo.</returns>
- private SessionInfo GetSessionInfo(
+ private async Task<SessionInfo> GetSessionInfo(
string appName,
string appVersion,
string deviceId,
@@ -480,9 +477,11 @@ namespace Emby.Server.Implementations.Session
CheckDisposed();
- var sessionInfo = _activeConnections.GetOrAdd(
- key,
- k => CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user));
+ if (!_activeConnections.TryGetValue(key, out var sessionInfo))
+ {
+ _activeConnections[key] = await CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false);
+ sessionInfo = _activeConnections[key];
+ }
sessionInfo.UserId = user?.Id ?? Guid.Empty;
sessionInfo.UserName = user?.Username;
@@ -505,7 +504,7 @@ namespace Emby.Server.Implementations.Session
return sessionInfo;
}
- private SessionInfo CreateSession(
+ private async Task<SessionInfo> CreateSession(
string key,
string appName,
string appVersion,
@@ -535,7 +534,7 @@ namespace Emby.Server.Implementations.Session
deviceName = "Network Device";
}
- var deviceOptions = _deviceManager.GetDeviceOptions(deviceId);
+ var deviceOptions = await _deviceManager.GetDeviceOptions(deviceId).ConfigureAwait(false);
if (string.IsNullOrEmpty(deviceOptions.CustomName))
{
sessionInfo.DeviceName = deviceName;
@@ -1200,16 +1199,18 @@ namespace Emby.Server.Implementations.Session
}
/// <inheritdoc />
- public async Task SendSyncPlayCommand(SessionInfo session, SendCommand command, CancellationToken cancellationToken)
+ public async Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken)
{
CheckDisposed();
+ var session = GetSession(sessionId);
await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
- public async Task SendSyncPlayGroupUpdate<T>(SessionInfo session, GroupUpdate<T> command, CancellationToken cancellationToken)
+ public async Task SendSyncPlayGroupUpdate<T>(string sessionId, GroupUpdate<T> command, CancellationToken cancellationToken)
{
CheckDisposed();
+ var session = GetSession(sessionId);
await SendMessageToSession(session, SessionMessageType.SyncPlayGroupUpdate, command, cancellationToken).ConfigureAwait(false);
}
@@ -1433,38 +1434,20 @@ namespace Emby.Server.Implementations.Session
/// <summary>
/// Authenticates the new session.
/// </summary>
- /// <param name="request">The request.</param>
- /// <returns>Task{SessionInfo}.</returns>
+ /// <param name="request">The authenticationrequest.</param>
+ /// <returns>The authentication result.</returns>
public Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
{
return AuthenticateNewSessionInternal(request, true);
}
- public Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request)
- {
- return AuthenticateNewSessionInternal(request, false);
- }
-
- public Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token)
+ /// <summary>
+ /// Directly authenticates the session without enforcing password.
+ /// </summary>
+ /// <param name="request">The authentication request.</param>
+ /// <returns>The authentication result.</returns>
+ public Task<AuthenticationResult> AuthenticateDirect(AuthenticationRequest request)
{
- var result = _authRepo.Get(new AuthenticationInfoQuery()
- {
- AccessToken = token,
- DeviceId = _appHost.SystemId,
- Limit = 1
- });
-
- if (result.TotalRecordCount == 0)
- {
- throw new SecurityException("Unknown quick connect token");
- }
-
- var info = result.Items[0];
- request.UserId = info.UserId;
-
- // There's no need to keep the quick connect token in the database, as AuthenticateNewSessionInternal() issues a long lived token.
- _authRepo.Delete(info);
-
return AuthenticateNewSessionInternal(request, false);
}
@@ -1510,15 +1493,15 @@ namespace Emby.Server.Implementations.Session
throw new SecurityException("User is at their maximum number of sessions.");
}
- var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName);
+ var token = await GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false);
- var session = LogSessionActivity(
+ var session = await LogSessionActivity(
request.App,
request.AppVersion,
request.DeviceId,
request.DeviceName,
request.RemoteEndPoint,
- user);
+ user).ConfigureAwait(false);
var returnResult = new AuthenticationResult
{
@@ -1533,36 +1516,33 @@ namespace Emby.Server.Implementations.Session
return returnResult;
}
- private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
+ private async Task<string> GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
{
- var existing = _authRepo.Get(
- new AuthenticationInfoQuery
+ var existing = (await _deviceManager.GetDevices(
+ new DeviceQuery
{
DeviceId = deviceId,
UserId = user.Id,
Limit = 1
- }).Items.FirstOrDefault();
+ }).ConfigureAwait(false)).Items.FirstOrDefault();
- if (!string.IsNullOrEmpty(deviceId))
- {
- var allExistingForDevice = _authRepo.Get(
- new AuthenticationInfoQuery
- {
- DeviceId = deviceId
- }).Items;
+ var allExistingForDevice = (await _deviceManager.GetDevices(
+ new DeviceQuery
+ {
+ DeviceId = deviceId
+ }).ConfigureAwait(false)).Items;
- foreach (var auth in allExistingForDevice)
+ foreach (var auth in allExistingForDevice)
+ {
+ if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal))
{
- if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal))
+ try
{
- try
- {
- Logout(auth);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error while logging out.");
- }
+ await Logout(auth).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error while logging out.");
}
}
}
@@ -1573,29 +1553,14 @@ namespace Emby.Server.Implementations.Session
return existing.AccessToken;
}
- var now = DateTime.UtcNow;
-
- var newToken = new AuthenticationInfo
- {
- AppName = app,
- AppVersion = appVersion,
- DateCreated = now,
- DateLastActivity = now,
- DeviceId = deviceId,
- DeviceName = deviceName,
- UserId = user.Id,
- AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
- UserName = user.Username
- };
-
_logger.LogInformation("Creating new access token for user {0}", user.Id);
- _authRepo.Create(newToken);
+ var device = await _deviceManager.CreateDevice(new Device(user.Id, app, appVersion, deviceName, deviceId)).ConfigureAwait(false);
- return newToken.AccessToken;
+ return device.AccessToken;
}
/// <inheritdoc />
- public void Logout(string accessToken)
+ public async Task Logout(string accessToken)
{
CheckDisposed();
@@ -1604,30 +1569,30 @@ namespace Emby.Server.Implementations.Session
throw new ArgumentNullException(nameof(accessToken));
}
- var existing = _authRepo.Get(
- new AuthenticationInfoQuery
+ var existing = (await _deviceManager.GetDevices(
+ new DeviceQuery
{
Limit = 1,
AccessToken = accessToken
- }).Items;
+ }).ConfigureAwait(false)).Items;
if (existing.Count > 0)
{
- Logout(existing[0]);
+ await Logout(existing[0]).ConfigureAwait(false);
}
}
/// <inheritdoc />
- public void Logout(AuthenticationInfo existing)
+ public async Task Logout(Device device)
{
CheckDisposed();
- _logger.LogInformation("Logging out access token {0}", existing.AccessToken);
+ _logger.LogInformation("Logging out access token {0}", device.AccessToken);
- _authRepo.Delete(existing);
+ await _deviceManager.DeleteDevice(device).ConfigureAwait(false);
var sessions = Sessions
- .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase))
+ .Where(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var session in sessions)
@@ -1638,36 +1603,30 @@ namespace Emby.Server.Implementations.Session
}
catch (Exception ex)
{
- _logger.LogError("Error reporting session ended", ex);
+ _logger.LogError(ex, "Error reporting session ended");
}
}
}
/// <inheritdoc />
- public void RevokeUserTokens(Guid userId, string currentAccessToken)
+ public async Task RevokeUserTokens(Guid userId, string currentAccessToken)
{
CheckDisposed();
- var existing = _authRepo.Get(new AuthenticationInfoQuery
+ var existing = await _deviceManager.GetDevices(new DeviceQuery
{
UserId = userId
- });
+ }).ConfigureAwait(false);
foreach (var info in existing.Items)
{
if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase))
{
- Logout(info);
+ await Logout(info).ConfigureAwait(false);
}
}
}
- /// <inheritdoc />
- public void RevokeToken(string token)
- {
- Logout(token);
- }
-
/// <summary>
/// Reports the capabilities.
/// </summary>
@@ -1787,18 +1746,9 @@ namespace Emby.Server.Implementations.Session
}
var item = _libraryManager.GetItemById(new Guid(itemId));
-
- var info = GetItemInfo(item, null);
-
- ReportNowViewingItem(sessionId, info);
- }
-
- /// <inheritdoc />
- public void ReportNowViewingItem(string sessionId, BaseItemDto item)
- {
var session = GetSession(sessionId);
- session.NowViewingItem = item;
+ session.NowViewingItem = GetItemInfo(item, null);
}
/// <inheritdoc />
@@ -1828,7 +1778,7 @@ namespace Emby.Server.Implementations.Session
}
/// <inheritdoc />
- public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion)
+ public Task<SessionInfo> GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion)
{
if (info == null)
{
@@ -1861,20 +1811,20 @@ namespace Emby.Server.Implementations.Session
}
/// <inheritdoc />
- public SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
+ public async Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
{
- var items = _authRepo.Get(new AuthenticationInfoQuery
+ var items = (await _deviceManager.GetDevices(new DeviceQuery
{
AccessToken = token,
Limit = 1
- }).Items;
+ }).ConfigureAwait(false)).Items;
if (items.Count == 0)
{
return null;
}
- return GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null);
+ return await GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null).ConfigureAwait(false);
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index e9e3ca7f4..2a14a8c7b 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.Session
/// <inheritdoc />
public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection)
{
- var session = GetSession(connection.QueryString, connection.RemoteEndPoint.ToString());
+ var session = await GetSession(connection.QueryString, connection.RemoteEndPoint.ToString()).ConfigureAwait(false);
if (session != null)
{
EnsureController(session, connection);
@@ -111,7 +111,7 @@ namespace Emby.Server.Implementations.Session
}
}
- private SessionInfo GetSession(IQueryCollection queryString, string remoteEndpoint)
+ private Task<SessionInfo> GetSession(IQueryCollection queryString, string remoteEndpoint)
{
if (queryString == null)
{
diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs
index 98bee3fd9..7b7ba5753 100644
--- a/Emby.Server.Implementations/Sorting/ArtistComparer.cs
+++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.String.</returns>
private static string? GetValue(BaseItem? x)
{
- if (!(x is Audio audio))
+ if (x is not Audio audio)
{
return string.Empty;
}
diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs
index 12efff261..75cf890e5 100644
--- a/Emby.Server.Implementations/SyncPlay/Group.cs
+++ b/Emby.Server.Implementations/SyncPlay/Group.cs
@@ -164,26 +164,26 @@ namespace Emby.Server.Implementations.SyncPlay
/// <summary>
/// Filters sessions of this group.
/// </summary>
- /// <param name="from">The current session.</param>
+ /// <param name="fromId">The current session identifier.</param>
/// <param name="type">The filtering type.</param>
/// <returns>The list of sessions matching the filter.</returns>
- private IEnumerable<SessionInfo> FilterSessions(SessionInfo from, SyncPlayBroadcastType type)
+ private IEnumerable<string> FilterSessions(string fromId, SyncPlayBroadcastType type)
{
return type switch
{
- SyncPlayBroadcastType.CurrentSession => new SessionInfo[] { from },
+ SyncPlayBroadcastType.CurrentSession => new string[] { fromId },
SyncPlayBroadcastType.AllGroup => _participants
.Values
- .Select(session => session.Session),
+ .Select(member => member.SessionId),
SyncPlayBroadcastType.AllExceptCurrentSession => _participants
.Values
- .Select(session => session.Session)
- .Where(session => !session.Id.Equals(from.Id, StringComparison.OrdinalIgnoreCase)),
+ .Select(member => member.SessionId)
+ .Where(sessionId => !sessionId.Equals(fromId, StringComparison.OrdinalIgnoreCase)),
SyncPlayBroadcastType.AllReady => _participants
.Values
- .Where(session => !session.IsBuffering)
- .Select(session => session.Session),
- _ => Enumerable.Empty<SessionInfo>()
+ .Where(member => !member.IsBuffering)
+ .Select(member => member.SessionId),
+ _ => Enumerable.Empty<string>()
};
}
@@ -225,7 +225,7 @@ namespace Emby.Server.Implementations.SyncPlay
// Get list of users.
var users = _participants
.Values
- .Select(participant => _userManager.GetUserById(participant.Session.UserId));
+ .Select(participant => _userManager.GetUserById(participant.UserId));
// Find problematic users.
var usersWithNoAccess = users.Where(user => !HasAccessToQueue(user, queue));
@@ -353,7 +353,7 @@ namespace Emby.Server.Implementations.SyncPlay
/// <returns>The group info for the clients.</returns>
public GroupInfoDto GetInfo()
{
- var participants = _participants.Values.Select(session => session.Session.UserName).Distinct().ToList();
+ var participants = _participants.Values.Select(session => session.UserName).Distinct().ToList();
return new GroupInfoDto(GroupId, GroupName, _state.Type, participants, DateTime.UtcNow);
}
@@ -389,9 +389,9 @@ namespace Emby.Server.Implementations.SyncPlay
{
IEnumerable<Task> GetTasks()
{
- foreach (var session in FilterSessions(from, type))
+ foreach (var sessionId in FilterSessions(from.Id, type))
{
- yield return _sessionManager.SendSyncPlayGroupUpdate(session, message, cancellationToken);
+ yield return _sessionManager.SendSyncPlayGroupUpdate(sessionId, message, cancellationToken);
}
}
@@ -403,9 +403,9 @@ namespace Emby.Server.Implementations.SyncPlay
{
IEnumerable<Task> GetTasks()
{
- foreach (var session in FilterSessions(from, type))
+ foreach (var sessionId in FilterSessions(from.Id, type))
{
- yield return _sessionManager.SendSyncPlayCommand(session, message, cancellationToken);
+ yield return _sessionManager.SendSyncPlayCommand(sessionId, message, cancellationToken);
}
}
@@ -537,6 +537,16 @@ namespace Emby.Server.Implementations.SyncPlay
}
/// <inheritdoc />
+ public void ClearPlayQueue(bool clearPlayingItem)
+ {
+ PlayQueue.ClearPlaylist(clearPlayingItem);
+ if (clearPlayingItem)
+ {
+ RestartCurrentItem();
+ }
+ }
+
+ /// <inheritdoc />
public bool RemoveFromPlayQueue(IReadOnlyList<Guid> playlistItemIds)
{
var playingItemRemoved = PlayQueue.RemoveFromPlaylist(playlistItemIds);
@@ -649,8 +659,9 @@ namespace Emby.Server.Implementations.SyncPlay
public PlayQueueUpdate GetPlayQueueUpdate(PlayQueueUpdateReason reason)
{
var startPositionTicks = PositionTicks;
+ var isPlaying = _state.Type.Equals(GroupStateType.Playing);
- if (_state.Type.Equals(GroupStateType.Playing))
+ if (isPlaying)
{
var currentTime = DateTime.UtcNow;
var elapsedTime = currentTime - LastActivity;
@@ -669,6 +680,7 @@ namespace Emby.Server.Implementations.SyncPlay
PlayQueue.GetPlaylist(),
PlayQueue.PlayingItemIndex,
startPositionTicks,
+ isPlaying,
PlayQueue.ShuffleMode,
PlayQueue.RepeatMode);
}
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
index 993456196..2ebeea717 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
@@ -160,7 +160,7 @@ namespace Emby.Server.Implementations.SyncPlay
_logger.LogWarning("Session {SessionId} tried to join group {GroupId} that does not exist.", session.Id, request.GroupId);
var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.GroupDoesNotExist, string.Empty);
- _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None);
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -172,7 +172,7 @@ namespace Emby.Server.Implementations.SyncPlay
_logger.LogWarning("Session {SessionId} tried to join group {GroupId} but does not have access to some content of the playing queue.", session.Id, group.GroupId.ToString());
var error = new GroupUpdate<string>(group.GroupId, GroupUpdateType.LibraryAccessDenied, string.Empty);
- _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None);
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -249,7 +249,7 @@ namespace Emby.Server.Implementations.SyncPlay
_logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id);
var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty);
- _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None);
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
}
@@ -329,7 +329,7 @@ namespace Emby.Server.Implementations.SyncPlay
_logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id);
var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty);
- _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None);
+ _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
}
}
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index af453d148..4d990c871 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -33,9 +33,9 @@ namespace Emby.Server.Implementations.TV
_configurationManager = configurationManager;
}
- public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
+ public QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options)
{
- var user = _userManager.GetUserById(request.UserId);
+ var user = _userManager.GetUserById(query.UserId);
if (user == null)
{
@@ -43,9 +43,9 @@ namespace Emby.Server.Implementations.TV
}
string presentationUniqueKey = null;
- if (!string.IsNullOrEmpty(request.SeriesId))
+ if (!string.IsNullOrEmpty(query.SeriesId))
{
- if (_libraryManager.GetItemById(request.SeriesId) is Series series)
+ if (_libraryManager.GetItemById(query.SeriesId) is Series series)
{
presentationUniqueKey = GetUniqueSeriesKey(series);
}
@@ -53,14 +53,14 @@ namespace Emby.Server.Implementations.TV
if (!string.IsNullOrEmpty(presentationUniqueKey))
{
- return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request);
+ return GetResult(GetNextUpEpisodes(query, user, new[] { presentationUniqueKey }, options), query);
}
BaseItem[] parents;
- if (request.ParentId.HasValue)
+ if (query.ParentId.HasValue)
{
- var parent = _libraryManager.GetItemById(request.ParentId.Value);
+ var parent = _libraryManager.GetItemById(query.ParentId.Value);
if (parent != null)
{
@@ -79,10 +79,10 @@ namespace Emby.Server.Implementations.TV
.ToArray();
}
- return GetNextUp(request, parents, dtoOptions);
+ return GetNextUp(query, parents, options);
}
- public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions dtoOptions)
+ public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options)
{
var user = _userManager.GetUserById(request.UserId);
@@ -104,7 +104,7 @@ namespace Emby.Server.Implementations.TV
if (!string.IsNullOrEmpty(presentationUniqueKey))
{
- return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request);
+ return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, options), request);
}
if (limit.HasValue)
@@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.TV
.Select(GetUniqueSeriesKey);
// Avoid implicitly captured closure
- var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
+ var episodes = GetNextUpEpisodes(request, user, items, options);
return GetResult(episodes, request);
}