Merge branch 'master' into bug/authorization-header-issue

pull/4799/head
cvium 3 years ago
commit 6b3ecf2533

3
.gitignore vendored

@ -278,3 +278,6 @@ web/
web-src.*
MediaBrowser.WebDashboard/jellyfin-web
apiclient/generated
# Omnisharp crash logs
mono_crash.*.json

@ -17,7 +17,7 @@ namespace Emby.Dlna.ContentDirectory
{
Item = item;
if (item is IItemByName && !(item is Folder))
if (item is IItemByName && item is not Folder)
{
StubType = Dlna.ContentDirectory.StubType.Folder;
}

@ -748,7 +748,7 @@ namespace Emby.Dlna.Didl
AddValue(writer, "upnp", "publisher", studio, NsUpnp);
}
if (!(item is Folder))
if (item is not Folder)
{
if (filter.Contains("dc:description"))
{

@ -173,7 +173,9 @@ namespace Emby.Dlna.PlayTo
uuid = uri.ToString().GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null);
var sessionInfo = await _sessionManager
.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null)
.ConfigureAwait(false);
var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();

@ -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;
@ -128,6 +130,62 @@ namespace Emby.Server.Implementations
private ISessionManager _sessionManager;
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>
@ -158,11 +216,6 @@ namespace Emby.Server.Implementations
/// </summary>
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>
@ -190,17 +243,6 @@ namespace Emby.Server.Implementations
/// <value>The application paths.</value>
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>
@ -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[] { "+" };
@ -1123,9 +1124,6 @@ namespace Emby.Server.Implementations
};
}
/// <inheritdoc/>
public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps;
/// <inheritdoc/>
public string GetSmartApiUrl(IPAddress remoteAddr, int? port = null)
{
@ -1212,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)
@ -1257,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);

@ -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 />
@ -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))
{

@ -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)
{

@ -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";

@ -75,6 +75,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 +1141,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 +1902,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 +1925,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 +2037,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 +4884,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;

@ -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();

@ -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;
}
}
}

@ -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);

@ -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;
}

@ -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,

@ -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)
{

@ -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);
}

@ -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; }

@ -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);

@ -423,7 +423,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 +437,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;
}

@ -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; }

@ -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)
{

@ -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" };
}

@ -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)
@ -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));
}
}

@ -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)

@ -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();

@ -17,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>
@ -81,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);
@ -296,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);
}
}
}

@ -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()
{

@ -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;

@ -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
};

@ -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;

@ -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);

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -33,7 +31,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,7 +43,7 @@ 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))
@ -71,7 +69,7 @@ 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);

@ -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));

@ -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");

@ -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;

@ -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);
}

@ -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));
}

@ -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; }
}
}
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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)
{

@ -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)
{

@ -95,17 +95,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(tuner, i),
IsFavorite = i.Favorite,
TunerHostId = info.Id,
TunerHostId = tuner.Id,
IsHD = i.HD,
AudioCodec = i.AudioCodec,
VideoCodec = i.VideoCodec,
@ -496,57 +496,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)
@ -557,26 +553,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var profile = streamId.Split('_')[0];
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 +588,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 +600,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return new SharedHttpStream(
mediaSource,
info,
tunerHost,
streamId,
FileSystem,
_httpClientFactory,
@ -616,7 +612,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return new HdHomerunUdpStream(
mediaSource,
info,
tunerHost,
streamId,
new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
modelInfo.TunerCount,

@ -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

@ -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)

@ -295,11 +295,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))

@ -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": "Неразпознат сезон",

@ -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",

@ -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)

@ -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)
{

@ -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");
}

@ -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

@ -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;
@ -19,11 +20,6 @@ namespace Emby.Server.Implementations.QuickConnect
/// </summary>
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>
@ -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);
}
}
}
}
}
}

@ -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>

@ -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}");
}

@ -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,

@ -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);
}
}
}
}

@ -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;
@ -1433,41 +1432,23 @@ 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)
/// <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)
{
return AuthenticateNewSessionInternal(request, false);
}
public Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token)
{
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);
}
private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)
{
CheckDisposed();
@ -1510,15 +1491,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 +1514,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 +1551,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 +1567,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 +1601,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 +1744,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 +1776,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 +1809,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 />

@ -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)
{

@ -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;
}

