mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-06 15:58:29 +01:00
Initial migration code
This commit is contained in:
@@ -93,11 +93,10 @@ namespace Emby.Server.Implementations.Activity
|
||||
|
||||
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
|
||||
|
||||
_userManager.UserCreated += OnUserCreated;
|
||||
_userManager.UserPasswordChanged += OnUserPasswordChanged;
|
||||
_userManager.UserDeleted += OnUserDeleted;
|
||||
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
||||
_userManager.UserLockedOut += OnUserLockedOut;
|
||||
_userManager.OnUserCreated += OnUserCreated;
|
||||
_userManager.OnUserPasswordChanged += OnUserPasswordChanged;
|
||||
_userManager.OnUserDeleted += OnUserDeleted;
|
||||
_userManager.OnUserLockedOut += OnUserLockedOut;
|
||||
|
||||
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
|
||||
|
||||
@@ -118,13 +117,13 @@ namespace Emby.Server.Implementations.Activity
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnUserLockedOut(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserLockedOutWithName"),
|
||||
e.Argument.Name),
|
||||
e.Argument.Username),
|
||||
NotificationType.UserLockedOut.ToString(),
|
||||
e.Argument.Id,
|
||||
DateTime.UtcNow,
|
||||
@@ -177,7 +176,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
||||
user.Name,
|
||||
user.Username,
|
||||
GetItemName(item),
|
||||
e.DeviceName),
|
||||
GetPlaybackStoppedNotificationType(item.MediaType),
|
||||
@@ -214,7 +213,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
|
||||
user.Name,
|
||||
user.Username,
|
||||
GetItemName(item),
|
||||
e.DeviceName),
|
||||
GetPlaybackNotificationType(item.MediaType),
|
||||
@@ -338,13 +337,13 @@ namespace Emby.Server.Implementations.Activity
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnUserPolicyUpdated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
private async void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
|
||||
e.Argument.Name),
|
||||
e.Argument.Username),
|
||||
"UserPolicyUpdated",
|
||||
e.Argument.Id,
|
||||
DateTime.UtcNow,
|
||||
@@ -352,13 +351,13 @@ namespace Emby.Server.Implementations.Activity
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnUserDeleted(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserDeletedWithName"),
|
||||
e.Argument.Name),
|
||||
e.Argument.Username),
|
||||
"UserDeleted",
|
||||
Guid.Empty,
|
||||
DateTime.UtcNow,
|
||||
@@ -366,26 +365,26 @@ namespace Emby.Server.Implementations.Activity
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnUserPasswordChanged(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserPasswordChangedWithName"),
|
||||
e.Argument.Name),
|
||||
e.Argument.Username),
|
||||
"UserPasswordChanged",
|
||||
e.Argument.Id,
|
||||
DateTime.UtcNow,
|
||||
LogLevel.Trace)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnUserCreated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||
private async void OnUserCreated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
await CreateLogEntry(new ActivityLog(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("UserCreatedWithName"),
|
||||
e.Argument.Name),
|
||||
e.Argument.Username),
|
||||
"UserCreated",
|
||||
e.Argument.Id,
|
||||
DateTime.UtcNow,
|
||||
@@ -562,11 +561,10 @@ namespace Emby.Server.Implementations.Activity
|
||||
|
||||
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
|
||||
|
||||
_userManager.UserCreated -= OnUserCreated;
|
||||
_userManager.UserPasswordChanged -= OnUserPasswordChanged;
|
||||
_userManager.UserDeleted -= OnUserDeleted;
|
||||
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
||||
_userManager.UserLockedOut -= OnUserLockedOut;
|
||||
_userManager.OnUserCreated -= OnUserCreated;
|
||||
_userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
|
||||
_userManager.OnUserDeleted -= OnUserDeleted;
|
||||
_userManager.OnUserLockedOut -= OnUserLockedOut;
|
||||
|
||||
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ using Emby.Server.Implementations.TV;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Implementations.Activity;
|
||||
using Jellyfin.Server.Implementations.User;
|
||||
using MediaBrowser.Api;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
@@ -595,17 +596,12 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
|
||||
|
||||
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
||||
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
||||
|
||||
serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>();
|
||||
|
||||
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
||||
|
||||
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
|
||||
|
||||
serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>();
|
||||
|
||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
|
||||
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
||||
@@ -700,17 +696,11 @@ namespace Emby.Server.Implementations
|
||||
_httpServer = Resolve<IHttpServer>();
|
||||
_httpClient = Resolve<IHttpClient>();
|
||||
|
||||
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
|
||||
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
||||
((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
|
||||
|
||||
SetStaticProperties();
|
||||
|
||||
var userManager = (UserManager)Resolve<IUserManager>();
|
||||
userManager.Initialize();
|
||||
|
||||
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
|
||||
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager);
|
||||
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize();
|
||||
|
||||
FindParts();
|
||||
}
|
||||
@@ -793,7 +783,6 @@ namespace Emby.Server.Implementations
|
||||
BaseItem.ProviderManager = Resolve<IProviderManager>();
|
||||
BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
|
||||
BaseItem.ItemRepository = Resolve<IItemRepository>();
|
||||
User.UserManager = Resolve<IUserManager>();
|
||||
BaseItem.FileSystem = _fileSystemManager;
|
||||
BaseItem.UserDataManager = Resolve<IUserDataManager>();
|
||||
BaseItem.ChannelManager = Resolve<IChannelManager>();
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
||||
|
||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
|
||||
/// </summary>
|
||||
@@ -791,8 +791,9 @@ namespace Emby.Server.Implementations.Channels
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
|
||||
User user,
|
||||
private async Task<ChannelItemResult> GetChannelItems(
|
||||
IChannel channel,
|
||||
Jellyfin.Data.Entities.User user,
|
||||
string externalFolderId,
|
||||
ChannelItemSortField? sortField,
|
||||
bool sortDescending,
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
|
||||
}
|
||||
|
||||
private IEnumerable<BoxSet> GetCollections(User user)
|
||||
private IEnumerable<BoxSet> GetCollections(Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var folder = GetCollectionsFolder(false).Result;
|
||||
|
||||
@@ -325,7 +325,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
|
||||
public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var results = new Dictionary<Guid, BaseItem>();
|
||||
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteDisplayPreferencesRepository.
|
||||
/// </summary>
|
||||
public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public SqliteDisplayPreferencesRepository(ILogger<SqliteDisplayPreferencesRepository> logger, IApplicationPaths appPaths, IFileSystem fileSystem)
|
||||
: base(logger)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
_jsonOptions = JsonDefaults.GetOptions();
|
||||
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name => "SQLite";
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeInternal();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
|
||||
|
||||
_fileSystem.DeleteFile(DbFilePath);
|
||||
|
||||
InitializeInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
private void InitializeInternal()
|
||||
{
|
||||
string[] queries =
|
||||
{
|
||||
"create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)",
|
||||
"create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)"
|
||||
};
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunQueries(queries);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the display preferences associated with an item in the repo
|
||||
/// </summary>
|
||||
/// <param name="displayPreferences">The display preferences.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="client">The client.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken)
|
||||
{
|
||||
if (displayPreferences == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(displayPreferences.Id))
|
||||
{
|
||||
throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db => SaveDisplayPreferences(displayPreferences, userId, client, db),
|
||||
TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection)
|
||||
{
|
||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(displayPreferences, _jsonOptions);
|
||||
|
||||
using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)"))
|
||||
{
|
||||
statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray());
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
statement.TryBind("@client", client);
|
||||
statement.TryBind("@data", serialized);
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save all display preferences associated with a user in the repo
|
||||
/// </summary>
|
||||
/// <param name="displayPreferences">The display preferences.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (displayPreferences == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
foreach (var displayPreference in displayPreferences)
|
||||
{
|
||||
SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db);
|
||||
}
|
||||
},
|
||||
TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display preferences.
|
||||
/// </summary>
|
||||
/// <param name="displayPreferencesId">The display preferences id.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="client">The client.</param>
|
||||
/// <returns>Task{DisplayPreferences}.</returns>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client)
|
||||
{
|
||||
if (string.IsNullOrEmpty(displayPreferencesId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferencesId));
|
||||
}
|
||||
|
||||
var guidId = displayPreferencesId.GetMD5();
|
||||
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
|
||||
{
|
||||
statement.TryBind("@id", guidId.ToByteArray());
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
statement.TryBind("@client", client);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
return Get(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new DisplayPreferences
|
||||
{
|
||||
Id = guidId.ToString("N", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all display preferences for the given user.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <returns>Task{DisplayPreferences}.</returns>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public IEnumerable<DisplayPreferences> GetAllDisplayPreferences(Guid userId)
|
||||
{
|
||||
var list = new List<DisplayPreferences>();
|
||||
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
|
||||
{
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
list.Add(Get(row));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private DisplayPreferences Get(IReadOnlyList<IResultSetValue> row)
|
||||
=> JsonSerializer.Deserialize<DisplayPreferences>(row[0].ToBlob(), _jsonOptions);
|
||||
|
||||
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken)
|
||||
=> SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken);
|
||||
|
||||
public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client)
|
||||
=> GetDisplayPreferences(displayPreferencesId, new Guid(userId), client);
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Data
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
|
||||
public void Initialize()
|
||||
{
|
||||
const string CreateMediaStreamsTableCommand
|
||||
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
||||
@@ -324,8 +324,6 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
connection.RunQueries(postQueries);
|
||||
}
|
||||
|
||||
userDataRepo.Initialize(userManager, WriteLock, WriteConnection);
|
||||
}
|
||||
|
||||
private static readonly string[] _retriveItemColumns =
|
||||
|
||||
@@ -1,379 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository
|
||||
{
|
||||
public SqliteUserDataRepository(
|
||||
ILogger<SqliteUserDataRepository> logger,
|
||||
IApplicationPaths appPaths)
|
||||
: base(logger)
|
||||
{
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "SQLite";
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database.
|
||||
/// </summary>
|
||||
public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection)
|
||||
{
|
||||
WriteLock.Dispose();
|
||||
WriteLock = dbLock;
|
||||
WriteConnection?.Dispose();
|
||||
WriteConnection = dbConnection;
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
var userDatasTableExists = TableExists(connection, "UserDatas");
|
||||
var userDataTableExists = TableExists(connection, "userdata");
|
||||
|
||||
var users = userDatasTableExists ? null : userManager.Users;
|
||||
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
db.ExecuteAll(string.Join(";", new[] {
|
||||
|
||||
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
|
||||
|
||||
"drop index if exists idx_userdata",
|
||||
"drop index if exists idx_userdata1",
|
||||
"drop index if exists idx_userdata2",
|
||||
"drop index if exists userdataindex1",
|
||||
"drop index if exists userdataindex",
|
||||
"drop index if exists userdataindex3",
|
||||
"drop index if exists userdataindex4",
|
||||
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
|
||||
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
|
||||
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
|
||||
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
|
||||
}));
|
||||
|
||||
if (userDataTableExists)
|
||||
{
|
||||
var existingColumnNames = GetColumnNames(db, "userdata");
|
||||
|
||||
AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
|
||||
AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
|
||||
AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
|
||||
|
||||
if (!userDatasTableExists)
|
||||
{
|
||||
ImportUserIds(db, users);
|
||||
|
||||
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
|
||||
}
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportUserIds(IDatabaseConnection db, IEnumerable<User> users)
|
||||
{
|
||||
var userIdsWithUserData = GetAllUserIdsWithUserData(db);
|
||||
|
||||
using (var statement = db.PrepareStatement("update userdata set InternalUserId=@InternalUserId where UserId=@UserId"))
|
||||
{
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (!userIdsWithUserData.Contains(user.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
statement.TryBind("@UserId", user.Id.ToByteArray());
|
||||
statement.TryBind("@InternalUserId", user.InternalId);
|
||||
|
||||
statement.MoveNext();
|
||||
statement.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Guid> GetAllUserIdsWithUserData(IDatabaseConnection db)
|
||||
{
|
||||
var list = new List<Guid>();
|
||||
|
||||
using (var statement = PrepareStatement(db, "select DISTINCT UserId from UserData where UserId not null"))
|
||||
{
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
try
|
||||
{
|
||||
list.Add(row[0].ReadGuidFromBlob());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error while getting user");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the user data.
|
||||
/// </summary>
|
||||
public void SaveUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken)
|
||||
{
|
||||
if (userData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(userData));
|
||||
}
|
||||
if (internalUserId <= 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(internalUserId));
|
||||
}
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
PersistUserData(internalUserId, key, userData, cancellationToken);
|
||||
}
|
||||
|
||||
public void SaveAllUserData(long internalUserId, UserItemData[] userData, CancellationToken cancellationToken)
|
||||
{
|
||||
if (userData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(userData));
|
||||
}
|
||||
if (internalUserId <= 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(internalUserId));
|
||||
}
|
||||
|
||||
PersistAllUserData(internalUserId, userData, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists the user data.
|
||||
/// </summary>
|
||||
/// <param name="internalUserId">The user id.</param>
|
||||
/// <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();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
SaveUserData(db, internalUserId, key, userData);
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveUserData(IDatabaseConnection db, long internalUserId, string key, UserItemData userData)
|
||||
{
|
||||
using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)"))
|
||||
{
|
||||
statement.TryBind("@userId", internalUserId);
|
||||
statement.TryBind("@key", key);
|
||||
|
||||
if (userData.Rating.HasValue)
|
||||
{
|
||||
statement.TryBind("@rating", userData.Rating.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBindNull("@rating");
|
||||
}
|
||||
|
||||
statement.TryBind("@played", userData.Played);
|
||||
statement.TryBind("@playCount", userData.PlayCount);
|
||||
statement.TryBind("@isFavorite", userData.IsFavorite);
|
||||
statement.TryBind("@playbackPositionTicks", userData.PlaybackPositionTicks);
|
||||
|
||||
if (userData.LastPlayedDate.HasValue)
|
||||
{
|
||||
statement.TryBind("@lastPlayedDate", userData.LastPlayedDate.Value.ToDateTimeParamValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBindNull("@lastPlayedDate");
|
||||
}
|
||||
|
||||
if (userData.AudioStreamIndex.HasValue)
|
||||
{
|
||||
statement.TryBind("@AudioStreamIndex", userData.AudioStreamIndex.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBindNull("@AudioStreamIndex");
|
||||
}
|
||||
|
||||
if (userData.SubtitleStreamIndex.HasValue)
|
||||
{
|
||||
statement.TryBind("@SubtitleStreamIndex", userData.SubtitleStreamIndex.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBindNull("@SubtitleStreamIndex");
|
||||
}
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persist all user data for the specified user
|
||||
/// </summary>
|
||||
private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
foreach (var userItemData in userDataList)
|
||||
{
|
||||
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user data.
|
||||
/// </summary>
|
||||
/// <param name="internalUserId">The user id.</param>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns>Task{UserItemData}.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// userId
|
||||
/// or
|
||||
/// key
|
||||
/// </exception>
|
||||
public UserItemData GetUserData(long internalUserId, string key)
|
||||
{
|
||||
if (internalUserId <= 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(internalUserId));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
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("@Key", key);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
return ReadRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public UserItemData GetUserData(long internalUserId, List<string> keys)
|
||||
{
|
||||
if (keys == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(keys));
|
||||
}
|
||||
|
||||
if (keys.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetUserData(internalUserId, 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)
|
||||
{
|
||||
if (internalUserId <= 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(internalUserId));
|
||||
}
|
||||
|
||||
var list = new List<UserItemData>();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
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);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
list.Add(ReadRow(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a row from the specified reader into the provided userData object
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
|
||||
{
|
||||
var userData = new UserItemData();
|
||||
|
||||
userData.Key = reader[0].ToString();
|
||||
//userData.UserId = reader[1].ReadGuidFromBlob();
|
||||
|
||||
if (reader[2].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
userData.Rating = reader[2].ToDouble();
|
||||
}
|
||||
|
||||
userData.Played = reader[3].ToBool();
|
||||
userData.PlayCount = reader[4].ToInt();
|
||||
userData.IsFavorite = reader[5].ToBool();
|
||||
userData.PlaybackPositionTicks = reader[6].ToInt64();
|
||||
|
||||
if (reader[7].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
userData.LastPlayedDate = reader[7].TryReadDateTime();
|
||||
}
|
||||
|
||||
if (reader[8].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
userData.AudioStreamIndex = reader[8].ToInt();
|
||||
}
|
||||
|
||||
if (reader[9].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
userData.SubtitleStreamIndex = reader[9].ToInt();
|
||||
}
|
||||
|
||||
return userData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteUserRepository
|
||||
/// </summary>
|
||||
public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public SqliteUserRepository(
|
||||
ILogger<SqliteUserRepository> logger,
|
||||
IServerApplicationPaths appPaths)
|
||||
: base(logger)
|
||||
{
|
||||
_jsonOptions = JsonDefaults.GetOptions();
|
||||
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name => "SQLite";
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
var localUsersTableExists = TableExists(connection, "LocalUsersv2");
|
||||
|
||||
connection.RunQueries(new[] {
|
||||
"create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)",
|
||||
"drop index if exists idx_users"
|
||||
});
|
||||
|
||||
if (!localUsersTableExists && TableExists(connection, "Users"))
|
||||
{
|
||||
TryMigrateToLocalUsersTable(connection);
|
||||
}
|
||||
|
||||
RemoveEmptyPasswordHashes(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryMigrateToLocalUsersTable(ManagedConnection connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.RunQueries(new[]
|
||||
{
|
||||
"INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users"
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error migrating users database");
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveEmptyPasswordHashes(ManagedConnection connection)
|
||||
{
|
||||
foreach (var user in RetrieveAllUsers(connection))
|
||||
{
|
||||
// If the user password is the sha1 hash of the empty string, remove it
|
||||
if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
|
||||
&& !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
user.Password = null;
|
||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
|
||||
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
||||
{
|
||||
statement.TryBind("@InternalId", user.InternalId);
|
||||
statement.TryBind("@data", serialized);
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a user in the repo
|
||||
/// </summary>
|
||||
public void CreateUser(User user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
|
||||
{
|
||||
statement.TryBind("@guid", user.Id.ToByteArray());
|
||||
statement.TryBind("@data", serialized);
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
var createdUser = GetUser(user.Id, connection);
|
||||
|
||||
if (createdUser == null)
|
||||
{
|
||||
throw new ApplicationException("created user should never be null");
|
||||
}
|
||||
|
||||
user.InternalId = createdUser.InternalId;
|
||||
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateUser(User user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions);
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
||||
{
|
||||
statement.TryBind("@InternalId", user.InternalId);
|
||||
statement.TryBind("@data", serialized);
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private User GetUser(Guid guid, ManagedConnection connection)
|
||||
{
|
||||
using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
|
||||
{
|
||||
statement.TryBind("@guid", guid);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
return GetUser(row);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private User GetUser(IReadOnlyList<IResultSetValue> row)
|
||||
{
|
||||
var id = row[0].ToInt64();
|
||||
var guid = row[1].ReadGuidFromBlob();
|
||||
|
||||
var user = JsonSerializer.Deserialize<User>(row[2].ToBlob(), _jsonOptions);
|
||||
user.InternalId = id;
|
||||
user.Id = guid;
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all users from the database
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{User}.</returns>
|
||||
public List<User> RetrieveAllUsers()
|
||||
{
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
return new List<User>(RetrieveAllUsers(connection));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all users from the database
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{User}.</returns>
|
||||
private IEnumerable<User> RetrieveAllUsers(ManagedConnection connection)
|
||||
{
|
||||
foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
|
||||
{
|
||||
yield return GetUser(row);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="ArgumentNullException">user</exception>
|
||||
public void DeleteUser(User user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
|
||||
{
|
||||
statement.TryBind("@id", user.InternalId);
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@@ -360,7 +361,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
|
||||
private string DefaultCameraUploadsPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads");
|
||||
|
||||
public bool CanAccessDevice(User user, string deviceId)
|
||||
public bool CanAccessDevice(Jellyfin.Data.Entities.User user, string deviceId)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
@@ -371,7 +372,13 @@ namespace Emby.Server.Implementations.Devices
|
||||
throw new ArgumentNullException(nameof(deviceId));
|
||||
}
|
||||
|
||||
if (!CanAccessDevice(user.Policy, 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);
|
||||
|
||||
@@ -383,21 +390,6 @@ namespace Emby.Server.Implementations.Devices
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CanAccessDevice(UserPolicy policy, string id)
|
||||
{
|
||||
if (policy.EnableAllDevices)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (policy.IsAdministrator)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceManagerEntryPoint : IServerEntryPoint
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
@@ -74,7 +75,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
/// <param name="owner">The owner.</param>
|
||||
/// <returns>Task{DtoBaseItem}.</returns>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null)
|
||||
public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, Jellyfin.Data.Entities.User user = null, BaseItem owner = null)
|
||||
{
|
||||
var options = new DtoOptions
|
||||
{
|
||||
@@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
|
||||
public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null)
|
||||
{
|
||||
var returnItems = new BaseItemDto[items.Count];
|
||||
var programTuples = new List<(BaseItem, BaseItemDto)>();
|
||||
@@ -138,7 +139,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
return returnItems;
|
||||
}
|
||||
|
||||
public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null)
|
||||
public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null)
|
||||
{
|
||||
var dto = GetBaseItemDtoInternal(item, options, user, owner);
|
||||
if (item is LiveTvChannel tvChannel)
|
||||
@@ -172,7 +173,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
return dto;
|
||||
}
|
||||
|
||||
private static IList<BaseItem> GetTaggedItems(IItemByName byName, User user, DtoOptions options)
|
||||
private static IList<BaseItem> GetTaggedItems(IItemByName byName, Jellyfin.Data.Entities.User user, DtoOptions options)
|
||||
{
|
||||
return byName.GetTaggedItems(
|
||||
new InternalItemsQuery(user)
|
||||
@@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
});
|
||||
}
|
||||
|
||||
private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null)
|
||||
private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null)
|
||||
{
|
||||
var dto = new BaseItemDto
|
||||
{
|
||||
@@ -315,7 +316,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
}
|
||||
|
||||
public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null)
|
||||
public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, Jellyfin.Data.Entities.User user = null)
|
||||
{
|
||||
var dto = GetBaseItemDtoInternal(item, options, user);
|
||||
|
||||
@@ -327,7 +328,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
return dto;
|
||||
}
|
||||
|
||||
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems, User user = null)
|
||||
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems, Jellyfin.Data.Entities.User user = null)
|
||||
{
|
||||
if (item is MusicArtist)
|
||||
{
|
||||
@@ -363,7 +364,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
/// <summary>
|
||||
/// Attaches the user specific info.
|
||||
/// </summary>
|
||||
private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions options)
|
||||
private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, Jellyfin.Data.Entities.User user, DtoOptions options)
|
||||
{
|
||||
if (item.IsFolder)
|
||||
{
|
||||
@@ -384,7 +385,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (options.ContainsField(ItemFields.ChildCount))
|
||||
{
|
||||
dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user);
|
||||
dto.ChildCount ??= GetChildCount(folder, user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,7 +415,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (options.ContainsField(ItemFields.BasicSyncInfo))
|
||||
{
|
||||
var userCanSync = user != null && user.Policy.EnableContentDownloading;
|
||||
var userCanSync = user != null && user.HasPermission(PermissionKind.EnableContentDownloading);
|
||||
if (userCanSync && item.SupportsExternalTransfer)
|
||||
{
|
||||
dto.SupportsSync = true;
|
||||
@@ -422,7 +423,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetChildCount(Folder folder, User user)
|
||||
private static int GetChildCount(Folder folder, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
// Right now this is too slow to calculate for top level folders on a per-user basis
|
||||
// Just return something so that apps that are expecting a value won't think the folders are empty
|
||||
|
||||
@@ -446,7 +446,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="includeIfNotFound">if set to <c>true</c> [include if not found].</param>
|
||||
/// <returns>IEnumerable{``0}.</returns>
|
||||
private IEnumerable<T> TranslatePhysicalItemToUserLibrary<T>(T item, User user, bool includeIfNotFound = false)
|
||||
private IEnumerable<T> TranslatePhysicalItemToUserLibrary<T>(T item, Jellyfin.Data.Entities.User user, bool includeIfNotFound = false)
|
||||
where T : BaseItem
|
||||
{
|
||||
// If the physical root changed, return the user root
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
@@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
private async void SendMessage(string name, TimerEventInfo info)
|
||||
{
|
||||
var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList();
|
||||
var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Class RefreshUsersMetadata.
|
||||
/// </summary>
|
||||
public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The user manager.
|
||||
/// </summary>
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
|
||||
/// </summary>
|
||||
public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Refresh Users";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => "RefreshUsers";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "Refresh user infos";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Category => "Library";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsHidden => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLogged => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
foreach (var user in _userManager.Users)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
IntervalTicks = TimeSpan.FromDays(1).Ticks,
|
||||
Type = TaskTriggerInfo.TriggerInterval
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
@@ -67,10 +67,8 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
_userManager.UserDeleted += OnUserDeleted;
|
||||
_userManager.UserUpdated += OnUserUpdated;
|
||||
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
||||
_userManager.UserConfigurationUpdated += OnUserConfigurationUpdated;
|
||||
_userManager.OnUserDeleted += OnUserDeleted;
|
||||
_userManager.OnUserUpdated += OnUserUpdated;
|
||||
|
||||
_appHost.HasPendingRestartChanged += OnHasPendingRestartChanged;
|
||||
|
||||
@@ -152,20 +150,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
var dto = _userManager.GetUserDto(e.Argument);
|
||||
|
||||
SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto);
|
||||
}
|
||||
|
||||
private void OnUserConfigurationUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
var dto = _userManager.GetUserDto(e.Argument);
|
||||
|
||||
SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto);
|
||||
}
|
||||
|
||||
private async void SendMessageToAdminSessions<T>(string name, T data)
|
||||
{
|
||||
try
|
||||
@@ -209,10 +193,9 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
if (dispose)
|
||||
{
|
||||
_userManager.UserDeleted -= OnUserDeleted;
|
||||
_userManager.UserUpdated -= OnUserUpdated;
|
||||
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
||||
_userManager.UserConfigurationUpdated -= OnUserConfigurationUpdated;
|
||||
_userManager.OnUserDeleted -= OnUserDeleted;
|
||||
_userManager.OnUserUpdated -= OnUserUpdated;
|
||||
|
||||
|
||||
_installationManager.PluginUninstalled -= OnPluginUninstalled;
|
||||
_installationManager.PackageInstalling -= OnPackageInstalling;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Security.Authentication;
|
||||
using Emby.Server.Implementations.SocketSharp;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -43,14 +44,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
ValidateUser(request, authAttribtues);
|
||||
}
|
||||
|
||||
public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
|
||||
public Jellyfin.Data.Entities.User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
|
||||
{
|
||||
var req = new WebSocketSharpRequest(request, null, request.Path, _logger);
|
||||
var user = ValidateUser(req, authAttributes);
|
||||
return user;
|
||||
}
|
||||
|
||||
private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||
private Jellyfin.Data.Entities.User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||
{
|
||||
// This code is executed before the service
|
||||
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||
@@ -90,7 +91,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
!string.IsNullOrEmpty(auth.Client) &&
|
||||
!string.IsNullOrEmpty(auth.Device))
|
||||
{
|
||||
_sessionManager.LogSessionActivity(auth.Client,
|
||||
_sessionManager.LogSessionActivity(
|
||||
auth.Client,
|
||||
auth.Version,
|
||||
auth.DeviceId,
|
||||
auth.Device,
|
||||
@@ -102,22 +104,22 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
}
|
||||
|
||||
private void ValidateUserAccess(
|
||||
User user,
|
||||
Jellyfin.Data.Entities.User user,
|
||||
IRequest request,
|
||||
IAuthenticationAttributes authAttribtues,
|
||||
AuthorizationInfo auth)
|
||||
{
|
||||
if (user.Policy.IsDisabled)
|
||||
if (user.HasPermission(PermissionKind.IsDisabled))
|
||||
{
|
||||
throw new SecurityException("User account has been disabled.");
|
||||
}
|
||||
|
||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
||||
if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
||||
{
|
||||
throw new SecurityException("User account has been disabled.");
|
||||
}
|
||||
|
||||
if (!user.Policy.IsAdministrator
|
||||
if (!user.HasPermission(PermissionKind.IsAdministrator)
|
||||
&& !authAttribtues.EscapeParentalControl
|
||||
&& !user.IsParentalScheduleAllowed())
|
||||
{
|
||||
@@ -176,11 +178,11 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ValidateRoles(string[] roles, User user)
|
||||
private static void ValidateRoles(string[] roles, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user == null || !user.Policy.IsAdministrator)
|
||||
if (user == null || !user.HasPermission(PermissionKind.IsAdministrator))
|
||||
{
|
||||
throw new SecurityException("User does not have admin access.");
|
||||
}
|
||||
@@ -188,7 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
|
||||
if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user == null || !user.Policy.EnableContentDeletion)
|
||||
if (user == null || !user.HasPermission(PermissionKind.EnableContentDeletion))
|
||||
{
|
||||
throw new SecurityException("User does not have delete access.");
|
||||
}
|
||||
@@ -196,7 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
|
||||
if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user == null || !user.Policy.EnableContentDownloading)
|
||||
if (user == null || !user.HasPermission(PermissionKind.EnableContentDownloading))
|
||||
{
|
||||
throw new SecurityException("User does not have download access.");
|
||||
}
|
||||
|
||||
@@ -149,9 +149,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
{
|
||||
info.User = _userManager.GetUserById(tokenInfo.UserId);
|
||||
|
||||
if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase))
|
||||
if (info.User != null && !string.Equals(info.User.Username, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
tokenInfo.UserName = info.User.Name;
|
||||
tokenInfo.UserName = info.User.Username;
|
||||
updateToken = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
return GetSession((IRequest)requestContext);
|
||||
}
|
||||
|
||||
public User GetUser(IRequest requestContext)
|
||||
public Jellyfin.Data.Entities.User GetUser(IRequest requestContext)
|
||||
{
|
||||
var session = GetSession(requestContext);
|
||||
|
||||
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
|
||||
}
|
||||
|
||||
public User GetUser(object requestContext)
|
||||
public Jellyfin.Data.Entities.User GetUser(object requestContext)
|
||||
{
|
||||
return GetUser((IRequest)requestContext);
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Cryptography;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// The default authentication provider.
|
||||
/// </summary>
|
||||
public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
|
||||
{
|
||||
private readonly ICryptoProvider _cryptographyProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultAuthenticationProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="cryptographyProvider">The cryptography provider.</param>
|
||||
public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider)
|
||||
{
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Default";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
// This is dumb and an artifact of the backwards way auth providers were designed.
|
||||
// This version of authenticate was never meant to be called, but needs to be here for interface compat
|
||||
// Only the providers that don't provide local user support use this
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
// This is the version that we need to use for local users. Because reasons.
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
|
||||
{
|
||||
if (resolvedUser == null)
|
||||
{
|
||||
throw new AuthenticationException($"Specified user does not exist.");
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
// As long as jellyfin supports passwordless users, we need this little block here to accommodate
|
||||
if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password))
|
||||
{
|
||||
return Task.FromResult(new ProviderAuthenticationResult
|
||||
{
|
||||
Username = username
|
||||
});
|
||||
}
|
||||
|
||||
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
|
||||
|
||||
PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password);
|
||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
|
||||
|| _cryptographyProvider.DefaultHashMethod == readyHash.Id)
|
||||
{
|
||||
byte[] calculatedHash = _cryptographyProvider.ComputeHash(
|
||||
readyHash.Id,
|
||||
passwordbytes,
|
||||
readyHash.Salt.ToArray());
|
||||
|
||||
if (readyHash.Hash.SequenceEqual(calculatedHash))
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}");
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new AuthenticationException("Invalid username or password");
|
||||
}
|
||||
|
||||
return Task.FromResult(new ProviderAuthenticationResult
|
||||
{
|
||||
Username = username
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPassword(User user)
|
||||
=> !string.IsNullOrEmpty(user.Password);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ChangePassword(User user, string newPassword)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newPassword))
|
||||
{
|
||||
user.Password = null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword);
|
||||
user.Password = newPasswordHash.ToString();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||
{
|
||||
if (newPassword != null)
|
||||
{
|
||||
newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newPasswordHash))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newPasswordHash));
|
||||
}
|
||||
|
||||
user.EasyPassword = newPasswordHash;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetEasyPasswordHash(User user)
|
||||
{
|
||||
return string.IsNullOrEmpty(user.EasyPassword)
|
||||
? null
|
||||
: Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hashed string.
|
||||
/// </summary>
|
||||
public string GetHashedString(User user, string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
return _cryptographyProvider.CreatePasswordHash(str).ToString();
|
||||
}
|
||||
|
||||
// TODO: make use of iterations parameter?
|
||||
PasswordHash passwordHash = PasswordHash.Parse(user.Password);
|
||||
var salt = passwordHash.Salt.ToArray();
|
||||
return new PasswordHash(
|
||||
passwordHash.Id,
|
||||
_cryptographyProvider.ComputeHash(
|
||||
passwordHash.Id,
|
||||
Encoding.UTF8.GetBytes(str),
|
||||
salt),
|
||||
salt,
|
||||
passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString();
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetHashed(User user, string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
return _cryptographyProvider.CreatePasswordHash(str).Hash;
|
||||
}
|
||||
|
||||
// TODO: make use of iterations parameter?
|
||||
PasswordHash passwordHash = PasswordHash.Parse(user.Password);
|
||||
return _cryptographyProvider.ComputeHash(
|
||||
passwordHash.Id,
|
||||
Encoding.UTF8.GetBytes(str),
|
||||
passwordHash.Salt.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// The default password reset provider.
|
||||
/// </summary>
|
||||
public class DefaultPasswordResetProvider : IPasswordResetProvider
|
||||
{
|
||||
private const string BaseResetFileName = "passwordreset";
|
||||
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
private readonly string _passwordResetFileBase;
|
||||
private readonly string _passwordResetFileBaseDir;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultPasswordResetProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
public DefaultPasswordResetProvider(
|
||||
IServerConfigurationManager configurationManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IUserManager userManager)
|
||||
{
|
||||
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
|
||||
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Default Password Reset Provider";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
|
||||
{
|
||||
SerializablePasswordReset spr;
|
||||
List<string> usersreset = new List<string>();
|
||||
foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
|
||||
{
|
||||
using (var str = File.OpenRead(resetfile))
|
||||
{
|
||||
spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (spr.ExpirationDate < DateTime.Now)
|
||||
{
|
||||
File.Delete(resetfile);
|
||||
}
|
||||
else if (string.Equals(
|
||||
spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal),
|
||||
pin.Replace("-", string.Empty, StringComparison.Ordinal),
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var resetUser = _userManager.GetUserByName(spr.UserName);
|
||||
if (resetUser == null)
|
||||
{
|
||||
throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
|
||||
}
|
||||
|
||||
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
|
||||
usersreset.Add(resetUser.Name);
|
||||
File.Delete(resetfile);
|
||||
}
|
||||
}
|
||||
|
||||
if (usersreset.Count < 1)
|
||||
{
|
||||
throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return new PinRedeemResult
|
||||
{
|
||||
Success = true,
|
||||
UsersReset = usersreset.ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
|
||||
{
|
||||
string pin = string.Empty;
|
||||
using (var cryptoRandom = RandomNumberGenerator.Create())
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
cryptoRandom.GetBytes(bytes);
|
||||
pin = BitConverter.ToString(bytes);
|
||||
}
|
||||
|
||||
DateTime expireTime = DateTime.Now.AddMinutes(30);
|
||||
string filePath = _passwordResetFileBase + user.InternalId + ".json";
|
||||
SerializablePasswordReset spr = new SerializablePasswordReset
|
||||
{
|
||||
ExpirationDate = expireTime,
|
||||
Pin = pin,
|
||||
PinFile = filePath,
|
||||
UserName = user.Name
|
||||
};
|
||||
|
||||
using (FileStream fileStream = File.OpenWrite(filePath))
|
||||
{
|
||||
_jsonSerializer.SerializeToStream(spr, fileStream);
|
||||
await fileStream.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new ForgotPasswordResult
|
||||
{
|
||||
Action = ForgotPasswordAction.PinCode,
|
||||
PinExpirationDate = expireTime,
|
||||
PinFile = filePath
|
||||
};
|
||||
}
|
||||
|
||||
private class SerializablePasswordReset : PasswordPinCreationResult
|
||||
{
|
||||
public string Pin { get; set; }
|
||||
|
||||
public string UserName { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// An invalid authentication provider.
|
||||
/// </summary>
|
||||
public class InvalidAuthProvider : IAuthenticationProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "InvalidOrMissingAuthenticationProvider";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
||||
{
|
||||
throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPassword(User user)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ChangePassword(User user, string newPassword)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||
{
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetPasswordHash(User user)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetEasyPasswordHash(User user)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ using Emby.Server.Implementations.Library.Resolvers;
|
||||
using Emby.Server.Implementations.Library.Validators;
|
||||
using Emby.Server.Implementations.Playlists;
|
||||
using Emby.Server.Implementations.ScheduledTasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -1470,7 +1471,7 @@ namespace Emby.Server.Implementations.Library
|
||||
query.Parent = null;
|
||||
}
|
||||
|
||||
private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true)
|
||||
private void AddUserToQuery(InternalItemsQuery query, Jellyfin.Data.Entities.User user, bool allowExternalContent = true)
|
||||
{
|
||||
if (query.AncestorIds.Length == 0 &&
|
||||
query.ParentId.Equals(Guid.Empty) &&
|
||||
@@ -1491,7 +1492,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Guid> GetTopParentIdsForQuery(BaseItem item, User user)
|
||||
private IEnumerable<Guid> GetTopParentIdsForQuery(BaseItem item, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
if (item is UserView view)
|
||||
{
|
||||
@@ -1524,7 +1525,8 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
// Handle grouping
|
||||
if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0)
|
||||
if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType)
|
||||
&& user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
|
||||
{
|
||||
return GetUserRootFolder()
|
||||
.GetChildren(user, true)
|
||||
@@ -1557,7 +1559,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>IEnumerable{System.String}.</returns>
|
||||
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
|
||||
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var tasks = IntroProviders
|
||||
.OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
|
||||
@@ -1579,7 +1581,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task<IEnumerable<IntroInfo>>.</returns>
|
||||
private async Task<IEnumerable<IntroInfo>> GetIntros(IIntroProvider provider, BaseItem item, User user)
|
||||
private async Task<IEnumerable<IntroInfo>> GetIntros(IIntroProvider provider, BaseItem item, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1680,7 +1682,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="sortBy">The sort by.</param>
|
||||
/// <param name="sortOrder">The sort order.</param>
|
||||
/// <returns>IEnumerable{BaseItem}.</returns>
|
||||
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, SortOrder sortOrder)
|
||||
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, Jellyfin.Data.Entities.User user, IEnumerable<string> sortBy, SortOrder sortOrder)
|
||||
{
|
||||
var isFirst = true;
|
||||
|
||||
@@ -1703,7 +1705,7 @@ 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, Jellyfin.Data.Entities.User user, IEnumerable<ValueTuple<string, SortOrder>> orderByList)
|
||||
{
|
||||
var isFirst = true;
|
||||
|
||||
@@ -1740,7 +1742,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>IBaseItemComparer.</returns>
|
||||
private IBaseItemComparer GetComparer(string name, User user)
|
||||
private IBaseItemComparer GetComparer(string name, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -2072,7 +2074,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
|
||||
|
||||
public UserView GetNamedView(
|
||||
User user,
|
||||
Jellyfin.Data.Entities.User user,
|
||||
string name,
|
||||
string viewType,
|
||||
string sortName)
|
||||
@@ -2125,7 +2127,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
public UserView GetNamedView(
|
||||
User user,
|
||||
Jellyfin.Data.Entities.User user,
|
||||
string name,
|
||||
Guid parentId,
|
||||
string viewType,
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -14,7 +15,6 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.Library
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
|
||||
public async Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, Jellyfin.Data.Entities.User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
|
||||
{
|
||||
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
|
||||
|
||||
@@ -190,10 +190,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!user.Policy.EnableAudioPlaybackTranscoding)
|
||||
{
|
||||
source.SupportsTranscoding = false;
|
||||
}
|
||||
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,7 +309,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public List<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null)
|
||||
public List<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, Jellyfin.Data.Entities.User user = null)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
@@ -350,9 +347,11 @@ namespace Emby.Server.Implementations.Library
|
||||
return new string[] { language };
|
||||
}
|
||||
|
||||
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
|
||||
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, Jellyfin.Data.Entities.User user, bool allowRememberingSelection)
|
||||
{
|
||||
if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
|
||||
if (userData.SubtitleStreamIndex.HasValue
|
||||
&& user.RememberSubtitleSelections
|
||||
&& user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
|
||||
{
|
||||
var index = userData.SubtitleStreamIndex.Value;
|
||||
// Make sure the saved index is still valid
|
||||
@@ -363,26 +362,27 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
|
||||
? Array.Empty<string>() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference);
|
||||
|
||||
var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
|
||||
? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
|
||||
|
||||
var defaultAudioIndex = source.DefaultAudioStreamIndex;
|
||||
var audioLangage = defaultAudioIndex == null
|
||||
? null
|
||||
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
|
||||
|
||||
source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams,
|
||||
source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(
|
||||
source.MediaStreams,
|
||||
preferredSubs,
|
||||
user.Configuration.SubtitleMode,
|
||||
user.SubtitleMode,
|
||||
audioLangage);
|
||||
|
||||
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs,
|
||||
user.Configuration.SubtitleMode, audioLangage);
|
||||
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage);
|
||||
}
|
||||
|
||||
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
|
||||
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, Jellyfin.Data.Entities.User user, bool allowRememberingSelection)
|
||||
{
|
||||
if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection)
|
||||
if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection)
|
||||
{
|
||||
var index = userData.AudioStreamIndex.Value;
|
||||
// Make sure the saved index is still valid
|
||||
@@ -393,14 +393,14 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
|
||||
var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference)
|
||||
? Array.Empty<string>()
|
||||
: NormalizeLanguage(user.Configuration.AudioLanguagePreference);
|
||||
: NormalizeLanguage(user.AudioLanguagePreference);
|
||||
|
||||
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
|
||||
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
|
||||
}
|
||||
|
||||
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user)
|
||||
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
// Item would only be null if the app didn't supply ItemId as part of the live stream open request
|
||||
var mediaType = item == null ? MediaType.Video : item.MediaType;
|
||||
@@ -560,17 +560,14 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
videoStream.BitRate = 30000000;
|
||||
}
|
||||
|
||||
else if (width >= 1900)
|
||||
{
|
||||
videoStream.BitRate = 20000000;
|
||||
}
|
||||
|
||||
else if (width >= 1200)
|
||||
{
|
||||
videoStream.BitRate = 8000000;
|
||||
}
|
||||
|
||||
else if (width >= 700)
|
||||
{
|
||||
videoStream.BitRate = 2000000;
|
||||
@@ -674,13 +671,14 @@ namespace Emby.Server.Implementations.Library
|
||||
mediaSource.AnalyzeDurationMs = 3000;
|
||||
}
|
||||
|
||||
mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
||||
mediaInfo = await _mediaEncoder.GetMediaInfo(
|
||||
new MediaInfoRequest
|
||||
{
|
||||
MediaSource = mediaSource,
|
||||
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
||||
ExtractChapters = false
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (cacheFilePath != null)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public List<BaseItem> GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions)
|
||||
public List<BaseItem> GetInstantMixFromSong(Audio item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var list = new List<Audio>
|
||||
{
|
||||
@@ -32,17 +32,17 @@ namespace Emby.Server.Implementations.Library
|
||||
return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList();
|
||||
}
|
||||
|
||||
public List<BaseItem> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions)
|
||||
public List<BaseItem> GetInstantMixFromArtist(MusicArtist item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions)
|
||||
{
|
||||
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
|
||||
}
|
||||
|
||||
public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions)
|
||||
public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions)
|
||||
{
|
||||
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
|
||||
}
|
||||
|
||||
public List<BaseItem> GetInstantMixFromFolder(Folder item, User user, DtoOptions dtoOptions)
|
||||
public List<BaseItem> GetInstantMixFromFolder(Folder item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var genres = item
|
||||
.GetRecursiveChildren(user, new InternalItemsQuery(user)
|
||||
@@ -58,12 +58,12 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetInstantMixFromGenres(genres, user, dtoOptions);
|
||||
}
|
||||
|
||||
public List<BaseItem> GetInstantMixFromPlaylist(Playlist item, User user, DtoOptions dtoOptions)
|
||||
public List<BaseItem> GetInstantMixFromPlaylist(Playlist item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions)
|
||||
{
|
||||
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
|
||||
}
|
||||
|
||||
public List<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions)
|
||||
public List<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var genreIds = genres.DistinctNames().Select(i =>
|
||||
{
|
||||
@@ -75,13 +75,12 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
}).Where(i => !i.Equals(Guid.Empty)).ToArray();
|
||||
|
||||
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
|
||||
}
|
||||
|
||||
public List<BaseItem> GetInstantMixFromGenreIds(Guid[] genreIds, User user, DtoOptions dtoOptions)
|
||||
public List<BaseItem> GetInstantMixFromGenreIds(Guid[] genreIds, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions)
|
||||
{
|
||||
return _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
@@ -97,7 +96,7 @@ namespace Emby.Server.Implementations.Library
|
||||
});
|
||||
}
|
||||
|
||||
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
|
||||
public List<BaseItem> GetInstantMixFromItem(BaseItem item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var genre = item as MusicGenre;
|
||||
if (genre != null)
|
||||
@@ -105,32 +104,27 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
|
||||
}
|
||||
|
||||
var playlist = item as Playlist;
|
||||
if (playlist != null)
|
||||
if (item is Playlist playlist)
|
||||
{
|
||||
return GetInstantMixFromPlaylist(playlist, user, dtoOptions);
|
||||
}
|
||||
|
||||
var album = item as MusicAlbum;
|
||||
if (album != null)
|
||||
if (item is MusicAlbum album)
|
||||
{
|
||||
return GetInstantMixFromAlbum(album, user, dtoOptions);
|
||||
}
|
||||
|
||||
var artist = item as MusicArtist;
|
||||
if (artist != null)
|
||||
if (item is MusicArtist artist)
|
||||
{
|
||||
return GetInstantMixFromArtist(artist, user, dtoOptions);
|
||||
}
|
||||
|
||||
var song = item as Audio;
|
||||
if (song != null)
|
||||
if (item is Audio song)
|
||||
{
|
||||
return GetInstantMixFromSong(song, user, dtoOptions);
|
||||
}
|
||||
|
||||
var folder = item as Folder;
|
||||
if (folder != null)
|
||||
if (item is Folder folder)
|
||||
{
|
||||
return GetInstantMixFromFolder(folder, user, dtoOptions);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
|
||||
{
|
||||
User user = null;
|
||||
Jellyfin.Data.Entities.User user = null;
|
||||
|
||||
if (query.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
@@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>IEnumerable{SearchHintResult}.</returns>
|
||||
/// <exception cref="ArgumentNullException">searchTerm</exception>
|
||||
private List<SearchHintInfo> GetSearchHints(SearchQuery query, User user)
|
||||
private List<SearchHintInfo> GetSearchHints(SearchQuery query, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var searchTerm = query.SearchTerm;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library
|
||||
SaveUserData(user, item, userData, reason, cancellationToken);
|
||||
}
|
||||
|
||||
public void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
|
||||
public void SaveUserData(Jellyfin.Data.Entities.User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
|
||||
{
|
||||
if (userData == null)
|
||||
{
|
||||
@@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetUserData(user, itemId, keys);
|
||||
}
|
||||
|
||||
public UserItemData GetUserData(User user, Guid itemId, List<string> keys)
|
||||
public UserItemData GetUserData(Jellyfin.Data.Entities.User user, Guid itemId, List<string> keys)
|
||||
{
|
||||
var userId = user.InternalId;
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public UserItemData GetUserData(User user, BaseItem item)
|
||||
public UserItemData GetUserData(Jellyfin.Data.Entities.User user, BaseItem item)
|
||||
{
|
||||
return GetUserData(user, item.Id, item.GetUserDataKeys());
|
||||
}
|
||||
@@ -167,7 +167,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetUserData(userId, item.Id, item.GetUserDataKeys());
|
||||
}
|
||||
|
||||
public UserItemDataDto GetUserDataDto(BaseItem item, User user)
|
||||
public UserItemDataDto GetUserDataDto(BaseItem item, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var userData = GetUserData(user, item);
|
||||
var dto = GetUserItemDataDto(userData);
|
||||
@@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return dto;
|
||||
}
|
||||
|
||||
public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions options)
|
||||
public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, Jellyfin.Data.Entities.User user, DtoOptions options)
|
||||
{
|
||||
var userData = GetUserData(user, item);
|
||||
var dto = GetUserItemDataDto(userData);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
@@ -125,12 +126,12 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (!query.IncludeHidden)
|
||||
{
|
||||
list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList();
|
||||
list = list.Where(i => !user.GetPreference(PreferenceKind.MyMediaExcludes).Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList();
|
||||
}
|
||||
|
||||
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
|
||||
|
||||
var orders = user.Configuration.OrderedViews.ToList();
|
||||
var orders = user.GetPreference(PreferenceKind.OrderedViews).ToList();
|
||||
|
||||
return list
|
||||
.OrderBy(i =>
|
||||
@@ -165,7 +166,13 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetUserSubViewWithName(name, parentId, type, sortName);
|
||||
}
|
||||
|
||||
private Folder GetUserView(List<ICollectionFolder> parents, string viewType, string localizationKey, string sortName, User user, string[] presetViews)
|
||||
private Folder GetUserView(
|
||||
List<ICollectionFolder> parents,
|
||||
string viewType,
|
||||
string localizationKey,
|
||||
string sortName,
|
||||
Jellyfin.Data.Entities.User user,
|
||||
string[] presetViews)
|
||||
{
|
||||
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
@@ -226,7 +233,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return list;
|
||||
}
|
||||
|
||||
private IReadOnlyList<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
|
||||
private IReadOnlyList<BaseItem> GetItemsForLatestItems(Jellyfin.Data.Entities.User user, LatestItemsQuery request, DtoOptions options)
|
||||
{
|
||||
var parentId = request.ParentId;
|
||||
|
||||
@@ -270,7 +277,8 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.Where(i => i is Folder)
|
||||
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes)
|
||||
.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -331,12 +339,11 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[]
|
||||
{
|
||||
typeof(Person).Name,
|
||||
typeof(Studio).Name,
|
||||
typeof(Year).Name,
|
||||
typeof(MusicGenre).Name,
|
||||
typeof(Genre).Name
|
||||
|
||||
nameof(Person),
|
||||
nameof(Studio),
|
||||
nameof(Year),
|
||||
nameof(MusicGenre),
|
||||
nameof(Genre)
|
||||
} : Array.Empty<string>();
|
||||
|
||||
var query = new InternalItemsQuery(user)
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Library;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
@@ -707,7 +708,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
Path = info.ThumbImageUrl,
|
||||
Type = ImageType.Thumb
|
||||
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -720,7 +720,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
Path = info.LogoImageUrl,
|
||||
Type = ImageType.Logo
|
||||
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -733,7 +732,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
Path = info.BackdropImageUrl,
|
||||
Type = ImageType.Backdrop
|
||||
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -765,13 +763,14 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated);
|
||||
}
|
||||
|
||||
public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
|
||||
public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, Jellyfin.Data.Entities.User user = null)
|
||||
{
|
||||
var program = _libraryManager.GetItemById(id);
|
||||
|
||||
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
|
||||
|
||||
var list = new List<Tuple<BaseItemDto, string, string>>() {
|
||||
var list = new List<Tuple<BaseItemDto, string, string>>
|
||||
{
|
||||
new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId)
|
||||
};
|
||||
|
||||
@@ -939,7 +938,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
};
|
||||
}
|
||||
|
||||
private int GetRecommendationScore(LiveTvProgram program, User user, bool factorChannelWatchCount)
|
||||
private int GetRecommendationScore(LiveTvProgram program, Jellyfin.Data.Entities.User user, bool factorChannelWatchCount)
|
||||
{
|
||||
var score = 0;
|
||||
|
||||
@@ -1325,7 +1324,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return 7;
|
||||
}
|
||||
|
||||
private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, DtoOptions dtoOptions, User user)
|
||||
private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, DtoOptions dtoOptions, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
@@ -1433,7 +1432,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> tuples, ItemFields[] fields, User user = null)
|
||||
public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> tuples, ItemFields[] fields, Jellyfin.Data.Entities.User user = null)
|
||||
{
|
||||
var programTuples = new List<Tuple<BaseItemDto, string, string>>();
|
||||
var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
|
||||
@@ -1483,7 +1482,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return EmbyTV.EmbyTV.Current.GetActiveRecordingInfo(path);
|
||||
}
|
||||
|
||||
public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User user = null)
|
||||
public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, Jellyfin.Data.Entities.User user = null)
|
||||
{
|
||||
var service = EmbyTV.EmbyTV.Current;
|
||||
|
||||
@@ -1895,7 +1894,7 @@ 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)> tuples, DtoOptions options, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
@@ -2206,23 +2205,22 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
var info = new LiveTvInfo
|
||||
{
|
||||
Services = services,
|
||||
IsEnabled = services.Length > 0
|
||||
IsEnabled = services.Length > 0,
|
||||
EnabledUsers = _userManager.Users
|
||||
.Where(IsLiveTvEnabled)
|
||||
.Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
|
||||
.ToArray()
|
||||
};
|
||||
|
||||
info.EnabledUsers = _userManager.Users
|
||||
.Where(IsLiveTvEnabled)
|
||||
.Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
|
||||
.ToArray();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private bool IsLiveTvEnabled(User user)
|
||||
private bool IsLiveTvEnabled(Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0);
|
||||
return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0);
|
||||
}
|
||||
|
||||
public IEnumerable<User> GetEnabledUsers()
|
||||
public IEnumerable<Jellyfin.Data.Entities.User> GetEnabledUsers()
|
||||
{
|
||||
return _userManager.Users
|
||||
.Where(IsLiveTvEnabled);
|
||||
@@ -2472,12 +2470,12 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return _tvDtoService.GetInternalProgramId(externalId);
|
||||
}
|
||||
|
||||
public List<BaseItem> GetRecordingFolders(User user)
|
||||
public List<BaseItem> GetRecordingFolders(Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
return GetRecordingFolders(user, false);
|
||||
}
|
||||
|
||||
private List<BaseItem> GetRecordingFolders(User user, bool refreshChannels)
|
||||
private List<BaseItem> GetRecordingFolders(Jellyfin.Data.Entities.User user, bool refreshChannels)
|
||||
{
|
||||
var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders()
|
||||
.SelectMany(i => i.Locations)
|
||||
|
||||
@@ -14,12 +14,12 @@ namespace Emby.Server.Implementations.Playlists
|
||||
Name = "Playlists";
|
||||
}
|
||||
|
||||
public override bool IsVisible(User user)
|
||||
public override bool IsVisible(Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
return base.IsVisible(user) && GetChildren(user, true).Any();
|
||||
}
|
||||
|
||||
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
|
||||
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>();
|
||||
}
|
||||
@@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
}
|
||||
|
||||
query.Recursive = true;
|
||||
query.IncludeItemTypes = new string[] { "Playlist" };
|
||||
query.IncludeItemTypes = new[] { "Playlist" };
|
||||
query.Parent = null;
|
||||
return LibraryManager.GetItemsResult(query);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
return path;
|
||||
}
|
||||
|
||||
private List<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, string playlistMediaType, User user, DtoOptions options)
|
||||
private List<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, string playlistMediaType, Jellyfin.Data.Entities.User user, DtoOptions options)
|
||||
{
|
||||
var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null);
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
});
|
||||
}
|
||||
|
||||
private void AddToPlaylistInternal(string playlistId, ICollection<Guid> newItemIds, User user, DtoOptions options)
|
||||
private void AddToPlaylistInternal(string playlistId, ICollection<Guid> newItemIds, Jellyfin.Data.Entities.User user, DtoOptions options)
|
||||
{
|
||||
// Retrieve the existing playlist
|
||||
var playlist = _libraryManager.GetItemById(playlistId) as Playlist
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -253,7 +254,7 @@ namespace Emby.Server.Implementations.Session
|
||||
string deviceId,
|
||||
string deviceName,
|
||||
string remoteEndPoint,
|
||||
User user)
|
||||
Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
@@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue;
|
||||
var userLastActivityDate = user.LastActivityDate;
|
||||
user.LastActivityDate = activityDate;
|
||||
|
||||
if ((activityDate - userLastActivityDate).TotalSeconds > 60)
|
||||
@@ -431,7 +432,13 @@ 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(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
|
||||
private SessionInfo GetSessionInfo(
|
||||
string appName,
|
||||
string appVersion,
|
||||
string deviceId,
|
||||
string deviceName,
|
||||
string remoteEndPoint,
|
||||
Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
@@ -444,14 +451,13 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
var sessionInfo = _activeConnections.GetOrAdd(key, k =>
|
||||
{
|
||||
return CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user);
|
||||
});
|
||||
var sessionInfo = _activeConnections.GetOrAdd(
|
||||
key,
|
||||
k => CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user));
|
||||
|
||||
sessionInfo.UserId = user == null ? Guid.Empty : user.Id;
|
||||
sessionInfo.UserName = user?.Name;
|
||||
sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary);
|
||||
sessionInfo.UserId = user?.Id ?? Guid.Empty;
|
||||
sessionInfo.UserName = user?.Username;
|
||||
sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user);
|
||||
sessionInfo.RemoteEndPoint = remoteEndPoint;
|
||||
sessionInfo.Client = appName;
|
||||
|
||||
@@ -470,7 +476,14 @@ namespace Emby.Server.Implementations.Session
|
||||
return sessionInfo;
|
||||
}
|
||||
|
||||
private SessionInfo CreateSession(string key, string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
|
||||
private SessionInfo CreateSession(
|
||||
string key,
|
||||
string appName,
|
||||
string appVersion,
|
||||
string deviceId,
|
||||
string deviceName,
|
||||
string remoteEndPoint,
|
||||
Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var sessionInfo = new SessionInfo(this, _logger)
|
||||
{
|
||||
@@ -481,11 +494,11 @@ namespace Emby.Server.Implementations.Session
|
||||
ServerId = _appHost.SystemId
|
||||
};
|
||||
|
||||
var username = user?.Name;
|
||||
var username = user?.Username;
|
||||
|
||||
sessionInfo.UserId = user?.Id ?? Guid.Empty;
|
||||
sessionInfo.UserName = username;
|
||||
sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary);
|
||||
sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user);
|
||||
sessionInfo.RemoteEndPoint = remoteEndPoint;
|
||||
|
||||
if (string.IsNullOrEmpty(deviceName))
|
||||
@@ -508,9 +521,9 @@ namespace Emby.Server.Implementations.Session
|
||||
return sessionInfo;
|
||||
}
|
||||
|
||||
private List<User> GetUsers(SessionInfo session)
|
||||
private List<Jellyfin.Data.Entities.User> GetUsers(SessionInfo session)
|
||||
{
|
||||
var users = new List<User>();
|
||||
var users = new List<Jellyfin.Data.Entities.User>();
|
||||
|
||||
if (session.UserId != Guid.Empty)
|
||||
{
|
||||
@@ -533,10 +546,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
private void StartIdleCheckTimer()
|
||||
{
|
||||
if (_idleTimer == null)
|
||||
{
|
||||
_idleTimer = new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
_idleTimer ??= new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
private void StopIdleCheckTimer()
|
||||
@@ -671,7 +681,7 @@ namespace Emby.Server.Implementations.Session
|
||||
/// </summary>
|
||||
/// <param name="user">The user object.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
private void OnPlaybackStart(User user, BaseItem item)
|
||||
private void OnPlaybackStart(Jellyfin.Data.Entities.User user, BaseItem item)
|
||||
{
|
||||
var data = _userDataManager.GetUserData(user, item);
|
||||
|
||||
@@ -754,7 +764,7 @@ namespace Emby.Server.Implementations.Session
|
||||
StartIdleCheckTimer();
|
||||
}
|
||||
|
||||
private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
|
||||
private void OnPlaybackProgress(Jellyfin.Data.Entities.User user, BaseItem item, PlaybackProgressInfo info)
|
||||
{
|
||||
var data = _userDataManager.GetUserData(user, item);
|
||||
|
||||
@@ -780,11 +790,11 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
private static bool UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data)
|
||||
private static bool UpdatePlaybackSettings(Jellyfin.Data.Entities.User user, PlaybackProgressInfo info, UserItemData data)
|
||||
{
|
||||
var changed = false;
|
||||
|
||||
if (user.Configuration.RememberAudioSelections)
|
||||
if (user.RememberAudioSelections)
|
||||
{
|
||||
if (data.AudioStreamIndex != info.AudioStreamIndex)
|
||||
{
|
||||
@@ -801,7 +811,7 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
if (user.Configuration.RememberSubtitleSelections)
|
||||
if (user.RememberSubtitleSelections)
|
||||
{
|
||||
if (data.SubtitleStreamIndex != info.SubtitleStreamIndex)
|
||||
{
|
||||
@@ -940,7 +950,7 @@ namespace Emby.Server.Implementations.Session
|
||||
_logger);
|
||||
}
|
||||
|
||||
private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed)
|
||||
private bool OnPlaybackStopped(Jellyfin.Data.Entities.User user, BaseItem item, long? positionTicks, bool playbackFailed)
|
||||
{
|
||||
bool playedToCompletion = false;
|
||||
|
||||
@@ -1112,13 +1122,13 @@ namespace Emby.Server.Implementations.Session
|
||||
if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Name));
|
||||
string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Username));
|
||||
}
|
||||
}
|
||||
|
||||
if (user != null
|
||||
&& command.ItemIds.Length == 1
|
||||
&& user.Configuration.EnableNextEpisodeAutoPlay
|
||||
&& user.EnableNextEpisodeAutoPlay
|
||||
&& _libraryManager.GetItemById(command.ItemIds[0]) is Episode episode)
|
||||
{
|
||||
var series = episode.Series;
|
||||
@@ -1154,7 +1164,7 @@ namespace Emby.Server.Implementations.Session
|
||||
await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user)
|
||||
private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
||||
@@ -1173,7 +1183,7 @@ namespace Emby.Server.Implementations.Session
|
||||
DtoOptions = new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false,
|
||||
Fields = new ItemFields[]
|
||||
Fields = new[]
|
||||
{
|
||||
ItemFields.SortName
|
||||
}
|
||||
@@ -1207,7 +1217,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return new[] { item };
|
||||
}
|
||||
|
||||
private IEnumerable<BaseItem> TranslateItemForInstantMix(Guid id, User user)
|
||||
private IEnumerable<BaseItem> TranslateItemForInstantMix(Guid id, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
||||
@@ -1335,7 +1345,7 @@ namespace Emby.Server.Implementations.Session
|
||||
list.Add(new SessionUserInfo
|
||||
{
|
||||
UserId = userId,
|
||||
UserName = user.Name
|
||||
UserName = user.Username
|
||||
});
|
||||
|
||||
session.AdditionalUsers = list.ToArray();
|
||||
@@ -1390,7 +1400,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
User user = null;
|
||||
Jellyfin.Data.Entities.User user = null;
|
||||
if (request.UserId != Guid.Empty)
|
||||
{
|
||||
user = _userManager.GetUserById(request.UserId);
|
||||
@@ -1446,7 +1456,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return returnResult;
|
||||
}
|
||||
|
||||
private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
|
||||
private string GetAuthorizationToken(Jellyfin.Data.Entities.User user, string deviceId, string app, string appVersion, string deviceName)
|
||||
{
|
||||
var existing = _authRepo.Get(
|
||||
new AuthenticationInfoQuery
|
||||
@@ -1495,7 +1505,7 @@ namespace Emby.Server.Implementations.Session
|
||||
DeviceName = deviceName,
|
||||
UserId = user.Id,
|
||||
AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
|
||||
UserName = user.Name
|
||||
UserName = user.Username
|
||||
};
|
||||
|
||||
_logger.LogInformation("Creating new access token for user {0}", user.Id);
|
||||
@@ -1692,15 +1702,15 @@ namespace Emby.Server.Implementations.Session
|
||||
return info;
|
||||
}
|
||||
|
||||
private string GetImageCacheTag(BaseItem item, ImageType type)
|
||||
private string GetImageCacheTag(Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _imageProcessor.GetImageCacheTag(item, type);
|
||||
return _imageProcessor.GetImageCacheTag(user);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting image information for {Type}", type);
|
||||
_logger.LogError(e, "Error getting image information for profile image");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1809,7 +1819,10 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToList();
|
||||
var adminUserIds = _userManager.Users
|
||||
.Where(i => i.HasPermission(PermissionKind.IsAdministrator))
|
||||
.Select(i => i.Id)
|
||||
.ToList();
|
||||
|
||||
return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Sorting
|
||||
/// Gets or sets the user.
|
||||
/// </summary>
|
||||
/// <value>The user.</value>
|
||||
public User User { get; set; }
|
||||
public Jellyfin.Data.Entities.User User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user manager.
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting
|
||||
/// Gets or sets the user.
|
||||
/// </summary>
|
||||
/// <value>The user.</value>
|
||||
public User User { get; set; }
|
||||
public Jellyfin.Data.Entities.User User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user manager.
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Sorting
|
||||
/// Gets or sets the user.
|
||||
/// </summary>
|
||||
/// <value>The user.</value>
|
||||
public User User { get; set; }
|
||||
public Jellyfin.Data.Entities.User User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compares the specified x.
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Sorting
|
||||
/// Gets or sets the user.
|
||||
/// </summary>
|
||||
/// <value>The user.</value>
|
||||
public User User { get; set; }
|
||||
public Jellyfin.Data.Entities.User User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compares the specified x.
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Sorting
|
||||
/// Gets or sets the user.
|
||||
/// </summary>
|
||||
/// <value>The user.</value>
|
||||
public User User { get; set; }
|
||||
public Jellyfin.Data.Entities.User User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compares the specified x.
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Emby.Server.Implementations.Sorting
|
||||
/// Gets or sets the user.
|
||||
/// </summary>
|
||||
/// <value>The user.</value>
|
||||
public User User { get; set; }
|
||||
public Jellyfin.Data.Entities.User User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compares the specified x.
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -74,7 +75,8 @@ namespace Emby.Server.Implementations.TV
|
||||
{
|
||||
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.Where(i => i is Folder)
|
||||
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes)
|
||||
.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
@@ -137,7 +139,7 @@ namespace Emby.Server.Implementations.TV
|
||||
return GetResult(episodes, request);
|
||||
}
|
||||
|
||||
public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys, DtoOptions dtoOptions)
|
||||
public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, Jellyfin.Data.Entities.User user, IEnumerable<string> seriesKeys, DtoOptions dtoOptions)
|
||||
{
|
||||
// Avoid implicitly captured closure
|
||||
var currentUser = user;
|
||||
@@ -186,13 +188,13 @@ namespace Emby.Server.Implementations.TV
|
||||
/// Gets the next up.
|
||||
/// </summary>
|
||||
/// <returns>Task{Episode}.</returns>
|
||||
private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions)
|
||||
private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
AncestorWithPresentationUniqueKey = null,
|
||||
SeriesPresentationUniqueKey = seriesKey,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode) },
|
||||
OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) },
|
||||
IsPlayed = true,
|
||||
Limit = 1,
|
||||
@@ -205,7 +207,6 @@ namespace Emby.Server.Implementations.TV
|
||||
},
|
||||
EnableImages = false
|
||||
}
|
||||
|
||||
}).FirstOrDefault();
|
||||
|
||||
Func<Episode> getEpisode = () =>
|
||||
@@ -220,7 +221,7 @@ namespace Emby.Server.Implementations.TV
|
||||
IsPlayed = false,
|
||||
IsVirtualItem = false,
|
||||
ParentIndexNumberNotEquals = 0,
|
||||
MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName,
|
||||
MinSortName = lastWatchedEpisode?.SortName,
|
||||
DtoOptions = dtoOptions
|
||||
|
||||
}).Cast<Episode>().FirstOrDefault();
|
||||
|
||||
Reference in New Issue
Block a user