@ -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);
}

@ -40,11 +40,11 @@ namespace Jellyfin.Api.Auth
}
/// <inheritdoc />
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
try
{
var authorizationInfo = _authService.Authenticate(Request);
var authorizationInfo = await _authService.Authenticate(Request).ConfigureAwait(false);
var role = UserRoles.User;
if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
{
@ -68,16 +68,16 @@ namespace Jellyfin.Api.Auth
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
return AuthenticateResult.Success(ticket);
}
catch (AuthenticationException ex)
{
_logger.LogDebug(ex, "Error authenticating with {Handler}", nameof(CustomAuthenticationHandler));
return Task.FromResult(AuthenticateResult.NoResult());
return AuthenticateResult.NoResult();
}
catch (SecurityException ex)
{
return Task.FromResult(AuthenticateResult.Fail(ex));
return AuthenticateResult.Fail(ex);
}
}
}

@ -47,7 +47,7 @@ namespace Jellyfin.Api.Controllers
{
return await _activityManager.GetPagedResultAsync(new ActivityLogQuery
{
StartIndex = startIndex,
Skip = startIndex,
Limit = limit,
MinDate = minDate,
HasUserId = hasUserId

@ -1,10 +1,8 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@ -18,24 +16,15 @@ namespace Jellyfin.Api.Controllers
[Route("Auth")]
public class ApiKeyController : BaseJellyfinApiController
{
private readonly ISessionManager _sessionManager;
private readonly IServerApplicationHost _appHost;
private readonly IAuthenticationRepository _authRepo;
private readonly IAuthenticationManager _authenticationManager;
/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyController"/> class.
/// </summary>
/// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="authRepo">Instance of <see cref="IAuthenticationRepository"/> interface.</param>
public ApiKeyController(
ISessionManager sessionManager,
IServerApplicationHost appHost,
IAuthenticationRepository authRepo)
/// <param name="authenticationManager">Instance of <see cref="IAuthenticationManager"/> interface.</param>
public ApiKeyController(IAuthenticationManager authenticationManager)
{
_sessionManager = sessionManager;
_appHost = appHost;
_authRepo = authRepo;
_authenticationManager = authenticationManager;
}
/// <summary>
@ -46,14 +35,15 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Keys")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<AuthenticationInfo>> GetKeys()
public async Task<ActionResult<QueryResult<AuthenticationInfo>>> GetKeys()
{
var result = _authRepo.Get(new AuthenticationInfoQuery
{
HasUser = false
});
var keys = await _authenticationManager.GetApiKeys();
return result;
return new QueryResult<AuthenticationInfo>
{
Items = keys,
TotalRecordCount = keys.Count
};
}
/// <summary>
@ -65,17 +55,10 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Keys")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CreateKey([FromQuery, Required] string app)
public async Task<ActionResult> CreateKey([FromQuery, Required] string app)
{
_authRepo.Create(new AuthenticationInfo
{
AppName = app,
AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
DateCreated = DateTime.UtcNow,
DeviceId = _appHost.SystemId,
DeviceName = _appHost.FriendlyName,
AppVersion = _appHost.ApplicationVersionString
});
await _authenticationManager.CreateApiKey(app).ConfigureAwait(false);
return NoContent();
}
@ -88,9 +71,10 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Keys/{key}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RevokeKey([FromRoute, Required] string key)
public async Task<ActionResult> RevokeKey([FromRoute, Required] string key)
{
_sessionManager.RevokeToken(key);
await _authenticationManager.DeleteApiKey(key).ConfigureAwait(false);
return NoContent();
}
}

@ -58,7 +58,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Guid? parentId,
[FromQuery] bool isLocked = false)
{
var userId = _authContext.GetAuthorizationInfo(Request).UserId;
var userId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).UserId;
var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
{

@ -1,8 +1,11 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Data.Dtos;
using Jellyfin.Data.Entities.Security;
using Jellyfin.Data.Queries;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Querying;
@ -19,22 +22,18 @@ namespace Jellyfin.Api.Controllers
public class DevicesController : BaseJellyfinApiController
{
private readonly IDeviceManager _deviceManager;
private readonly IAuthenticationRepository _authenticationRepository;
private readonly ISessionManager _sessionManager;
/// <summary>
/// Initializes a new instance of the <see cref="DevicesController"/> class.
/// </summary>
/// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param>
/// <param name="authenticationRepository">Instance of <see cref="IAuthenticationRepository"/> interface.</param>
/// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
public DevicesController(
IDeviceManager deviceManager,
IAuthenticationRepository authenticationRepository,
ISessionManager sessionManager)
{
_deviceManager = deviceManager;
_authenticationRepository = authenticationRepository;
_sessionManager = sessionManager;
}
@ -47,10 +46,9 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
{
var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty };
return _deviceManager.GetDevices(deviceQuery);
return await _deviceManager.GetDevicesForUser(userId, supportsSync).ConfigureAwait(false);
}
/// <summary>
@ -63,9 +61,9 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Info")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, Required] string id)
public async Task<ActionResult<DeviceInfo>> GetDeviceInfo([FromQuery, Required] string id)
{
var deviceInfo = _deviceManager.GetDevice(id);
var deviceInfo = await _deviceManager.GetDevice(id).ConfigureAwait(false);
if (deviceInfo == null)
{
return NotFound();
@ -84,9 +82,9 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Options")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, Required] string id)
public async Task<ActionResult<DeviceOptions>> GetDeviceOptions([FromQuery, Required] string id)
{
var deviceInfo = _deviceManager.GetDeviceOptions(id);
var deviceInfo = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false);
if (deviceInfo == null)
{
return NotFound();
@ -101,22 +99,14 @@ namespace Jellyfin.Api.Controllers
/// <param name="id">Device Id.</param>
/// <param name="deviceOptions">Device Options.</param>
/// <response code="204">Device options updated.</response>
/// <response code="404">Device not found.</response>
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Options")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateDeviceOptions(
public async Task<ActionResult> UpdateDeviceOptions(
[FromQuery, Required] string id,
[FromBody, Required] DeviceOptions deviceOptions)
[FromBody, Required] DeviceOptionsDto deviceOptions)
{
var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
if (existingDeviceOptions == null)
{
return NotFound();
}
_deviceManager.UpdateDeviceOptions(id, deviceOptions);
await _deviceManager.UpdateDeviceOptions(id, deviceOptions.CustomName).ConfigureAwait(false);
return NoContent();
}
@ -130,19 +120,19 @@ namespace Jellyfin.Api.Controllers
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteDevice([FromQuery, Required] string id)
public async Task<ActionResult> DeleteDevice([FromQuery, Required] string id)
{
var existingDevice = _deviceManager.GetDevice(id);
var existingDevice = await _deviceManager.GetDevice(id).ConfigureAwait(false);
if (existingDevice == null)
{
return NotFound();
}
var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items;
var sessions = await _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }).ConfigureAwait(false);
foreach (var session in sessions)
foreach (var session in sessions.Items)
{
_sessionManager.Logout(session);
await _sessionManager.Logout(session).ConfigureAwait(false);
}
return NoContent();

@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] ImageType imageType,
[FromQuery] int? index = null)
{
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
}
@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] ImageType imageType,
[FromRoute] int index)
{
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
}
@ -190,7 +190,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] ImageType imageType,
[FromQuery] int? index = null)
{
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
}
@ -234,7 +234,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] ImageType imageType,
[FromRoute] int index)
{
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
}

@ -154,11 +154,11 @@ namespace Jellyfin.Api.Controllers
};
if (!item.IsVirtualItem
&& !(item is ICollectionFolder)
&& !(item is UserView)
&& !(item is AggregateFolder)
&& !(item is LiveTvChannel)
&& !(item is IItemByName)
&& item is not ICollectionFolder
&& item is not UserView
&& item is not AggregateFolder
&& item is not LiveTvChannel
&& item is not IItemByName
&& item.SourceType == SourceType.Library)
{
var inheritedContentType = _libraryManager.GetInheritedContentType(item);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save