Merge branch 'master' into buffer

This commit is contained in:
Bond-009
2020-06-18 17:01:15 +02:00
committed by GitHub
1259 changed files with 27613 additions and 14222 deletions

View File

@@ -1,16 +1,13 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
@@ -27,9 +24,12 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
/// <summary>
/// Entry point for the activity logger.
/// </summary>
public sealed class ActivityLogEntryPoint : IServerEntryPoint
{
private readonly ILogger _logger;
private readonly ILogger<ActivityLogEntryPoint> _logger;
private readonly IInstallationManager _installationManager;
private readonly ISessionManager _sessionManager;
private readonly ITaskManager _taskManager;
@@ -37,25 +37,21 @@ namespace Emby.Server.Implementations.Activity
private readonly ILocalizationManager _localization;
private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager;
private readonly IDeviceManager _deviceManager;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
/// </summary>
/// <param name="logger"></param>
/// <param name="sessionManager"></param>
/// <param name="deviceManager"></param>
/// <param name="taskManager"></param>
/// <param name="activityManager"></param>
/// <param name="localization"></param>
/// <param name="installationManager"></param>
/// <param name="subManager"></param>
/// <param name="userManager"></param>
/// <param name="appHost"></param>
/// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param>
/// <param name="taskManager">The task manager.</param>
/// <param name="activityManager">The activity manager.</param>
/// <param name="localization">The localization manager.</param>
/// <param name="installationManager">The installation manager.</param>
/// <param name="subManager">The subtitle manager.</param>
/// <param name="userManager">The user manager.</param>
public ActivityLogEntryPoint(
ILogger<ActivityLogEntryPoint> logger,
ISessionManager sessionManager,
IDeviceManager deviceManager,
ITaskManager taskManager,
IActivityManager activityManager,
ILocalizationManager localization,
@@ -65,7 +61,6 @@ namespace Emby.Server.Implementations.Activity
{
_logger = logger;
_sessionManager = sessionManager;
_deviceManager = deviceManager;
_taskManager = taskManager;
_activityManager = activityManager;
_localization = localization;
@@ -74,6 +69,7 @@ namespace Emby.Server.Implementations.Activity
_userManager = userManager;
}
/// <inheritdoc />
public Task RunAsync()
{
_taskManager.TaskCompleted += OnTaskCompleted;
@@ -92,58 +88,45 @@ namespace Emby.Server.Implementations.Activity
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
_userManager.UserCreated += OnUserCreated;
_userManager.UserPasswordChanged += OnUserPasswordChanged;
_userManager.UserDeleted += OnUserDeleted;
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
_userManager.UserLockedOut += OnUserLockedOut;
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
_userManager.OnUserCreated += OnUserCreated;
_userManager.OnUserPasswordChanged += OnUserPasswordChanged;
_userManager.OnUserDeleted += OnUserDeleted;
_userManager.OnUserLockedOut += OnUserLockedOut;
return Task.CompletedTask;
}
private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserLockedOutWithName"),
e.Argument.Username),
NotificationType.UserLockedOut.ToString(),
e.Argument.Id)
{
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("CameraImageUploadedFrom"),
e.Argument.Device.Name),
Type = NotificationType.CameraImageUploaded.ToString()
});
LogSeverity = LogLevel.Error
}).ConfigureAwait(false);
}
private void OnUserLockedOut(object sender, GenericEventArgs<User> e)
private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserLockedOutWithName"),
e.Argument.Name),
Type = NotificationType.UserLockedOut.ToString(),
UserId = e.Argument.Id
});
}
private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
e.Provider,
Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
Type = "SubtitleDownloadFailure",
Notifications.NotificationEntryPoint.GetItemName(e.Item)),
"SubtitleDownloadFailure",
Guid.Empty)
{
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
ShortOverview = e.Exception.Message
});
}).ConfigureAwait(false);
}
private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
{
var item = e.MediaInfo;
@@ -166,15 +149,19 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users[0];
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
Type = GetPlaybackStoppedNotificationType(item.MediaType),
UserId = user.Id
});
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
user.Username,
GetItemName(item),
e.DeviceName),
GetPlaybackStoppedNotificationType(item.MediaType),
user.Id))
.ConfigureAwait(false);
}
private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
{
var item = e.MediaInfo;
@@ -197,17 +184,16 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users.First();
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
user.Name,
user.Username,
GetItemName(item),
e.DeviceName),
Type = GetPlaybackNotificationType(item.MediaType),
UserId = user.Id
});
GetPlaybackNotificationType(item.MediaType),
user.Id))
.ConfigureAwait(false);
}
private static string GetItemName(BaseItemDto item)
@@ -257,236 +243,203 @@ namespace Emby.Server.Implementations.Activity
return null;
}
private void OnSessionEnded(object sender, SessionEventArgs e)
private async void OnSessionEnded(object sender, SessionEventArgs e)
{
string name;
var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName))
{
name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("DeviceOfflineWithName"),
session.DeviceName);
// Causing too much spam for now
return;
}
else
{
name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOfflineFromDevice"),
session.UserName,
session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
session.DeviceName),
"SessionEnded",
session.UserId)
{
Name = name,
Type = "SessionEnded",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
session.RemoteEndPoint),
UserId = session.UserId
});
}).ConfigureAwait(false);
}
private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
{
var user = e.Argument.User;
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
user.Name),
Type = "AuthenticationSucceeded",
"AuthenticationSucceeded",
user.Id)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.SessionInfo.RemoteEndPoint),
UserId = user.Id
});
}).ConfigureAwait(false);
}
private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
e.Argument.Username),
Type = "AuthenticationFailed",
"AuthenticationFailed",
Guid.Empty)
{
LogSeverity = LogLevel.Error,
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.RemoteEndPoint),
Severity = LogLevel.Error
});
}).ConfigureAwait(false);
}
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
e.Argument.Name),
Type = "UserPolicyUpdated",
UserId = e.Argument.Id
});
}
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserDeletedWithName"),
e.Argument.Name),
Type = "UserDeleted"
});
e.Argument.Username),
"UserDeleted",
Guid.Empty))
.ConfigureAwait(false);
}
private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPasswordChangedWithName"),
e.Argument.Name),
Type = "UserPasswordChanged",
UserId = e.Argument.Id
});
e.Argument.Username),
"UserPasswordChanged",
e.Argument.Id))
.ConfigureAwait(false);
}
private void OnUserCreated(object sender, GenericEventArgs<User> e)
private async void OnUserCreated(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserCreatedWithName"),
e.Argument.Name),
Type = "UserCreated",
UserId = e.Argument.Id
});
e.Argument.Username),
"UserCreated",
e.Argument.Id))
.ConfigureAwait(false);
}
private void OnSessionStarted(object sender, SessionEventArgs e)
private async void OnSessionStarted(object sender, SessionEventArgs e)
{
string name;
var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName))
{
name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("DeviceOnlineWithName"),
session.DeviceName);
// Causing too much spam for now
return;
}
else
{
name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOnlineFromDevice"),
session.UserName,
session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
session.DeviceName),
"SessionStarted",
session.UserId)
{
Name = name,
Type = "SessionStarted",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
session.RemoteEndPoint),
UserId = session.UserId
});
session.RemoteEndPoint)
}).ConfigureAwait(false);
}
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
private async void OnPluginUpdated(object sender, InstallationInfo e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUpdatedWithName"),
e.Argument.Item1.Name),
Type = NotificationType.PluginUpdateInstalled.ToString(),
e.Name),
NotificationType.PluginUpdateInstalled.ToString(),
Guid.Empty)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
e.Argument.Item2.versionStr),
Overview = e.Argument.Item2.description
});
e.Version),
Overview = e.Changelog
}).ConfigureAwait(false);
}
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
private async void OnPluginUninstalled(object sender, IPlugin e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUninstalledWithName"),
e.Argument.Name),
Type = NotificationType.PluginUninstalled.ToString()
});
e.Name),
NotificationType.PluginUninstalled.ToString(),
Guid.Empty))
.ConfigureAwait(false);
}
private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
private async void OnPluginInstalled(object sender, InstallationInfo e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginInstalledWithName"),
e.Argument.name),
Type = NotificationType.PluginInstalled.ToString(),
e.Name),
NotificationType.PluginInstalled.ToString(),
Guid.Empty)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
e.Argument.versionStr)
});
e.Version)
}).ConfigureAwait(false);
}
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
{
var installationInfo = e.InstallationInfo;
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
await CreateLogEntry(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("NameInstallFailed"),
installationInfo.Name),
Type = NotificationType.InstallationFailed.ToString(),
NotificationType.InstallationFailed.ToString(),
Guid.Empty)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
installationInfo.Version),
Overview = e.Exception.Message
});
}).ConfigureAwait(false);
}
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
{
var result = e.Result;
var task = e.Task;
var activityTask = task.ScheduledTask as IConfigurableScheduledTask;
if (activityTask != null && !activityTask.IsLogged)
if (task.ScheduledTask is IConfigurableScheduledTask activityTask
&& !activityTask.IsLogged)
{
return;
}
@@ -511,22 +464,20 @@ namespace Emby.Server.Implementations.Activity
vals.Add(e.Result.LongErrorMessage);
}
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLog(
string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
NotificationType.TaskFailed.ToString(),
Guid.Empty)
{
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ScheduledTaskFailedWithName"),
task.Name),
Type = NotificationType.TaskFailed.ToString(),
LogSeverity = LogLevel.Error,
Overview = string.Join(Environment.NewLine, vals),
ShortOverview = runningTime,
Severity = LogLevel.Error
});
ShortOverview = runningTime
}).ConfigureAwait(false);
}
}
private void CreateLogEntry(ActivityLogEntry entry)
=> _activityManager.Create(entry);
private async Task CreateLogEntry(ActivityLog entry)
=> await _activityManager.CreateAsync(entry).ConfigureAwait(false);
/// <inheritdoc />
public void Dispose()
@@ -548,19 +499,16 @@ namespace Emby.Server.Implementations.Activity
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
_userManager.UserCreated -= OnUserCreated;
_userManager.UserPasswordChanged -= OnUserPasswordChanged;
_userManager.UserDeleted -= OnUserDeleted;
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
_userManager.UserLockedOut -= OnUserLockedOut;
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
_userManager.OnUserCreated -= OnUserCreated;
_userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
_userManager.OnUserDeleted -= OnUserDeleted;
_userManager.OnUserLockedOut -= OnUserLockedOut;
}
/// <summary>
/// Constructs a user-friendly string for this TimeSpan instance.
/// </summary>
public static string ToUserFriendlyString(TimeSpan span)
private static string ToUserFriendlyString(TimeSpan span)
{
const int DaysInYear = 365;
const int DaysInMonth = 30;
@@ -574,7 +522,7 @@ namespace Emby.Server.Implementations.Activity
{
int years = days / DaysInYear;
values.Add(CreateValueString(years, "year"));
days = days % DaysInYear;
days %= DaysInYear;
}
// Number of months

View File

@@ -1,67 +0,0 @@
#pragma warning disable CS1591
using System;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
public class ActivityManager : IActivityManager
{
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
private readonly IActivityRepository _repo;
private readonly ILogger _logger;
private readonly IUserManager _userManager;
public ActivityManager(
ILoggerFactory loggerFactory,
IActivityRepository repo,
IUserManager userManager)
{
_logger = loggerFactory.CreateLogger(nameof(ActivityManager));
_repo = repo;
_userManager = userManager;
}
public void Create(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;
_repo.Create(entry);
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
foreach (var item in result.Items)
{
if (item.UserId == Guid.Empty)
{
continue;
}
var user = _userManager.GetUserById(item.UserId);
if (user != null)
{
var dto = _userManager.GetUserDto(user);
item.UserPrimaryImageTag = dto.PrimaryImageTag;
}
}
return result;
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
return GetActivityLogEntries(minDate, null, startIndex, limit);
}
}
}

View File

@@ -1,313 +0,0 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Emby.Server.Implementations.Data;
using MediaBrowser.Controller;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Activity
{
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private readonly IFileSystem _fileSystem;
public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
_fileSystem = fileSystem;
}
public void Initialize()
{
try
{
InitializeInternal();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
_fileSystem.DeleteFile(DbFilePath);
InitializeInternal();
}
}
private void InitializeInternal()
{
using (var connection = GetConnection())
{
connection.RunQueries(new[]
{
"create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
"drop index if exists idx_ActivityLogEntries"
});
TryMigrate(connection);
}
}
private void TryMigrate(ManagedConnection connection)
{
try
{
if (TableExists(connection, "ActivityLogEntries"))
{
connection.RunQueries(new[]
{
"INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries",
"drop table if exists ActivityLogEntries"
});
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error migrating activity log database");
}
}
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
public void Create(ActivityLogEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
using (var connection = GetConnection())
{
connection.RunInTransaction(db =>
{
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
{
statement.TryBind("@Name", entry.Name);
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
if (entry.UserId.Equals(Guid.Empty))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}
}, TransactionMode);
}
}
public void Update(ActivityLogEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
using (var connection = GetConnection())
{
connection.RunInTransaction(db =>
{
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
{
statement.TryBind("@Id", entry.Id);
statement.TryBind("@Name", entry.Name);
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
if (entry.UserId.Equals(Guid.Empty))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}
}, TransactionMode);
}
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var commandText = BaseActivitySelectText;
var whereClauses = new List<string>();
if (minDate.HasValue)
{
whereClauses.Add("DateCreated>=@DateCreated");
}
if (hasUserId.HasValue)
{
if (hasUserId.Value)
{
whereClauses.Add("UserId not null");
}
else
{
whereClauses.Add("UserId is null");
}
}
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
if (startIndex.HasValue && startIndex.Value > 0)
{
var pagingWhereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
whereClauses.Add(
string.Format(
CultureInfo.InvariantCulture,
"Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
pagingWhereText,
startIndex.Value));
}
var whereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
commandText += whereText;
commandText += " ORDER BY DateCreated DESC";
if (limit.HasValue)
{
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
}
var statementTexts = new[]
{
commandText,
"select count (Id) from ActivityLog" + whereTextWithoutPaging
};
var list = new List<ActivityLogEntry>();
var result = new QueryResult<ActivityLogEntry>();
using (var connection = GetConnection(true))
{
connection.RunInTransaction(
db =>
{
var statements = PrepareAll(db, statementTexts).ToList();
using (var statement = statements[0])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
foreach (var row in statement.ExecuteQuery())
{
list.Add(GetEntry(row));
}
}
using (var statement = statements[1])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
},
ReadTransactionMode);
}
result.Items = list;
return result;
}
private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader)
{
var index = 0;
var info = new ActivityLogEntry
{
Id = reader[index].ToInt64()
};
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Name = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Overview = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.ShortOverview = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Type = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.ItemId = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.UserId = new Guid(reader[index].ToString());
}
index++;
info.Date = reader[index].ReadDateTime();
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
}
return info;
}
}
}

View File

@@ -15,6 +15,11 @@ namespace Emby.Server.Implementations.AppBase
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
/// </summary>
/// <param name="programDataPath">The program data path.</param>
/// <param name="logDirectoryPath">The log directory path.</param>
/// <param name="configurationDirectoryPath">The configuration directory path.</param>
/// <param name="cacheDirectoryPath">The cache directory path.</param>
/// <param name="webDirectoryPath">The web directory path.</param>
protected BaseApplicationPaths(
string programDataPath,
string logDirectoryPath,

View File

@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase
CommonApplicationPaths = applicationPaths;
XmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
Logger = loggerFactory.CreateLogger(GetType().Name);
Logger = loggerFactory.CreateLogger<BaseConfigurationManager>();
UpdateCachePath();
}
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.AppBase
/// Gets the logger.
/// </summary>
/// <value>The logger.</value>
protected ILogger Logger { get; private set; }
protected ILogger<BaseConfigurationManager> Logger { get; private set; }
/// <summary>
/// Gets the XML serializer.

View File

@@ -36,24 +36,22 @@ namespace Emby.Server.Implementations.AppBase
configuration = Activator.CreateInstance(type);
}
using (var stream = new MemoryStream())
using var stream = new MemoryStream();
xmlSerializer.SerializeToStream(configuration, stream);
// Take the object we just got and serialize it back to bytes
var newBytes = stream.ToArray();
// If the file didn't exist before, or if something has changed, re-save
if (buffer == null || !buffer.SequenceEqual(newBytes))
{
xmlSerializer.SerializeToStream(configuration, stream);
Directory.CreateDirectory(Path.GetDirectoryName(path));
// Take the object we just got and serialize it back to bytes
var newBytes = stream.ToArray();
// If the file didn't exist before, or if something has changed, re-save
if (buffer == null || !buffer.SequenceEqual(newBytes))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
// Save it after load in case we got new items
File.WriteAllBytes(path, newBytes);
}
return configuration;
// Save it after load in case we got new items
File.WriteAllBytes(path, newBytes);
}
return configuration;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,10 +22,8 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
{
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
}
using var fileStream = File.OpenRead(sourceFile);
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
@@ -36,67 +34,61 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
{
using (var reader = ReaderFactory.Open(source))
using var reader = ReaderFactory.Open(source);
var options = new ExtractionOptions
{
var options = new ExtractionOptions();
options.ExtractFullPath = true;
ExtractFullPath = true
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
}
/// <inheritdoc />
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
{
using (var reader = ZipReader.Open(source))
using var reader = ZipReader.Open(source);
var options = new ExtractionOptions
{
var options = new ExtractionOptions();
options.ExtractFullPath = true;
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
}
reader.WriteAllToDirectory(targetPath, options);
}
/// <inheritdoc />
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
{
using (var reader = GZipReader.Open(source))
using var reader = GZipReader.Open(source);
var options = new ExtractionOptions
{
var options = new ExtractionOptions();
options.ExtractFullPath = true;
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
}
reader.WriteAllToDirectory(targetPath, options);
}
/// <inheritdoc />
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
{
using (var reader = GZipReader.Open(source))
using var reader = GZipReader.Open(source);
if (reader.MoveToNextEntry())
{
if (reader.MoveToNextEntry())
{
var entry = reader.Entry;
var entry = reader.Entry;
var filename = entry.Key;
if (string.IsNullOrWhiteSpace(filename))
{
filename = defaultFileName;
}
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
var filename = entry.Key;
if (string.IsNullOrWhiteSpace(filename))
{
filename = defaultFileName;
}
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
}
}
@@ -108,10 +100,8 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
{
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
}
using var fileStream = File.OpenRead(sourceFile);
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
@@ -122,21 +112,15 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
{
using (var archive = SevenZipArchive.Open(source))
using var archive = SevenZipArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{
using (var reader = archive.ExtractAllEntries())
{
var options = new ExtractionOptions();
options.ExtractFullPath = true;
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
}
}
reader.WriteAllToDirectory(targetPath, options);
}
/// <summary>
@@ -147,10 +131,8 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using (var fileStream = File.OpenRead(sourceFile))
{
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
}
using var fileStream = File.OpenRead(sourceFile);
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
@@ -161,21 +143,15 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
{
using (var archive = TarArchive.Open(source))
using var archive = TarArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{
using (var reader = archive.ExtractAllEntries())
{
var options = new ExtractionOptions();
options.ExtractFullPath = true;
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
}
}
reader.WriteAllToDirectory(targetPath, options);
}
}
}

View File

@@ -1,13 +1,15 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Branding;
namespace Emby.Server.Implementations.Branding
{
/// <summary>
/// A configuration factory for <see cref="BrandingOptions"/>.
/// </summary>
public class BrandingConfigurationFactory : IConfigurationFactory
{
/// <inheritdoc />
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new[]

View File

@@ -31,18 +31,18 @@ namespace Emby.Server.Implementations.Browser
/// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
/// </summary>
/// <param name="appHost">The application host.</param>
/// <param name="url">The URL.</param>
private static void TryOpenUrl(IServerApplicationHost appHost, string url)
/// <param name="relativeUrl">The URL to open, relative to the server base URL.</param>
private static void TryOpenUrl(IServerApplicationHost appHost, string relativeUrl)
{
try
{
string baseUrl = appHost.GetLocalApiUrl("localhost");
appHost.LaunchUrl(baseUrl + url);
appHost.LaunchUrl(baseUrl + relativeUrl);
}
catch (Exception ex)
{
var logger = appHost.Resolve<ILogger>();
logger?.LogError(ex, "Failed to open browser window with URL {URL}", url);
var logger = appHost.Resolve<ILogger<IServerApplicationHost>>();
logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
}
}
}

View File

@@ -1,7 +1,6 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Channels;
@@ -11,6 +10,9 @@ using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.Channels
{
/// <summary>
/// A media source provider for channels.
/// </summary>
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
{
private readonly ChannelManager _channelManager;
@@ -27,12 +29,9 @@ namespace Emby.Server.Implementations.Channels
/// <inheritdoc />
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
{
if (item.SourceType == SourceType.Channel)
{
return _channelManager.GetDynamicMediaSources(item, cancellationToken);
}
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
return item.SourceType == SourceType.Channel
? _channelManager.GetDynamicMediaSources(item, cancellationToken)
: Task.FromResult(Enumerable.Empty<MediaSourceInfo>());
}
/// <inheritdoc />

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -11,20 +9,32 @@ using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Channels
{
/// <summary>
/// An image provider for channels.
/// </summary>
public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
{
private readonly IChannelManager _channelManager;
/// <summary>
/// Initializes a new instance of the <see cref="ChannelImageProvider"/> class.
/// </summary>
/// <param name="channelManager">The channel manager.</param>
public ChannelImageProvider(IChannelManager channelManager)
{
_channelManager = channelManager;
}
/// <inheritdoc />
public string Name => "Channel Image Provider";
/// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return GetChannel(item).GetSupportedChannelImages();
}
/// <inheritdoc />
public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
{
var channel = GetChannel(item);
@@ -32,8 +42,7 @@ namespace Emby.Server.Implementations.Channels
return channel.GetChannelImage(type, cancellationToken);
}
public string Name => "Channel Image Provider";
/// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is Channel;
@@ -46,6 +55,7 @@ namespace Emby.Server.Implementations.Channels
return ((ChannelManager)_channelManager).GetChannelProvider(channel);
}
/// <inheritdoc />
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
return GetSupportedImages(item).Any(i => !item.HasImage(i));

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -8,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
@@ -15,8 +14,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Channels;
@@ -26,28 +23,51 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Server.Implementations.Channels
{
/// <summary>
/// The LiveTV channel manager.
/// </summary>
public class ChannelManager : IChannelManager
{
internal IChannel[] Channels { get; private set; }
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
private readonly IDtoService _dtoService;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly ILogger<ChannelManager> _logger;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IProviderManager _providerManager;
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
/// <summary>
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
/// </summary>
/// <param name="userManager">The user manager.</param>
/// <param name="dtoService">The dto service.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="config">The server configuration manager.</param>
/// <param name="fileSystem">The filesystem.</param>
/// <param name="userDataManager">The user data manager.</param>
/// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="providerManager">The provider manager.</param>
public ChannelManager(
IUserManager userManager,
IDtoService dtoService,
ILibraryManager libraryManager,
ILoggerFactory loggerFactory,
ILogger<ChannelManager> logger,
IServerConfigurationManager config,
IFileSystem fileSystem,
IUserDataManager userDataManager,
@@ -57,7 +77,7 @@ namespace Emby.Server.Implementations.Channels
_userManager = userManager;
_dtoService = dtoService;
_libraryManager = libraryManager;
_logger = loggerFactory.CreateLogger(nameof(ChannelManager));
_logger = logger;
_config = config;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
@@ -65,13 +85,17 @@ namespace Emby.Server.Implementations.Channels
_providerManager = providerManager;
}
internal IChannel[] Channels { get; private set; }
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
/// <inheritdoc />
public void AddParts(IEnumerable<IChannel> channels)
{
Channels = channels.ToArray();
}
/// <inheritdoc />
public bool EnableMediaSourceDisplay(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -80,15 +104,16 @@ namespace Emby.Server.Implementations.Channels
return !(channel is IDisableMediaSourceDisplay);
}
/// <inheritdoc />
public bool CanDelete(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
var supportsDelete = channel as ISupportsDelete;
return supportsDelete != null && supportsDelete.CanDelete(item);
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
}
/// <inheritdoc />
public bool EnableMediaProbe(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -97,6 +122,7 @@ namespace Emby.Server.Implementations.Channels
return channel is ISupportsMediaProbe;
}
/// <inheritdoc />
public Task DeleteItem(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -123,11 +149,16 @@ namespace Emby.Server.Implementations.Channels
.OrderBy(i => i.Name);
}
/// <summary>
/// Get the installed channel IDs.
/// </summary>
/// <returns>An <see cref="IEnumerable{T}"/> containing installed channel IDs.</returns>
public IEnumerable<Guid> GetInstalledChannelIds()
{
return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
}
/// <inheritdoc />
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
{
var user = query.UserId.Equals(Guid.Empty)
@@ -146,15 +177,13 @@ namespace Emby.Server.Implementations.Channels
{
try
{
var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes;
return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
}
catch
{
return false;
}
}).ToList();
}
@@ -171,7 +200,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
}).ToList();
}
@@ -188,9 +216,9 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
}).ToList();
}
if (query.IsFavorite.HasValue)
{
var val = query.IsFavorite.Value;
@@ -215,7 +243,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
}).ToList();
}
@@ -226,6 +253,7 @@ namespace Emby.Server.Implementations.Channels
{
all = all.Skip(query.StartIndex.Value).ToList();
}
if (query.Limit.HasValue)
{
all = all.Take(query.Limit.Value).ToList();
@@ -248,6 +276,7 @@ namespace Emby.Server.Implementations.Channels
};
}
/// <inheritdoc />
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
{
var user = query.UserId.Equals(Guid.Empty)
@@ -256,11 +285,9 @@ namespace Emby.Server.Implementations.Channels
var internalResult = GetChannelsInternal(query);
var dtoOptions = new DtoOptions()
{
};
var dtoOptions = new DtoOptions();
//TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
// TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
var result = new QueryResult<BaseItemDto>
@@ -272,6 +299,12 @@ namespace Emby.Server.Implementations.Channels
return result;
}
/// <summary>
/// Refreshes the associated channels.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>The completed task.</returns>
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
{
var allChannelsList = GetAllChannels().ToList();
@@ -305,14 +338,7 @@ namespace Emby.Server.Implementations.Channels
private Channel GetChannelEntity(IChannel channel)
{
var item = GetChannel(GetInternalChannelId(channel.Name));
if (item == null)
{
item = GetChannel(channel, CancellationToken.None).Result;
}
return item;
return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
}
private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item)
@@ -341,8 +367,8 @@ namespace Emby.Server.Implementations.Channels
}
catch
{
}
return;
}
@@ -351,6 +377,7 @@ namespace Emby.Server.Implementations.Channels
_jsonSerializer.SerializeToFile(mediaSources, path);
}
/// <inheritdoc />
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
{
IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item);
@@ -360,16 +387,20 @@ namespace Emby.Server.Implementations.Channels
.ToList();
}
/// <summary>
/// Gets the dynamic media sources based on the provided item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>The task representing the operation to get the media sources.</returns>
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken)
{
var channel = GetChannel(item.ChannelId);
var channelPlugin = GetChannelProvider(channel);
var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
IEnumerable<MediaSourceInfo> results;
if (requiresCallback != null)
if (channelPlugin is IRequiresMediaInfoCallback requiresCallback)
{
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
.ConfigureAwait(false);
@@ -384,9 +415,6 @@ namespace Emby.Server.Implementations.Channels
.ToList();
}
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
{
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
@@ -409,7 +437,7 @@ namespace Emby.Server.Implementations.Channels
private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info)
{
info.RunTimeTicks = info.RunTimeTicks ?? item.RunTimeTicks;
info.RunTimeTicks ??= item.RunTimeTicks;
return info;
}
@@ -444,18 +472,21 @@ namespace Emby.Server.Implementations.Channels
{
isNew = true;
}
item.Path = path;
if (!item.ChannelId.Equals(id))
{
forceUpdate = true;
}
item.ChannelId = id;
if (item.ParentId != parentFolderId)
{
forceUpdate = true;
}
item.ParentId = parentFolderId;
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
@@ -472,51 +503,56 @@ namespace Emby.Server.Implementations.Channels
_libraryManager.CreateItem(item, null);
}
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = !isNew && forceUpdate
}, cancellationToken).ConfigureAwait(false);
await item.RefreshMetadata(
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = !isNew && forceUpdate
},
cancellationToken).ConfigureAwait(false);
return item;
}
private static string GetOfficialRating(ChannelParentalRating rating)
{
switch (rating)
return rating switch
{
case ChannelParentalRating.Adult:
return "XXX";
case ChannelParentalRating.UsR:
return "R";
case ChannelParentalRating.UsPG13:
return "PG-13";
case ChannelParentalRating.UsPG:
return "PG";
default:
return null;
}
ChannelParentalRating.Adult => "XXX",
ChannelParentalRating.UsR => "R",
ChannelParentalRating.UsPG13 => "PG-13",
ChannelParentalRating.UsPG => "PG",
_ => null
};
}
/// <summary>
/// Gets a channel with the provided Guid.
/// </summary>
/// <param name="id">The Guid.</param>
/// <returns>The corresponding channel.</returns>
public Channel GetChannel(Guid id)
{
return _libraryManager.GetItemById(id) as Channel;
}
/// <inheritdoc />
public Channel GetChannel(string id)
{
return _libraryManager.GetItemById(id) as Channel;
}
/// <inheritdoc />
public ChannelFeatures[] GetAllChannelFeatures()
{
return _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Channel).Name },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
return _libraryManager.GetItemIds(
new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Channel).Name },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
}
/// <inheritdoc />
public ChannelFeatures GetChannelFeatures(string id)
{
if (string.IsNullOrEmpty(id))
@@ -530,15 +566,27 @@ namespace Emby.Server.Implementations.Channels
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
}
/// <summary>
/// Checks whether the provided Guid supports external transfer.
/// </summary>
/// <param name="channelId">The Guid.</param>
/// <returns>Whether or not the provided Guid supports external transfer.</returns>
public bool SupportsExternalTransfer(Guid channelId)
{
//var channel = GetChannel(channelId);
var channelProvider = GetChannelProvider(channelId);
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
}
public ChannelFeatures GetChannelFeaturesDto(Channel channel,
/// <summary>
/// Gets the provided channel's supported features.
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="provider">The provider.</param>
/// <param name="features">The features.</param>
/// <returns>The supported features.</returns>
public ChannelFeatures GetChannelFeaturesDto(
Channel channel,
IChannel provider,
InternalChannelFeatures features)
{
@@ -567,9 +615,11 @@ namespace Emby.Server.Implementations.Channels
{
throw new ArgumentNullException(nameof(name));
}
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
}
/// <inheritdoc />
public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
{
var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
@@ -588,6 +638,7 @@ namespace Emby.Server.Implementations.Channels
return result;
}
/// <inheritdoc />
public async Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken)
{
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
@@ -614,7 +665,7 @@ namespace Emby.Server.Implementations.Channels
query.IsFolder = false;
// hack for trailers, figure out a better way later
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1;
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.Contains("Trailer", StringComparison.Ordinal);
if (sortByPremiereDate)
{
@@ -640,10 +691,12 @@ namespace Emby.Server.Implementations.Channels
{
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
var query = new InternalItemsQuery();
query.Parent = internalChannel;
query.EnableTotalRecordCount = false;
query.ChannelIds = new Guid[] { internalChannel.Id };
var query = new InternalItemsQuery
{
Parent = internalChannel,
EnableTotalRecordCount = false,
ChannelIds = new Guid[] { internalChannel.Id }
};
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
@@ -651,17 +704,20 @@ namespace Emby.Server.Implementations.Channels
{
if (item is Folder folder)
{
await GetChannelItemsInternal(new InternalItemsQuery
{
Parent = folder,
EnableTotalRecordCount = false,
ChannelIds = new Guid[] { internalChannel.Id }
}, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
await GetChannelItemsInternal(
new InternalItemsQuery
{
Parent = folder,
EnableTotalRecordCount = false,
ChannelIds = new Guid[] { internalChannel.Id }
},
new SimpleProgress<double>(),
cancellationToken).ConfigureAwait(false);
}
}
}
/// <inheritdoc />
public async Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken)
{
// Get the internal channel entity
@@ -672,7 +728,8 @@ namespace Emby.Server.Implementations.Channels
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
var itemsResult = await GetChannelItems(channelProvider,
var itemsResult = await GetChannelItems(
channelProvider,
query.User,
parentItem is Channel ? null : parentItem.ExternalId,
null,
@@ -684,13 +741,12 @@ namespace Emby.Server.Implementations.Channels
{
query.Parent = channel;
}
query.ChannelIds = Array.Empty<Guid>();
// Not yet sure why this is causing a problem
query.GroupByPresentationUniqueKey = false;
//_logger.LogDebug("GetChannelItemsInternal");
// null if came from cache
if (itemsResult != null)
{
@@ -707,12 +763,15 @@ namespace Emby.Server.Implementations.Channels
var deadItem = _libraryManager.GetItemById(deadId);
if (deadItem != null)
{
_libraryManager.DeleteItem(deadItem, new DeleteOptions
{
DeleteFileLocation = false,
DeleteFromExternalProvider = false
}, parentItem, false);
_libraryManager.DeleteItem(
deadItem,
new DeleteOptions
{
DeleteFileLocation = false,
DeleteFromExternalProvider = false
},
parentItem,
false);
}
}
}
@@ -720,6 +779,7 @@ namespace Emby.Server.Implementations.Channels
return _libraryManager.GetItemsResult(query);
}
/// <inheritdoc />
public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
{
var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
@@ -735,15 +795,15 @@ namespace Emby.Server.Implementations.Channels
return result;
}
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
private async Task<ChannelItemResult> GetChannelItems(
IChannel channel,
User user,
string externalFolderId,
ChannelItemSortField? sortField,
bool sortDescending,
CancellationToken cancellationToken)
{
var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
var userId = user?.Id.ToString("N", CultureInfo.InvariantCulture);
var cacheLength = CacheLength;
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
@@ -761,11 +821,9 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
}
catch (IOException)
{
}
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -785,16 +843,14 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
}
catch (IOException)
{
}
var query = new InternalChannelItemQuery
{
UserId = user == null ? Guid.Empty : user.Id,
UserId = user?.Id ?? Guid.Empty,
SortBy = sortField,
SortDescending = sortDescending,
FolderId = externalFolderId
@@ -833,7 +889,8 @@ namespace Emby.Server.Implementations.Channels
}
}
private string GetChannelDataCachePath(IChannel channel,
private string GetChannelDataCachePath(
IChannel channel,
string userId,
string externalFolderId,
ChannelItemSortField? sortField,
@@ -843,8 +900,7 @@ namespace Emby.Server.Implementations.Channels
var userCacheKey = string.Empty;
var hasCacheKey = channel as IHasCacheKey;
if (hasCacheKey != null)
if (channel is IHasCacheKey hasCacheKey)
{
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
}
@@ -858,6 +914,7 @@ namespace Emby.Server.Implementations.Channels
{
filename += "-sortField-" + sortField.Value;
}
if (sortDescending)
{
filename += "-sortDescending";
@@ -865,7 +922,8 @@ namespace Emby.Server.Implementations.Channels
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
return Path.Combine(_config.ApplicationPaths.CachePath,
return Path.Combine(
_config.ApplicationPaths.CachePath,
"channels",
channelId,
version,
@@ -919,60 +977,32 @@ namespace Emby.Server.Implementations.Channels
if (info.Type == ChannelItemType.Folder)
{
if (info.FolderType == ChannelFolderType.MusicAlbum)
item = info.FolderType switch
{
item = GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew);
}
else if (info.FolderType == ChannelFolderType.MusicArtist)
{
item = GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew);
}
else if (info.FolderType == ChannelFolderType.PhotoAlbum)
{
item = GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew);
}
else if (info.FolderType == ChannelFolderType.Series)
{
item = GetItemById<Series>(info.Id, channelProvider.Name, out isNew);
}
else if (info.FolderType == ChannelFolderType.Season)
{
item = GetItemById<Season>(info.Id, channelProvider.Name, out isNew);
}
else
{
item = GetItemById<Folder>(info.Id, channelProvider.Name, out isNew);
}
ChannelFolderType.MusicAlbum => GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew),
ChannelFolderType.MusicArtist => GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew),
ChannelFolderType.PhotoAlbum => GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew),
ChannelFolderType.Series => GetItemById<Series>(info.Id, channelProvider.Name, out isNew),
ChannelFolderType.Season => GetItemById<Season>(info.Id, channelProvider.Name, out isNew),
_ => GetItemById<Folder>(info.Id, channelProvider.Name, out isNew)
};
}
else if (info.MediaType == ChannelMediaType.Audio)
{
if (info.ContentType == ChannelMediaContentType.Podcast)
{
item = GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew);
}
else
{
item = GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
}
item = info.ContentType == ChannelMediaContentType.Podcast
? GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew)
: GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
}
else
{
if (info.ContentType == ChannelMediaContentType.Episode)
item = info.ContentType switch
{
item = GetItemById<Episode>(info.Id, channelProvider.Name, out isNew);
}
else if (info.ContentType == ChannelMediaContentType.Movie)
{
item = GetItemById<Movie>(info.Id, channelProvider.Name, out isNew);
}
else if (info.ContentType == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer)
{
item = GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew);
}
else
{
item = GetItemById<Video>(info.Id, channelProvider.Name, out isNew);
}
ChannelMediaContentType.Episode => GetItemById<Episode>(info.Id, channelProvider.Name, out isNew),
ChannelMediaContentType.Movie => GetItemById<Movie>(info.Id, channelProvider.Name, out isNew),
var x when x == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer
=> GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew),
_ => GetItemById<Video>(info.Id, channelProvider.Name, out isNew)
};
}
var enableMediaProbe = channelProvider is ISupportsMediaProbe;
@@ -981,7 +1011,6 @@ namespace Emby.Server.Implementations.Channels
{
item.RunTimeTicks = null;
}
else if (isNew || !enableMediaProbe)
{
item.RunTimeTicks = info.RunTimeTicks;
@@ -1014,26 +1043,24 @@ namespace Emby.Server.Implementations.Channels
}
}
var hasArtists = item as IHasArtist;
if (hasArtists != null)
if (item is IHasArtist hasArtists)
{
hasArtists.Artists = info.Artists.ToArray();
}
var hasAlbumArtists = item as IHasAlbumArtist;
if (hasAlbumArtists != null)
if (item is IHasAlbumArtist hasAlbumArtists)
{
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
}
var trailer = item as Trailer;
if (trailer != null)
if (item is Trailer trailer)
{
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
{
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
forceUpdate = true;
}
trailer.TrailerTypes = info.TrailerTypes.ToArray();
}
@@ -1045,7 +1072,7 @@ namespace Emby.Server.Implementations.Channels
}
// was used for status
//if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
// if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
//{
// item.ExternalEtag = info.Etag;
// forceUpdate = true;
@@ -1057,6 +1084,7 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
}
item.ChannelId = internalChannelId;
if (!item.ParentId.Equals(parentFolderId))
@@ -1064,16 +1092,17 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
}
item.ParentId = parentFolderId;
var hasSeries = item as IHasSeries;
if (hasSeries != null)
if (item is IHasSeries hasSeries)
{
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
{
forceUpdate = true;
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
}
hasSeries.SeriesName = info.SeriesName;
}
@@ -1082,24 +1111,23 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
}
item.ExternalId = info.Id;
var channelAudioItem = item as Audio;
if (channelAudioItem != null)
if (item is Audio channelAudioItem)
{
channelAudioItem.ExtraType = info.ExtraType;
var mediaSource = info.MediaSources.FirstOrDefault();
item.Path = mediaSource == null ? null : mediaSource.Path;
item.Path = mediaSource?.Path;
}
var channelVideoItem = item as Video;
if (channelVideoItem != null)
if (item is Video channelVideoItem)
{
channelVideoItem.ExtraType = info.ExtraType;
var mediaSource = info.MediaSources.FirstOrDefault();
item.Path = mediaSource == null ? null : mediaSource.Path;
item.Path = mediaSource?.Path;
}
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
@@ -1156,7 +1184,7 @@ namespace Emby.Server.Implementations.Channels
}
}
if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
if (isNew || forceUpdate || item.DateLastRefreshed == default)
{
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System;
using System.Linq;
using System.Threading;
@@ -11,21 +9,34 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
{
/// <summary>
/// A task to remove all non-installed channels from the database.
/// </summary>
public class ChannelPostScanTask
{
private readonly IChannelManager _channelManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
/// <summary>
/// Initializes a new instance of the <see cref="ChannelPostScanTask"/> class.
/// </summary>
/// <param name="channelManager">The channel manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="libraryManager">The library manager.</param>
public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
{
_channelManager = channelManager;
_userManager = userManager;
_logger = logger;
_libraryManager = libraryManager;
}
/// <summary>
/// Runs this task.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The completed task.</returns>
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
CleanDatabase(cancellationToken);

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Threading;
@@ -7,29 +5,36 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.Channels
{
/// <summary>
/// The "Refresh Channels" scheduled task.
/// </summary>
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly IChannelManager _channelManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILogger<RefreshChannelsScheduledTask> _logger;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshChannelsScheduledTask"/> class.
/// </summary>
/// <param name="channelManager">The channel manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="localization">The localization manager.</param>
public RefreshChannelsScheduledTask(
IChannelManager channelManager,
IUserManager userManager,
ILogger<RefreshChannelsScheduledTask> logger,
ILibraryManager libraryManager,
ILocalizationManager localization)
{
_channelManager = channelManager;
_userManager = userManager;
_logger = logger;
_libraryManager = libraryManager;
_localization = localization;
@@ -63,7 +68,7 @@ namespace Emby.Server.Implementations.Channels
await manager.RefreshChannels(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
await new ChannelPostScanTask(_channelManager, _userManager, _logger, _libraryManager).Run(progress, cancellationToken)
await new ChannelPostScanTask(_channelManager, _logger, _libraryManager).Run(progress, cancellationToken)
.ConfigureAwait(false);
}
@@ -72,7 +77,6 @@ namespace Emby.Server.Implementations.Channels
{
return new[]
{
// Every so often
new TaskTriggerInfo
{

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
@@ -15,8 +13,18 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Collections
{
/// <summary>
/// A collection image provider.
/// </summary>
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
{
/// <summary>
/// Initializes a new instance of the <see cref="CollectionImageProvider"/> class.
/// </summary>
/// <param name="fileSystem">The filesystem.</param>
/// <param name="providerManager">The provider manager.</param>
/// <param name="applicationPaths">The application paths.</param>
/// <param name="imageProcessor">The image processor.</param>
public CollectionImageProvider(
IFileSystem fileSystem,
IProviderManager providerManager,
@@ -26,6 +34,7 @@ namespace Emby.Server.Implementations.Collections
{
}
/// <inheritdoc />
protected override bool Supports(BaseItem item)
{
// Right now this is the only way to prevent this image from getting created ahead of internet image providers
@@ -37,6 +46,7 @@ namespace Emby.Server.Implementations.Collections
return base.Supports(item);
}
/// <inheritdoc />
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (BoxSet)item;
@@ -48,13 +58,10 @@ namespace Emby.Server.Implementations.Collections
var episode = subItem as Episode;
if (episode != null)
var series = episode?.Series;
if (series != null && series.HasImage(ImageType.Primary))
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))
{
return series;
}
return series;
}
if (subItem.HasImage(ImageType.Primary))
@@ -80,6 +87,7 @@ namespace Emby.Server.Implementations.Collections
.ToList();
}
/// <inheritdoc />
protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -7,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration;
@@ -23,16 +22,29 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Collections
{
/// <summary>
/// The collection manager.
/// </summary>
public class CollectionManager : ICollectionManager
{
private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _iLibraryMonitor;
private readonly ILogger _logger;
private readonly ILogger<CollectionManager> _logger;
private readonly IProviderManager _providerManager;
private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths;
/// <summary>
/// Initializes a new instance of the <see cref="CollectionManager"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="appPaths">The application paths.</param>
/// <param name="localizationManager">The localization manager.</param>
/// <param name="fileSystem">The filesystem.</param>
/// <param name="iLibraryMonitor">The library monitor.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="providerManager">The provider manager.</param>
public CollectionManager(
ILibraryManager libraryManager,
IApplicationPaths appPaths,
@@ -45,14 +57,19 @@ namespace Emby.Server.Implementations.Collections
_libraryManager = libraryManager;
_fileSystem = fileSystem;
_iLibraryMonitor = iLibraryMonitor;
_logger = loggerFactory.CreateLogger(nameof(CollectionManager));
_logger = loggerFactory.CreateLogger<CollectionManager>();
_providerManager = providerManager;
_localizationManager = localizationManager;
_appPaths = appPaths;
}
/// <inheritdoc />
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
/// <inheritdoc />
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
/// <inheritdoc />
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
private IEnumerable<Folder> FindFolders(string path)
@@ -109,11 +126,12 @@ namespace Emby.Server.Implementations.Collections
{
var folder = GetCollectionsFolder(false).Result;
return folder == null ?
new List<BoxSet>() :
folder.GetChildren(user, true).OfType<BoxSet>();
return folder == null
? Enumerable.Empty<BoxSet>()
: folder.GetChildren(user, true).OfType<BoxSet>();
}
/// <inheritdoc />
public BoxSet CreateCollection(CollectionCreationOptions options)
{
var name = options.Name;
@@ -178,11 +196,13 @@ namespace Emby.Server.Implementations.Collections
}
}
/// <inheritdoc />
public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
{
AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
}
/// <inheritdoc />
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
{
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
@@ -191,7 +211,6 @@ namespace Emby.Server.Implementations.Collections
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
{
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
if (collection == null)
{
throw new ArgumentException("No collection exists with the supplied Id");
@@ -246,11 +265,13 @@ namespace Emby.Server.Implementations.Collections
}
}
/// <inheritdoc />
public void RemoveFromCollection(Guid collectionId, IEnumerable<string> itemIds)
{
RemoveFromCollection(collectionId, itemIds.Select(i => new Guid(i)));
}
/// <inheritdoc />
public void RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
{
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
@@ -289,10 +310,13 @@ namespace Emby.Server.Implementations.Collections
}
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
_providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = true
}, RefreshPriority.High);
_providerManager.QueueRefresh(
collection.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ForceSave = true
},
RefreshPriority.High);
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
{
@@ -301,6 +325,7 @@ namespace Emby.Server.Implementations.Collections
});
}
/// <inheritdoc />
public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
{
var results = new Dictionary<Guid, BaseItem>();
@@ -309,9 +334,7 @@ namespace Emby.Server.Implementations.Collections
foreach (var item in items)
{
var grouping = item as ISupportsBoxSetGrouping;
if (grouping == null)
if (!(item is ISupportsBoxSetGrouping))
{
results[item.Id] = item;
}
@@ -341,12 +364,21 @@ namespace Emby.Server.Implementations.Collections
}
}
/// <summary>
/// The collection manager entry point.
/// </summary>
public sealed class CollectionManagerEntryPoint : IServerEntryPoint
{
private readonly CollectionManager _collectionManager;
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly ILogger<CollectionManagerEntryPoint> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.
/// </summary>
/// <param name="collectionManager">The collection manager.</param>
/// <param name="config">The server configuration manager.</param>
/// <param name="logger">The logger.</param>
public CollectionManagerEntryPoint(
ICollectionManager collectionManager,
IServerConfigurationManager config,

View File

@@ -67,23 +67,22 @@ namespace Emby.Server.Implementations.Configuration
/// <summary>
/// Updates the metadata path.
/// </summary>
/// <exception cref="UnauthorizedAccessException">If the directory does not exist, and the caller does not have the required permission to create it.</exception>
/// <exception cref="NotSupportedException">If there is a custom path transcoding path specified, but it is invalid.</exception>
/// <exception cref="IOException">If the directory does not exist, and it also could not be created.</exception>
private void UpdateMetadataPath()
{
if (string.IsNullOrWhiteSpace(Configuration.MetadataPath))
{
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
}
else
{
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Configuration.MetadataPath;
}
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = string.IsNullOrWhiteSpace(Configuration.MetadataPath)
? ApplicationPaths.DefaultInternalMetadataPath
: Configuration.MetadataPath;
Directory.CreateDirectory(ApplicationPaths.InternalMetadataPath);
}
/// <summary>
/// Replaces the configuration.
/// </summary>
/// <param name="newConfiguration">The new configuration.</param>
/// <exception cref="DirectoryNotFoundException"></exception>
/// <exception cref="DirectoryNotFoundException">If the configuration path doesn't exist.</exception>
public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
{
var newConfig = (ServerConfiguration)newConfiguration;
@@ -91,7 +90,7 @@ namespace Emby.Server.Implementations.Configuration
ValidateMetadataPath(newConfig);
ValidateSslCertificate(newConfig);
ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration> { Argument = newConfig });
ConfigurationUpdating?.Invoke(this, new GenericEventArgs<ServerConfiguration>(newConfig));
base.ReplaceConfiguration(newConfiguration);
}
@@ -194,12 +193,6 @@ namespace Emby.Server.Implementations.Configuration
changed = true;
}
if (!config.CameraUploadUpgraded)
{
config.CameraUploadUpgraded = true;
changed = true;
}
if (!config.CollectionsUpgraded)
{
config.CollectionsUpgraded = true;

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.Updates;
using MediaBrowser.Providers.Music;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Emby.Server.Implementations
@@ -18,7 +17,7 @@ namespace Emby.Server.Implementations
{
{ HostWebClientKey, bool.TrueString },
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" },
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.TrueString }

View File

@@ -1,3 +1,5 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
@@ -31,7 +33,7 @@ namespace Emby.Server.Implementations.Cryptography
private RandomNumberGenerator _randomNumberGenerator;
private bool _disposed = false;
private bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="CryptographyProvider"/> class.
@@ -56,15 +58,13 @@ namespace Emby.Server.Implementations.Cryptography
{
// downgrading for now as we need this library to be dotnetstandard compliant
// with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
if (method == DefaultHashMethod)
if (method != DefaultHashMethod)
{
using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
{
return r.GetBytes(32);
}
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
}
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
using var r = new Rfc2898DeriveBytes(bytes, salt, iterations);
return r.GetBytes(32);
}
/// <inheritdoc />
@@ -74,25 +74,22 @@ namespace Emby.Server.Implementations.Cryptography
{
return PBKDF2(hashMethod, bytes, salt, DefaultIterations);
}
else if (_supportedHashMethods.Contains(hashMethod))
if (!_supportedHashMethods.Contains(hashMethod))
{
using (var h = HashAlgorithm.Create(hashMethod))
{
if (salt.Length == 0)
{
return h.ComputeHash(bytes);
}
else
{
byte[] salted = new byte[bytes.Length + salt.Length];
Array.Copy(bytes, salted, bytes.Length);
Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
return h.ComputeHash(salted);
}
}
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
}
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
using var h = HashAlgorithm.Create(hashMethod);
if (salt.Length == 0)
{
return h.ComputeHash(bytes);
}
byte[] salted = new byte[bytes.Length + salt.Length];
Array.Copy(bytes, salted, bytes.Length);
Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
return h.ComputeHash(salted);
}
/// <inheritdoc />
@@ -134,8 +131,6 @@ namespace Emby.Server.Implementations.Cryptography
_randomNumberGenerator.Dispose();
}
_randomNumberGenerator = null;
_disposed = true;
}
}

View File

@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Data
/// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
protected BaseSqliteRepository(ILogger logger)
protected BaseSqliteRepository(ILogger<BaseSqliteRepository> logger)
{
Logger = logger;
}
@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Data
/// Gets the logger.
/// </summary>
/// <value>The logger.</value>
protected ILogger Logger { get; }
protected ILogger<BaseSqliteRepository> Logger { get; }
/// <summary>
/// Gets the default connection flags.
@@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.Data
}
return false;
}, ReadTransactionMode);
}
@@ -248,12 +247,12 @@ namespace Emby.Server.Implementations.Data
public enum SynchronousMode
{
/// <summary>
/// SQLite continues without syncing as soon as it has handed data off to the operating system
/// SQLite continues without syncing as soon as it has handed data off to the operating system.
/// </summary>
Off = 0,
/// <summary>
/// SQLite database engine will still sync at the most critical moments
/// SQLite database engine will still sync at the most critical moments.
/// </summary>
Normal = 1,

View File

@@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Data
public class CleanDatabaseScheduledTask : ILibraryPostScanTask
{
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly ILogger<CleanDatabaseScheduledTask> _logger;
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
{
@@ -51,7 +51,6 @@ namespace Emby.Server.Implementations.Data
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
});
}

View File

@@ -1,4 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
/// Opens the connection to the database
/// Opens the connection to the database.
/// </summary>
/// <returns>Task.</returns>
private void InitializeInternal()
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
/// Save the display preferences associated with an item in the repo
/// 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>
@@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
/// Save all display preferences associated with a user in the repo
/// 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>

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -134,10 +135,12 @@ namespace Emby.Server.Implementations.Data
{
throw new ArgumentNullException(nameof(userData));
}
if (internalUserId <= 0)
{
throw new ArgumentNullException(nameof(internalUserId));
}
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException(nameof(key));
@@ -152,6 +155,7 @@ namespace Emby.Server.Implementations.Data
{
throw new ArgumentNullException(nameof(userData));
}
if (internalUserId <= 0)
{
throw new ArgumentNullException(nameof(internalUserId));
@@ -234,7 +238,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
/// Persist all user data for the specified user
/// Persist all user data for the specified user.
/// </summary>
private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken)
{
@@ -308,7 +312,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
/// Return all user-data associated with the given user
/// Return all user-data associated with the given user.
/// </summary>
/// <param name="internalUserId"></param>
/// <returns></returns>
@@ -338,7 +342,7 @@ namespace Emby.Server.Implementations.Data
}
/// <summary>
/// Read a row from the specified reader into the provided userData object
/// Read a row from the specified reader into the provided userData object.
/// </summary>
/// <param name="reader"></param>
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
@@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Data
var userData = new UserItemData();
userData.Key = reader[0].ToString();
//userData.UserId = reader[1].ReadGuidFromBlob();
// userData.UserId = reader[1].ReadGuidFromBlob();
if (reader[2].SQLiteType != SQLiteType.Null)
{
@@ -375,5 +379,15 @@ namespace Emby.Server.Implementations.Data
return userData;
}
/// <inheritdoc/>
/// <remarks>
/// There is nothing to dispose here since <see cref="BaseSqliteRepository.WriteLock"/> and
/// <see cref="BaseSqliteRepository.WriteConnection"/> are managed by <see cref="SqliteItemRepository"/>.
/// See <see cref="Initialize(IUserManager, SemaphoreSlim, SQLiteDatabaseConnection)"/>.
/// </remarks>
protected override void Dispose(bool dispose)
{
}
}
}

View File

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

View File

@@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Devices
public class DeviceId
{
private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger;
private readonly ILogger<DeviceId> _logger;
private readonly object _syncLock = new object();
@@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Devices
public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
{
_appPaths = appPaths;
_logger = loggerFactory.CreateLogger("SystemId");
_logger = loggerFactory.CreateLogger<DeviceId>();
}
public string Value => _id ?? (_id = GetDeviceId());

View File

@@ -5,27 +5,18 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.Users;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Devices
{
@@ -33,42 +24,26 @@ namespace Emby.Server.Implementations.Devices
{
private readonly IJsonSerializer _json;
private readonly IUserManager _userManager;
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
private readonly IAuthenticationRepository _authRepo;
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
private readonly object _capabilitiesSyncLock = new object();
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
private readonly object _cameraUploadSyncLock = new object();
private readonly object _capabilitiesSyncLock = new object();
public DeviceManager(
IAuthenticationRepository authRepo,
IJsonSerializer json,
ILibraryManager libraryManager,
ILocalizationManager localizationManager,
IUserManager userManager,
IFileSystem fileSystem,
ILibraryMonitor libraryMonitor,
IServerConfigurationManager config)
{
_json = json;
_userManager = userManager;
_fileSystem = fileSystem;
_libraryMonitor = libraryMonitor;
_config = config;
_libraryManager = libraryManager;
_localizationManager = localizationManager;
_authRepo = authRepo;
_capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
}
private Dictionary<string, ClientCapabilities> _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
{
var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
@@ -86,13 +61,7 @@ namespace Emby.Server.Implementations.Devices
{
_authRepo.UpdateDeviceOptions(deviceId, options);
if (DeviceOptionsUpdated != null)
{
DeviceOptionsUpdated(this, new GenericEventArgs<Tuple<string, DeviceOptions>>()
{
Argument = new Tuple<string, DeviceOptions>(deviceId, options)
});
}
DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options)));
}
public DeviceOptions GetDeviceOptions(string deviceId)
@@ -143,7 +112,7 @@ namespace Emby.Server.Implementations.Devices
{
IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
{
//UserId = query.UserId
// UserId = query.UserId
HasUser = true
}).Items;
@@ -194,184 +163,24 @@ namespace Emby.Server.Implementations.Devices
return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture));
}
public ContentUploadHistory GetCameraUploadHistory(string deviceId)
{
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
lock (_cameraUploadSyncLock)
{
try
{
return _json.DeserializeFromFile<ContentUploadHistory>(path);
}
catch (IOException)
{
return new ContentUploadHistory
{
DeviceId = deviceId
};
}
}
}
public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file)
{
var device = GetDevice(deviceId, false);
var uploadPathInfo = GetUploadPath(device);
var path = uploadPathInfo.Item1;
if (!string.IsNullOrWhiteSpace(file.Album))
{
path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album));
}
path = Path.Combine(path, file.Name);
path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg");
Directory.CreateDirectory(Path.GetDirectoryName(path));
await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false);
_libraryMonitor.ReportFileSystemChangeBeginning(path);
try
{
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
await stream.CopyToAsync(fs).ConfigureAwait(false);
}
AddCameraUpload(deviceId, file);
}
finally
{
_libraryMonitor.ReportFileSystemChangeComplete(path, true);
}
if (CameraImageUploaded != null)
{
CameraImageUploaded?.Invoke(this, new GenericEventArgs<CameraImageUploadInfo>
{
Argument = new CameraImageUploadInfo
{
Device = device,
FileInfo = file
}
});
}
}
private void AddCameraUpload(string deviceId, LocalFileInfo file)
{
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
lock (_cameraUploadSyncLock)
{
ContentUploadHistory history;
try
{
history = _json.DeserializeFromFile<ContentUploadHistory>(path);
}
catch (IOException)
{
history = new ContentUploadHistory
{
DeviceId = deviceId
};
}
history.DeviceId = deviceId;
var list = history.FilesUploaded.ToList();
list.Add(file);
history.FilesUploaded = list.ToArray();
_json.SerializeToFile(history, path);
}
}
internal Task EnsureLibraryFolder(string path, string name)
{
var existingFolders = _libraryManager
.RootFolder
.Children
.OfType<Folder>()
.Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path))
.ToList();
if (existingFolders.Count > 0)
{
return Task.CompletedTask;
}
Directory.CreateDirectory(path);
var libraryOptions = new LibraryOptions
{
PathInfos = new[] { new MediaPathInfo { Path = path } },
EnablePhotos = true,
EnableRealtimeMonitor = false,
SaveLocalMetadata = true
};
if (string.IsNullOrWhiteSpace(name))
{
name = _localizationManager.GetLocalizedString("HeaderCameraUploads");
}
return _libraryManager.AddVirtualFolder(name, CollectionType.HomeVideos, libraryOptions, true);
}
private Tuple<string, string, string> GetUploadPath(DeviceInfo device)
{
var config = _config.GetUploadOptions();
var path = config.CameraUploadPath;
if (string.IsNullOrWhiteSpace(path))
{
path = DefaultCameraUploadsPath;
}
var topLibraryPath = path;
if (config.EnableCameraUploadSubfolders)
{
path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name));
}
return new Tuple<string, string, string>(path, topLibraryPath, null);
}
internal string GetUploadsPath()
{
var config = _config.GetUploadOptions();
var path = config.CameraUploadPath;
if (string.IsNullOrWhiteSpace(path))
{
path = DefaultCameraUploadsPath;
}
return path;
}
private string DefaultCameraUploadsPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads");
public bool CanAccessDevice(User user, string deviceId)
{
if (user == null)
{
throw new ArgumentException("user not found");
}
if (string.IsNullOrEmpty(deviceId))
{
throw new ArgumentNullException(nameof(deviceId));
}
if (!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,118 +192,5 @@ 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
{
private readonly DeviceManager _deviceManager;
private readonly IServerConfigurationManager _config;
private ILogger _logger;
public DeviceManagerEntryPoint(
IDeviceManager deviceManager,
IServerConfigurationManager config,
ILogger<DeviceManagerEntryPoint> logger)
{
_deviceManager = (DeviceManager)deviceManager;
_config = config;
_logger = logger;
}
public async Task RunAsync()
{
if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted)
{
var path = _deviceManager.GetUploadsPath();
if (Directory.Exists(path))
{
try
{
await _deviceManager.EnsureLibraryFolder(path, null).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating camera uploads library");
}
_config.Configuration.CameraUploadUpgraded = true;
_config.SaveConfiguration();
}
}
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~DeviceManagerEntryPoint() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
public class DevicesConfigStore : IConfigurationFactory
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new ConfigurationStore[]
{
new ConfigurationStore
{
Key = "devices",
ConfigurationType = typeof(DevicesOptions)
}
};
}
}
public static class UploadConfigExtension
{
public static DevicesOptions GetUploadOptions(this IConfigurationManager config)
{
return config.GetConfiguration<DevicesOptions>("devices");
}
}
}

View File

@@ -6,14 +6,14 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
@@ -24,12 +24,20 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Person = MediaBrowser.Controller.Entities.Person;
using Photo = MediaBrowser.Controller.Entities.Photo;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Server.Implementations.Dto
{
public class DtoService : IDtoService
{
private readonly ILogger _logger;
private readonly ILogger<DtoService> _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserDataManager _userDataRepository;
private readonly IItemRepository _itemRepo;
@@ -38,21 +46,23 @@ namespace Emby.Server.Implementations.Dto
private readonly IProviderManager _providerManager;
private readonly IApplicationHost _appHost;
private readonly Func<IMediaSourceManager> _mediaSourceManager;
private readonly Func<ILiveTvManager> _livetvManager;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
public DtoService(
ILoggerFactory loggerFactory,
ILogger<DtoService> logger,
ILibraryManager libraryManager,
IUserDataManager userDataRepository,
IItemRepository itemRepo,
IImageProcessor imageProcessor,
IProviderManager providerManager,
IApplicationHost appHost,
Func<IMediaSourceManager> mediaSourceManager,
Func<ILiveTvManager> livetvManager)
IMediaSourceManager mediaSourceManager,
Lazy<ILiveTvManager> livetvManagerFactory)
{
_logger = loggerFactory.CreateLogger(nameof(DtoService));
_logger = logger;
_libraryManager = libraryManager;
_userDataRepository = userDataRepository;
_itemRepo = itemRepo;
@@ -60,11 +70,11 @@ namespace Emby.Server.Implementations.Dto
_providerManager = providerManager;
_appHost = appHost;
_mediaSourceManager = mediaSourceManager;
_livetvManager = livetvManager;
_livetvManagerFactory = livetvManagerFactory;
}
/// <summary>
/// Converts a BaseItem to a DTOBaseItem
/// Converts a BaseItem to a DTOBaseItem.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fields">The fields.</param>
@@ -125,12 +135,12 @@ namespace Emby.Server.Implementations.Dto
if (programTuples.Count > 0)
{
_livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
}
if (channelTuples.Count > 0)
{
_livetvManager().AddChannelInfo(channelTuples, options, user);
LivetvManager.AddChannelInfo(channelTuples, options, user);
}
return returnItems;
@@ -142,12 +152,12 @@ namespace Emby.Server.Implementations.Dto
if (item is LiveTvChannel tvChannel)
{
var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) };
_livetvManager().AddChannelInfo(list, options, user);
LivetvManager.AddChannelInfo(list, options, user);
}
else if (item is LiveTvProgram)
{
var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) };
var task = _livetvManager().AddInfoToProgramDto(list, options.Fields, user);
var task = LivetvManager.AddInfoToProgramDto(list, options.Fields, user);
Task.WaitAll(task);
}
@@ -223,7 +233,7 @@ namespace Emby.Server.Implementations.Dto
if (item is IHasMediaSources
&& options.ContainsField(ItemFields.MediaSources))
{
dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(item, true, user).ToArray();
dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
NormalizeMediaSourceContainers(dto);
}
@@ -254,7 +264,7 @@ namespace Emby.Server.Implementations.Dto
dto.Etag = item.GetEtag(user);
}
var liveTvManager = _livetvManager();
var liveTvManager = LivetvManager;
var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
if (activeRecording != null)
{
@@ -267,6 +277,7 @@ namespace Emby.Server.Implementations.Dto
dto.EpisodeTitle = dto.Name;
dto.Name = dto.SeriesName;
}
liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
}
@@ -282,6 +293,7 @@ namespace Emby.Server.Implementations.Dto
{
continue;
}
var containers = container.Split(new[] { ',' });
if (containers.Length < 2)
{
@@ -382,7 +394,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.ChildCount))
{
dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user);
dto.ChildCount ??= GetChildCount(folder, user);
}
}
@@ -396,7 +408,6 @@ namespace Emby.Server.Implementations.Dto
dto.DateLastMediaAdded = folder.DateLastMediaAdded;
}
}
else
{
if (options.EnableUserData)
@@ -412,7 +423,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;
@@ -433,7 +444,7 @@ namespace Emby.Server.Implementations.Dto
}
/// <summary>
/// Gets client-side Id of a server-side BaseItem
/// Gets client-side Id of a server-side BaseItem.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
@@ -447,6 +458,7 @@ namespace Emby.Server.Implementations.Dto
{
dto.SeriesName = item.SeriesName;
}
private static void SetPhotoProperties(BaseItemDto dto, Photo item)
{
dto.CameraMake = item.CameraMake;
@@ -528,7 +540,7 @@ namespace Emby.Server.Implementations.Dto
}
/// <summary>
/// Attaches People DTO's to a DTOBaseItem
/// Attaches People DTO's to a DTOBaseItem.
/// </summary>
/// <param name="dto">The dto.</param>
/// <param name="item">The item.</param>
@@ -545,22 +557,27 @@ namespace Emby.Server.Implementations.Dto
{
return 0;
}
if (i.IsType(PersonType.GuestStar))
{
return 1;
}
if (i.IsType(PersonType.Director))
{
return 2;
}
if (i.IsType(PersonType.Writer))
{
return 3;
}
if (i.IsType(PersonType.Producer))
{
return 4;
}
if (i.IsType(PersonType.Composer))
{
return 4;
@@ -584,7 +601,6 @@ namespace Emby.Server.Implementations.Dto
_logger.LogError(ex, "Error getting person {Name}", c);
return null;
}
}).Where(i => i != null)
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.Select(x => x.First())
@@ -603,8 +619,9 @@ namespace Emby.Server.Implementations.Dto
if (dictionary.TryGetValue(person.Name, out Person entity))
{
baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
baseItemPerson.ImageBlurHashes = dto.ImageBlurHashes;
list.Add(baseItemPerson);
}
}
@@ -652,8 +669,72 @@ namespace Emby.Server.Implementations.Dto
return _libraryManager.GetGenreId(name);
}
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
{
var image = item.GetImageInfo(imageType, imageIndex);
if (image != null)
{
return GetTagAndFillBlurhash(dto, item, image);
}
return null;
}
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
{
var tag = GetImageCacheTag(item, image);
if (!string.IsNullOrEmpty(image.BlurHash))
{
if (dto.ImageBlurHashes == null)
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
if (!dto.ImageBlurHashes.ContainsKey(image.Type))
{
dto.ImageBlurHashes[image.Type] = new Dictionary<string, string>();
}
dto.ImageBlurHashes[image.Type][tag] = image.BlurHash;
}
return tag;
}
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
{
return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
}
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInfo> images)
{
var tags = GetImageTags(item, images);
var hashes = new Dictionary<string, string>();
for (int i = 0; i < images.Count; i++)
{
var img = images[i];
if (!string.IsNullOrEmpty(img.BlurHash))
{
var tag = tags[i];
hashes[tag] = img.BlurHash;
}
}
if (hashes.Count > 0)
{
if (dto.ImageBlurHashes == null)
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
dto.ImageBlurHashes[imageType] = hashes;
}
return tags;
}
/// <summary>
/// Sets simple property values on a DTOBaseItem
/// Sets simple property values on a DTOBaseItem.
/// </summary>
/// <param name="dto">The dto.</param>
/// <param name="item">The item.</param>
@@ -672,8 +753,8 @@ namespace Emby.Server.Implementations.Dto
dto.LockData = item.IsLocked;
dto.ForcedSortName = item.ForcedSortName;
}
dto.Container = item.Container;
dto.Container = item.Container;
dto.EndDate = item.EndDate;
if (options.ContainsField(ItemFields.ExternalUrls))
@@ -692,10 +773,12 @@ namespace Emby.Server.Implementations.Dto
dto.AspectRatio = hasAspectRatio.AspectRatio;
}
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
if (backdropLimit > 0)
{
dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
}
if (options.ContainsField(ItemFields.ScreenshotImageTags))
@@ -703,7 +786,7 @@ namespace Emby.Server.Implementations.Dto
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
if (screenshotLimit > 0)
{
dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
dto.ScreenshotImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Screenshot, screenshotLimit);
}
}
@@ -719,12 +802,11 @@ namespace Emby.Server.Implementations.Dto
// Prevent implicitly captured closure
var currentItem = item;
foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type))
.ToList())
foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
{
if (options.GetImageLimit(image.Type) > 0)
{
var tag = GetImageCacheTag(item, image);
var tag = GetTagAndFillBlurhash(dto, item, image);
if (tag != null)
{
@@ -869,11 +951,10 @@ namespace Emby.Server.Implementations.Dto
if (albumParent != null)
{
dto.AlbumId = albumParent.Id;
dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
}
//if (options.ContainsField(ItemFields.MediaSourceCount))
// if (options.ContainsField(ItemFields.MediaSourceCount))
//{
// Songs always have one
//}
@@ -883,13 +964,13 @@ namespace Emby.Server.Implementations.Dto
{
dto.Artists = hasArtist.Artists;
//var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
// var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
//{
// EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
//});
//dto.ArtistItems = artistItems.Items
// dto.ArtistItems = artistItems.Items
// .Select(i =>
// {
// var artist = i.Item1;
@@ -902,7 +983,7 @@ namespace Emby.Server.Implementations.Dto
// .ToList();
// Include artists that are not in the database yet, e.g., just added via metadata editor
//var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
// var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
dto.ArtistItems = hasArtist.Artists
//.Except(foundArtists, new DistinctNameComparer())
.Select(i =>
@@ -927,7 +1008,6 @@ namespace Emby.Server.Implementations.Dto
}
return null;
}).Where(i => i != null).ToArray();
}
@@ -936,13 +1016,13 @@ namespace Emby.Server.Implementations.Dto
{
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
//var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
// var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
//{
// EnableTotalRecordCount = false,
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
//});
//dto.AlbumArtists = artistItems.Items
// dto.AlbumArtists = artistItems.Items
// .Select(i =>
// {
// var artist = i.Item1;
@@ -978,7 +1058,6 @@ namespace Emby.Server.Implementations.Dto
}
return null;
}).Where(i => i != null).ToArray();
}
@@ -1045,7 +1124,7 @@ namespace Emby.Server.Implementations.Dto
}
else
{
mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
}
dto.MediaStreams = mediaStreams;
@@ -1092,12 +1171,12 @@ namespace Emby.Server.Implementations.Dto
// this block will add the series poster for episodes without a poster
// TODO maybe remove the if statement entirely
//if (options.ContainsField(ItemFields.SeriesPrimaryImage))
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{
episodeSeries = episodeSeries ?? episode.Series;
if (episodeSeries != null)
{
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
}
}
@@ -1138,12 +1217,12 @@ namespace Emby.Server.Implementations.Dto
// this block will add the series poster for seasons without a poster
// TODO maybe remove the if statement entirely
//if (options.ContainsField(ItemFields.SeriesPrimaryImage))
// if (options.ContainsField(ItemFields.SeriesPrimaryImage))
{
series = series ?? season.Series;
if (series != null)
{
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
}
}
}
@@ -1273,9 +1352,10 @@ namespace Emby.Server.Implementations.Dto
if (image != null)
{
dto.ParentLogoItemId = GetDtoId(parent);
dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
}
}
if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null)
{
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
@@ -1283,9 +1363,10 @@ namespace Emby.Server.Implementations.Dto
if (image != null)
{
dto.ParentArtItemId = GetDtoId(parent);
dto.ParentArtImageTag = GetImageCacheTag(parent, image);
dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
}
}
if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
{
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
@@ -1293,9 +1374,10 @@ namespace Emby.Server.Implementations.Dto
if (image != null)
{
dto.ParentThumbItemId = GetDtoId(parent);
dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
}
}
if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0)))
{
var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
@@ -1303,7 +1385,7 @@ namespace Emby.Server.Implementations.Dto
if (images.Count > 0)
{
dto.ParentBackdropItemId = GetDtoId(parent);
dto.ParentBackdropImageTags = GetImageTags(parent, images);
dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
}
}

View File

@@ -1,4 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
@@ -19,7 +24,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="IPNetwork2" Version="2.4.0.126" />
<PackageReference Include="IPNetwork2" Version="2.5.211" />
<PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
@@ -29,14 +34,16 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
<PackageReference Include="Mono.Nat" Version="2.0.1" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
<PackageReference Include="sharpcompress" Version="0.25.0" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
<PackageReference Include="sharpcompress" Version="0.25.1" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.0.9" />
</ItemGroup>
<ItemGroup>
@@ -47,6 +54,7 @@
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers-->

View File

@@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.EntryPoints
public class ExternalPortForwarding : IServerEntryPoint
{
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly ILogger<ExternalPortForwarding> _logger;
private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery;
@@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.EntryPoints
.Append(config.PublicHttpsPort).Append(Separator)
.Append(_appHost.HttpPort).Append(Separator)
.Append(_appHost.HttpsPort).Append(Separator)
.Append(_appHost.EnableHttps).Append(Separator)
.Append(_appHost.ListenWithHttps).Append(Separator)
.Append(config.EnableRemoteAccess).Append(Separator)
.ToString();
}
@@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.EntryPoints
{
yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
if (_appHost.EnableHttps)
if (_appHost.ListenWithHttps)
{
yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
}

View File

@@ -6,6 +6,7 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -28,7 +29,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILogger<LibraryChangedNotifier> _logger;
/// <summary>
/// The library changed sync lock.
@@ -131,7 +132,6 @@ namespace Emby.Server.Implementations.EntryPoints
}
catch
{
}
}
}
@@ -302,7 +302,7 @@ namespace Emby.Server.Implementations.EntryPoints
.Select(x => x.First())
.ToList();
SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None);
SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None).GetAwaiter().GetResult();
if (LibraryUpdateTimer != null)
{
@@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.EntryPoints
/// <param name="foldersAddedTo">The folders added to.</param>
/// <param name="foldersRemovedFrom">The folders removed from.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private async void SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
private async Task SendChangeNotifications(List<BaseItem> itemsAdded, List<BaseItem> itemsUpdated, List<BaseItem> itemsRemoved, List<Folder> foldersAddedTo, List<Folder> foldersRemovedFrom, CancellationToken cancellationToken)
{
var userIds = _sessionManager.Sessions
.Select(i => i.UserId)

View File

@@ -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;
@@ -17,7 +18,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly ILiveTvManager _liveTvManager;
private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILogger<RecordingNotifier> _logger;
public RecordingNotifier(
ISessionManager sessionManager,
@@ -42,29 +43,29 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask;
}
private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("SeriesTimerCreated", e.Argument);
await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false);
}
private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("TimerCreated", e.Argument);
await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false);
}
private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("SeriesTimerCancelled", e.Argument);
await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false);
}
private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("TimerCancelled", e.Argument);
await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false);
}
private async void SendMessage(string name, TimerEventInfo info)
private async Task SendMessage(string name, TimerEventInfo info)
{
var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList();
var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList();
try
{

View File

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

View File

@@ -3,15 +3,16 @@ 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;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Updates;
namespace Emby.Server.Implementations.EntryPoints
{
@@ -67,10 +68,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;
@@ -85,29 +84,29 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask;
}
private void OnPackageInstalling(object sender, InstallationEventArgs e)
private async void OnPackageInstalling(object sender, InstallationInfo e)
{
SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo);
await SendMessageToAdminSessions("PackageInstalling", e).ConfigureAwait(false);
}
private void OnPackageInstallationCancelled(object sender, InstallationEventArgs e)
private async void OnPackageInstallationCancelled(object sender, InstallationInfo e)
{
SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo);
await SendMessageToAdminSessions("PackageInstallationCancelled", e).ConfigureAwait(false);
}
private void OnPackageInstallationCompleted(object sender, InstallationEventArgs e)
private async void OnPackageInstallationCompleted(object sender, InstallationInfo e)
{
SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo);
await SendMessageToAdminSessions("PackageInstallationCompleted", e).ConfigureAwait(false);
}
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
{
SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo);
await SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo).ConfigureAwait(false);
}
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
{
SendMessageToAdminSessions("ScheduledTaskEnded", e.Result);
await SendMessageToAdminSessions("ScheduledTaskEnded", e.Result).ConfigureAwait(false);
}
/// <summary>
@@ -115,9 +114,9 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
private async void OnPluginUninstalled(object sender, IPlugin e)
{
SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo());
await SendMessageToAdminSessions("PluginUninstalled", e).ConfigureAwait(false);
}
/// <summary>
@@ -125,9 +124,9 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
private void OnHasPendingRestartChanged(object sender, EventArgs e)
private async void OnHasPendingRestartChanged(object sender, EventArgs e)
{
_sessionManager.SendRestartRequiredNotification(CancellationToken.None);
await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
@@ -135,11 +134,11 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
private void OnUserUpdated(object sender, GenericEventArgs<User> e)
private async void OnUserUpdated(object sender, GenericEventArgs<User> e)
{
var dto = _userManager.GetUserDto(e.Argument);
SendMessageToUserSession(e.Argument, "UserUpdated", dto);
await SendMessageToUserSession(e.Argument, "UserUpdated", dto).ConfigureAwait(false);
}
/// <summary>
@@ -147,26 +146,12 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
{
SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture));
await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
{
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)
private async Task SendMessageToAdminSessions<T>(string name, T data)
{
try
{
@@ -174,11 +159,10 @@ namespace Emby.Server.Implementations.EntryPoints
}
catch (Exception)
{
}
}
private async void SendMessageToUserSession<T>(User user, string name, T data)
private async Task SendMessageToUserSession<T>(User user, string name, T data)
{
try
{
@@ -190,7 +174,6 @@ namespace Emby.Server.Implementations.EntryPoints
}
catch (Exception)
{
}
}
@@ -209,10 +192,8 @@ 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;

View File

@@ -16,46 +16,63 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IServerApplicationHost _appHost;
private readonly IConfiguration _appConfig;
private readonly IServerConfigurationManager _config;
private readonly IStartupOptions _startupOptions;
/// <summary>
/// Initializes a new instance of the <see cref="StartupWizard"/> class.
/// </summary>
/// <param name="appHost">The application host.</param>
/// <param name="appConfig">The application configuration.</param>
/// <param name="config">The configuration manager.</param>
public StartupWizard(IServerApplicationHost appHost, IConfiguration appConfig, IServerConfigurationManager config)
/// <param name="startupOptions">The application startup options.</param>
public StartupWizard(
IServerApplicationHost appHost,
IConfiguration appConfig,
IServerConfigurationManager config,
IStartupOptions startupOptions)
{
_appHost = appHost;
_appConfig = appConfig;
_config = config;
_startupOptions = startupOptions;
}
/// <inheritdoc />
public Task RunAsync()
{
Run();
return Task.CompletedTask;
}
private void Run()
{
if (!_appHost.CanLaunchWebBrowser)
{
return Task.CompletedTask;
return;
}
if (!_appConfig.HostWebClient())
// Always launch the startup wizard if possible when it has not been completed
if (!_config.Configuration.IsStartupWizardCompleted && _appConfig.HostWebClient())
{
BrowserLauncher.OpenSwaggerPage(_appHost);
BrowserLauncher.OpenWebApp(_appHost);
return;
}
else if (!_config.Configuration.IsStartupWizardCompleted)
// Do nothing if the web app is configured to not run automatically
if (!_config.Configuration.AutoRunWebApp || _startupOptions.NoAutoRunWebApp)
{
return;
}
// Launch the swagger page if the web client is not hosted, otherwise open the web client
if (_appConfig.HostWebClient())
{
BrowserLauncher.OpenWebApp(_appHost);
}
else if (_config.Configuration.AutoRunWebApp)
else
{
var options = ((ApplicationHost)_appHost).StartupOptions;
if (!options.NoAutoRunWebApp)
{
BrowserLauncher.OpenWebApp(_appHost);
}
BrowserLauncher.OpenSwaggerPage(_appHost);
}
return Task.CompletedTask;
}
/// <inheritdoc />

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Emby.Server.Implementations.Udp;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
@@ -20,8 +21,9 @@ namespace Emby.Server.Implementations.EntryPoints
/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly ILogger<UdpServerEntryPoint> _logger;
private readonly IServerApplicationHost _appHost;
private readonly IConfiguration _config;
/// <summary>
/// The UDP server.
@@ -35,19 +37,20 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
public UdpServerEntryPoint(
ILogger<UdpServerEntryPoint> logger,
IServerApplicationHost appHost)
IServerApplicationHost appHost,
IConfiguration configuration)
{
_logger = logger;
_appHost = appHost;
_config = configuration;
}
/// <inheritdoc />
public async Task RunAsync()
public Task RunAsync()
{
_udpServer = new UdpServer(_logger, _appHost);
_udpServer = new UdpServer(_logger, _appHost, _config);
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
return Task.CompletedTask;
}
/// <inheritdoc />

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
@@ -21,10 +22,10 @@ namespace Emby.Server.Implementations.HttpClientManager
/// </summary>
public class HttpClientManager : IHttpClient
{
private readonly ILogger _logger;
private readonly ILogger<HttpClientManager> _logger;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly Func<string> _defaultUserAgentFn;
private readonly IApplicationHost _appHost;
/// <summary>
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
@@ -40,12 +41,12 @@ namespace Emby.Server.Implementations.HttpClientManager
IApplicationPaths appPaths,
ILogger<HttpClientManager> logger,
IFileSystem fileSystem,
Func<string> defaultUserAgentFn)
IApplicationHost appHost)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_fileSystem = fileSystem;
_appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths));
_defaultUserAgentFn = defaultUserAgentFn;
_appHost = appHost;
}
/// <summary>
@@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.HttpClientManager
if (options.EnableDefaultUserAgent
&& !request.Headers.TryGetValues(HeaderNames.UserAgent, out _))
{
request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn());
request.Headers.Add(HeaderNames.UserAgent, _appHost.ApplicationUserAgent);
}
switch (options.DecompressionMethod)
@@ -139,7 +140,7 @@ namespace Emby.Server.Implementations.HttpClientManager
=> SendAsync(options, HttpMethod.Get);
/// <summary>
/// Performs a GET request and returns the resulting stream
/// Performs a GET request and returns the resulting stream.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task{Stream}.</returns>

View File

@@ -32,12 +32,12 @@ namespace Emby.Server.Implementations.HttpServer
private readonly IFileSystem _fileSystem;
/// <summary>
/// The _options
/// The _options.
/// </summary>
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
/// <summary>
/// The _requested ranges
/// The _requested ranges.
/// </summary>
private List<KeyValuePair<long, long?>> _requestedRanges;

View File

@@ -6,14 +6,16 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Services;
using Emby.Server.Implementations.SocketSharp;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Events;
@@ -21,15 +23,17 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using ServiceStack.Text.Jsv;
namespace Emby.Server.Implementations.HttpServer
{
public class HttpListenerHost : IHttpServer, IDisposable
public class HttpListenerHost : IHttpServer
{
/// <summary>
/// The key for a setting that specifies the default redirect path
@@ -37,18 +41,18 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
private readonly ILogger _logger;
private readonly ILogger<HttpListenerHost> _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
private readonly IServerApplicationHost _appHost;
private readonly IJsonSerializer _jsonSerializer;
private readonly IXmlSerializer _xmlSerializer;
private readonly IHttpListener _socketListener;
private readonly Func<Type, Func<string, object>> _funcParseFn;
private readonly string _defaultRedirectPath;
private readonly string _baseUrlPrefix;
private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
private readonly IHostEnvironment _hostEnvironment;
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
@@ -62,10 +66,10 @@ namespace Emby.Server.Implementations.HttpServer
INetworkManager networkManager,
IJsonSerializer jsonSerializer,
IXmlSerializer xmlSerializer,
IHttpListener socketListener,
ILocalizationManager localizationManager,
ServiceController serviceController,
IHostEnvironment hostEnvironment)
IHostEnvironment hostEnvironment,
ILoggerFactory loggerFactory)
{
_appHost = applicationHost;
_logger = logger;
@@ -75,11 +79,9 @@ namespace Emby.Server.Implementations.HttpServer
_networkManager = networkManager;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
_socketListener = socketListener;
ServiceController = serviceController;
_socketListener.WebSocketConnected = OnWebSocketConnected;
_hostEnvironment = hostEnvironment;
_loggerFactory = loggerFactory;
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
@@ -171,38 +173,6 @@ namespace Emby.Server.Implementations.HttpServer
return attributes;
}
private void OnWebSocketConnected(WebSocketConnectEventArgs e)
{
if (_disposed)
{
return;
}
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
{
OnReceive = ProcessWebSocketMessageReceived,
Url = e.Url,
QueryString = e.QueryString
};
connection.Closed += OnConnectionClosed;
lock (_webSocketConnections)
{
_webSocketConnections.Add(connection);
}
WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection));
}
private void OnConnectionClosed(object sender, EventArgs e)
{
lock (_webSocketConnections)
{
_webSocketConnections.Remove((IWebSocketConnection)sender);
}
}
private static Exception GetActualException(Exception ex)
{
if (ex is AggregateException agg)
@@ -230,7 +200,8 @@ namespace Emby.Server.Implementations.HttpServer
switch (ex)
{
case ArgumentException _: return 400;
case SecurityException _: return 401;
case AuthenticationException _: return 401;
case SecurityException _: return 403;
case DirectoryNotFoundException _:
case FileNotFoundException _:
case ResourceNotFoundException _: return 404;
@@ -239,81 +210,46 @@ namespace Emby.Server.Implementations.HttpServer
}
}
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, string urlToLog)
private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog, bool ignoreStackTrace)
{
try
if (ignoreStackTrace)
{
ex = GetActualException(ex);
if (logExceptionStackTrace)
{
_logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
}
else
{
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
}
var httpRes = httpReq.Response;
if (httpRes.HasStarted)
{
return;
}
var statusCode = GetStatusCode(ex);
httpRes.StatusCode = statusCode;
var errContent = NormalizeExceptionMessage(ex.Message);
httpRes.ContentType = "text/plain";
httpRes.ContentLength = errContent.Length;
await httpRes.WriteAsync(errContent).ConfigureAwait(false);
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
}
catch (Exception errorEx)
else
{
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response). URL: {Url}", urlToLog);
_logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
}
var httpRes = httpReq.Response;
if (httpRes.HasStarted)
{
return;
}
httpRes.StatusCode = statusCode;
var errContent = _hostEnvironment.IsDevelopment()
? (NormalizeExceptionMessage(ex) ?? string.Empty)
: "Error processing request.";
httpRes.ContentType = "text/plain";
httpRes.ContentLength = errContent.Length;
await httpRes.WriteAsync(errContent).ConfigureAwait(false);
}
private string NormalizeExceptionMessage(string msg)
private string NormalizeExceptionMessage(Exception ex)
{
if (msg == null)
// Do not expose the exception message for AuthenticationException
if (ex is AuthenticationException)
{
return string.Empty;
return null;
}
// Strip any information we don't want to reveal
msg = msg.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase);
msg = msg.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
return msg;
}
/// <summary>
/// Shut down the Web Service
/// </summary>
public void Stop()
{
List<IWebSocketConnection> connections;
lock (_webSocketConnections)
{
connections = _webSocketConnections.ToList();
_webSocketConnections.Clear();
}
foreach (var connection in connections)
{
try
{
connection.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing connection");
}
}
return ex.Message
?.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase)
.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
}
public static string RemoveQueryStringByKey(string url, string key)
@@ -425,33 +361,52 @@ namespace Emby.Server.Implementations.HttpServer
return true;
}
/// <summary>
/// Validate a connection from a remote IP address to a URL to see if a redirection to HTTPS is required.
/// </summary>
/// <returns>True if the request is valid, or false if the request is not valid and an HTTPS redirect is required.</returns>
private bool ValidateSsl(string remoteIp, string urlString)
{
if (_config.Configuration.RequireHttps && _appHost.EnableHttps && !_config.Configuration.IsBehindProxy)
if (_config.Configuration.RequireHttps
&& _appHost.ListenWithHttps
&& !urlString.Contains("https://", StringComparison.OrdinalIgnoreCase))
{
if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1)
// These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected
if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1
|| urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1)
{
// These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected
if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1
|| urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1)
{
return true;
}
return true;
}
if (!_networkManager.IsInLocalNetwork(remoteIp))
{
return false;
}
if (!_networkManager.IsInLocalNetwork(remoteIp))
{
return false;
}
}
return true;
}
/// <inheritdoc />
public Task RequestHandler(HttpContext context)
{
if (context.WebSockets.IsWebSocketRequest)
{
return WebSocketRequestHandler(context);
}
var request = context.Request;
var response = context.Response;
var localPath = context.Request.Path.ToString();
var req = new WebSocketSharpRequest(request, response, request.Path);
return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted);
}
/// <summary>
/// Overridable method that can be used to implement a custom handler.
/// </summary>
public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
private async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
@@ -494,9 +449,11 @@ namespace Emby.Server.Implementations.HttpServer
if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
{
httpRes.StatusCode = 200;
httpRes.Headers.Add("Access-Control-Allow-Origin", "*");
httpRes.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
httpRes.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
foreach(var (key, value) in GetDefaultCorsHeaders(httpReq))
{
httpRes.Headers.Add(key, value);
}
httpRes.ContentType = "text/plain";
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
return;
@@ -536,22 +493,50 @@ namespace Emby.Server.Implementations.HttpServer
throw new FileNotFoundException();
}
}
catch (Exception ex)
catch (Exception requestEx)
{
// Do not handle exceptions manually when in development mode
// The framework-defined development exception page will be returned instead
if (_hostEnvironment.IsDevelopment())
try
{
throw;
}
var requestInnerEx = GetActualException(requestEx);
var statusCode = GetStatusCode(requestInnerEx);
bool ignoreStackTrace =
ex is SocketException
|| ex is IOException
|| ex is OperationCanceledException
|| ex is SecurityException
|| ex is FileNotFoundException;
await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false);
foreach (var (key, value) in GetDefaultCorsHeaders(httpReq))
{
if (!httpRes.Headers.ContainsKey(key))
{
httpRes.Headers.Add(key, value);
}
}
bool ignoreStackTrace =
requestInnerEx is SocketException
|| requestInnerEx is IOException
|| requestInnerEx is OperationCanceledException
|| requestInnerEx is SecurityException
|| requestInnerEx is AuthenticationException
|| requestInnerEx is FileNotFoundException;
// Do not handle 500 server exceptions manually when in development mode.
// Instead, re-throw the exception so it can be handled by the DeveloperExceptionPageMiddleware.
// However, do not use the DeveloperExceptionPageMiddleware when the stack trace should be ignored,
// because it will log the stack trace when it handles the exception.
if (statusCode == 500 && !ignoreStackTrace && _hostEnvironment.IsDevelopment())
{
throw;
}
await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog, ignoreStackTrace).ConfigureAwait(false);
}
catch (Exception handlerException)
{
var aggregateEx = new AggregateException("Error while handling request exception", requestEx, handlerException);
_logger.LogError(aggregateEx, "Error while handling exception in response to {Url}", urlToLog);
if (_hostEnvironment.IsDevelopment())
{
throw aggregateEx;
}
}
}
finally
{
@@ -569,6 +554,68 @@ namespace Emby.Server.Implementations.HttpServer
}
}
private async Task WebSocketRequestHandler(HttpContext context)
{
if (_disposed)
{
return;
}
try
{
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
var connection = new WebSocketConnection(
_loggerFactory.CreateLogger<WebSocketConnection>(),
webSocket,
context.Connection.RemoteIpAddress,
context.Request.Query)
{
OnReceive = ProcessWebSocketMessageReceived
};
WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection));
await connection.ProcessAsync().ConfigureAwait(false);
_logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress);
}
catch (Exception ex) // Otherwise ASP.Net will ignore the exception
{
_logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress);
if (!context.Response.HasStarted)
{
context.Response.StatusCode = 500;
}
}
}
/// <summary>
/// Get the default CORS headers.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
public IDictionary<string, string> GetDefaultCorsHeaders(IRequest req)
{
var origin = req.Headers["Origin"];
if (origin == StringValues.Empty)
{
origin = req.Headers["Host"];
if (origin == StringValues.Empty)
{
origin = "*";
}
}
var headers = new Dictionary<string, string>();
headers.Add("Access-Control-Allow-Origin", origin);
headers.Add("Access-Control-Allow-Credentials", "true");
headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization, Cookie");
return headers;
}
// Entry point for HttpListener
public ServiceHandler GetServiceHandler(IHttpRequest httpReq)
{
@@ -615,7 +662,7 @@ namespace Emby.Server.Implementations.HttpServer
ResponseFilters = new Action<IRequest, HttpResponse, object>[]
{
new ResponseFilter(_logger).FilterResponse
new ResponseFilter(this, _logger).FilterResponse
};
}
@@ -676,11 +723,6 @@ namespace Emby.Server.Implementations.HttpServer
return _jsonSerializer.DeserializeFromStreamAsync(stream, type);
}
public Task ProcessWebSocketRequest(HttpContext context)
{
return _socketListener.ProcessWebSocketRequest(context);
}
private string NormalizeEmbyRoutePath(string path)
{
_logger.LogDebug("Normalizing /emby route");
@@ -699,28 +741,6 @@ namespace Emby.Server.Implementations.HttpServer
return _baseUrlPrefix + NormalizeUrlPath(path);
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
Stop();
}
_disposed = true;
}
/// <summary>
/// Processes the web socket message received.
/// </summary>
@@ -732,8 +752,6 @@ namespace Emby.Server.Implementations.HttpServer
return Task.CompletedTask;
}
_logger.LogDebug("Websocket message received: {0}", result.MessageType);
IEnumerable<Task> GetTasks()
{
foreach (var x in _webSocketListeners)

View File

@@ -28,10 +28,16 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
public class HttpResultFactory : IHttpResultFactory
{
// Last-Modified and If-Modified-Since must follow strict date format,
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since
private const string HttpDateFormat = "ddd, dd MMM yyyy HH:mm:ss \"GMT\"";
// We specifically use en-US culture because both day of week and month names require it
private static readonly CultureInfo _enUSculture = new CultureInfo("en-US", false);
/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly ILogger<HttpResultFactory> _logger;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IStreamHelper _streamHelper;
@@ -44,12 +50,13 @@ namespace Emby.Server.Implementations.HttpServer
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
_streamHelper = streamHelper;
_logger = loggerfactory.CreateLogger("HttpResultFactory");
_logger = loggerfactory.CreateLogger<HttpResultFactory>();
}
/// <summary>
/// Gets the result.
/// </summary>
/// <param name="requestContext">The request context.</param>
/// <param name="content">The content.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="responseHeaders">The response headers.</param>
@@ -249,16 +256,20 @@ namespace Emby.Server.Implementations.HttpServer
{
var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString();
if (string.IsNullOrEmpty(acceptEncoding))
if (!string.IsNullOrEmpty(acceptEncoding))
{
//if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
// if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
// return "br";
if (acceptEncoding.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1)
if (acceptEncoding.Contains("deflate", StringComparison.OrdinalIgnoreCase))
{
return "deflate";
}
if (acceptEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)
if (acceptEncoding.Contains("gzip", StringComparison.OrdinalIgnoreCase))
{
return "gzip";
}
}
return null;
@@ -420,7 +431,11 @@ namespace Emby.Server.Implementations.HttpServer
if (!noCache)
{
DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
if (!DateTime.TryParseExact(requestContext.Headers[HeaderNames.IfModifiedSince], HttpDateFormat, _enUSculture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var ifModifiedSinceHeader))
{
_logger.LogDebug("Failed to parse If-Modified-Since header date: {0}", requestContext.Headers[HeaderNames.IfModifiedSince]);
return null;
}
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
{
@@ -631,7 +646,7 @@ namespace Emby.Server.Implementations.HttpServer
if (lastModifiedDate.HasValue)
{
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture);
responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToUniversalTime().ToString(HttpDateFormat, _enUSculture);
}
}
@@ -680,7 +695,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that.
/// </summary>
/// <param name="date">The date.</param>
/// <returns>DateTime.</returns>

View File

@@ -1,39 +0,0 @@
#pragma warning disable CS1591
using System;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Net;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
namespace Emby.Server.Implementations.HttpServer
{
public interface IHttpListener : IDisposable
{
/// <summary>
/// Gets or sets the error handler.
/// </summary>
/// <value>The error handler.</value>
Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
/// <summary>
/// Gets or sets the request handler.
/// </summary>
/// <value>The request handler.</value>
Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
/// <summary>
/// Gets or sets the web socket handler.
/// </summary>
/// <value>The web socket handler.</value>
Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
/// <summary>
/// Stops this instance.
/// </summary>
Task Stop();
Task ProcessWebSocketRequest(HttpContext ctx);
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Text;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -13,14 +14,17 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
public class ResponseFilter
{
private readonly IHttpServer _server;
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ResponseFilter"/> class.
/// </summary>
/// <param name="server">The HTTP server.</param>
/// <param name="logger">The logger.</param>
public ResponseFilter(ILogger logger)
public ResponseFilter(IHttpServer server, ILogger logger)
{
_server = server;
_logger = logger;
}
@@ -32,10 +36,16 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="dto">The dto.</param>
public void FilterResponse(IRequest req, HttpResponse res, object dto)
{
foreach(var (key, value) in _server.GetDefaultCorsHeaders(req))
{
res.Headers.Add(key, value);
}
// Try to prevent compatibility view
res.Headers.Add("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
res.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
res.Headers.Add("Access-Control-Allow-Origin", "*");
res.Headers["Access-Control-Allow-Headers"] = ("Accept, Accept-Language, Authorization, Cache-Control, " +
"Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " +
"Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " +
"Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " +
"X-Emby-Authorization");
if (dto is Exception exception)
{
@@ -82,6 +92,10 @@ namespace Emby.Server.Implementations.HttpServer
{
return null;
}
else if (inString.Length == 0)
{
return inString;
}
var newString = new StringBuilder(inString.Length);

View File

@@ -3,9 +3,11 @@
using System;
using System.Linq;
using Emby.Server.Implementations.SocketSharp;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
@@ -44,7 +46,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
{
var req = new WebSocketSharpRequest(request, null, request.Path, _logger);
var req = new WebSocketSharpRequest(request, null, request.Path);
var user = ValidateUser(req, authAttributes);
return user;
}
@@ -68,7 +70,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
if (user == null && auth.UserId != Guid.Empty)
{
throw new SecurityException("User with Id " + auth.UserId + " not found");
throw new AuthenticationException("User with Id " + auth.UserId + " not found");
}
if (user != null)
@@ -89,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,
@@ -103,35 +106,26 @@ namespace Emby.Server.Implementations.HttpServer.Security
private void ValidateUserAccess(
User user,
IRequest request,
IAuthenticationAttributes authAttribtues,
IAuthenticationAttributes authAttributes,
AuthorizationInfo auth)
{
if (user.Policy.IsDisabled)
if (user.HasPermission(PermissionKind.IsDisabled))
{
throw new SecurityException("User account has been disabled.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
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.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
throw new SecurityException("User account has been disabled.");
}
if (!user.Policy.IsAdministrator
&& !authAttribtues.EscapeParentalControl
if (!user.HasPermission(PermissionKind.IsAdministrator)
&& !authAttributes.EscapeParentalControl
&& !user.IsParentalScheduleAllowed())
{
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
throw new SecurityException("This user account is not allowed access at this time.")
{
SecurityExceptionType = SecurityExceptionType.ParentalControl
};
throw new SecurityException("This user account is not allowed access at this time.");
}
}
@@ -146,6 +140,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
return true;
}
if (authAttribtues.AllowLocalOnly && request.IsLocal)
{
return true;
@@ -188,34 +183,25 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
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.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
throw new SecurityException("User does not have admin access.");
}
}
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.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
throw new SecurityException("User does not have delete access.");
}
}
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.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
throw new SecurityException("User does not have download access.");
}
}
}
@@ -230,17 +216,17 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (string.IsNullOrEmpty(token))
{
throw new SecurityException("Access token is required.");
throw new AuthenticationException("Access token is required.");
}
var info = GetTokenInfo(request);
if (info == null)
{
throw new SecurityException("Access token is invalid or expired.");
throw new AuthenticationException("Access token is invalid or expired.");
}
//if (!string.IsNullOrEmpty(info.UserId))
// if (!string.IsNullOrEmpty(info.UserId))
//{
// var user = _userManager.GetUserById(info.UserId);

View File

@@ -71,6 +71,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
token = httpReq.Headers["X-MediaBrowser-Token"];
}
if (string.IsNullOrEmpty(token))
{
token = httpReq.QueryString["api_key"];
@@ -116,7 +117,6 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
info.Device = tokenInfo.DeviceName;
}
else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
{
if (allowTokenInfoUpdate)
@@ -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;
}
}
@@ -161,6 +161,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
_authRepo.Update(tokenInfo);
}
}
httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo;
}

View File

@@ -1,7 +1,7 @@
#pragma warning disable CS1591
using System;
using MediaBrowser.Controller.Entities;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;

View File

@@ -1,15 +1,18 @@
using System;
#nullable enable
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Net;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using UtfUnknown;
namespace Emby.Server.Implementations.HttpServer
{
@@ -21,72 +24,53 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly ILogger<WebSocketConnection> _logger;
/// <summary>
/// The json serializer.
/// The json serializer options.
/// </summary>
private readonly IJsonSerializer _jsonSerializer;
private readonly JsonSerializerOptions _jsonOptions;
/// <summary>
/// The socket.
/// </summary>
private readonly IWebSocket _socket;
private readonly WebSocket _socket;
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="socket">The socket.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="ArgumentNullException">socket</exception>
public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger)
/// <param name="query">The query.</param>
public WebSocketConnection(
ILogger<WebSocketConnection> logger,
WebSocket socket,
IPAddress? remoteEndPoint,
IQueryCollection query)
{
if (socket == null)
{
throw new ArgumentNullException(nameof(socket));
}
if (string.IsNullOrEmpty(remoteEndPoint))
{
throw new ArgumentNullException(nameof(remoteEndPoint));
}
if (jsonSerializer == null)
{
throw new ArgumentNullException(nameof(jsonSerializer));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
Id = Guid.NewGuid();
_jsonSerializer = jsonSerializer;
_socket = socket;
_socket.OnReceiveBytes = OnReceiveInternal;
RemoteEndPoint = remoteEndPoint;
_logger = logger;
_socket = socket;
RemoteEndPoint = remoteEndPoint;
QueryString = query;
socket.Closed += OnSocketClosed;
_jsonOptions = JsonDefaults.GetOptions();
LastActivityDate = DateTime.Now;
}
/// <inheritdoc />
public event EventHandler<EventArgs> Closed;
public event EventHandler<EventArgs>? Closed;
/// <summary>
/// Gets or sets the remote end point.
/// </summary>
public string RemoteEndPoint { get; private set; }
public IPAddress? RemoteEndPoint { get; }
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
public Func<WebSocketMessageInfo, Task> OnReceive { get; set; }
public Func<WebSocketMessageInfo, Task>? OnReceive { get; set; }
/// <summary>
/// Gets the last activity date.
@@ -94,23 +78,14 @@ namespace Emby.Server.Implementations.HttpServer
/// <value>The last activity date.</value>
public DateTime LastActivityDate { get; private set; }
/// <summary>
/// Gets the id.
/// </summary>
/// <value>The id.</value>
public Guid Id { get; private set; }
/// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>The URL.</value>
public string Url { get; set; }
/// <inheritdoc />
public DateTime LastKeepAliveDate { get; set; }
/// <summary>
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
public IQueryCollection QueryString { get; set; }
public IQueryCollection QueryString { get; }
/// <summary>
/// Gets the state.
@@ -118,70 +93,6 @@ namespace Emby.Server.Implementations.HttpServer
/// <value>The state.</value>
public WebSocketState State => _socket.State;
void OnSocketClosed(object sender, EventArgs e)
{
Closed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Called when [receive].
/// </summary>
/// <param name="bytes">The bytes.</param>
private void OnReceiveInternal(byte[] bytes)
{
LastActivityDate = DateTime.UtcNow;
if (OnReceive == null)
{
return;
}
var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase))
{
OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length));
}
else
{
OnReceiveInternal(Encoding.ASCII.GetString(bytes, 0, bytes.Length));
}
}
private void OnReceiveInternal(string message)
{
LastActivityDate = DateTime.UtcNow;
if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase))
{
// This info is useful sometimes but also clogs up the log
_logger.LogDebug("Received web socket message that is not a json structure: {message}", message);
return;
}
if (OnReceive == null)
{
return;
}
try
{
var stub = (WebSocketMessage<object>)_jsonSerializer.DeserializeFromString(message, typeof(WebSocketMessage<object>));
var info = new WebSocketMessageInfo
{
MessageType = stub.MessageType,
Data = stub.Data?.ToString(),
Connection = this
};
OnReceive(info);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing web socket message");
}
}
/// <summary>
/// Sends a message asynchronously.
/// </summary>
@@ -189,48 +100,146 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="message">The message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">message</exception>
public Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
var json = _jsonSerializer.SerializeToString(message);
return SendAsync(json, cancellationToken);
}
/// <summary>
/// Sends a message asynchronously.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
cancellationToken.ThrowIfCancellationRequested();
return _socket.SendAsync(buffer, true, cancellationToken);
var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions);
return _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken);
}
/// <inheritdoc />
public Task SendAsync(string text, CancellationToken cancellationToken)
public async Task ProcessAsync(CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(text))
var pipe = new Pipe();
var writer = pipe.Writer;
ValueWebSocketReceiveResult receiveresult;
do
{
throw new ArgumentNullException(nameof(text));
// Allocate at least 512 bytes from the PipeWriter
Memory<byte> memory = writer.GetMemory(512);
try
{
receiveresult = await _socket.ReceiveAsync(memory, cancellationToken);
}
catch (WebSocketException ex)
{
_logger.LogWarning("WS {IP} error receiving data: {Message}", RemoteEndPoint, ex.Message);
break;
}
int bytesRead = receiveresult.Count;
if (bytesRead == 0)
{
break;
}
// Tell the PipeWriter how much was read from the Socket
writer.Advance(bytesRead);
// Make the data available to the PipeReader
FlushResult flushResult = await writer.FlushAsync();
if (flushResult.IsCompleted)
{
// The PipeReader stopped reading
break;
}
LastActivityDate = DateTime.UtcNow;
if (receiveresult.EndOfMessage)
{
await ProcessInternal(pipe.Reader).ConfigureAwait(false);
}
} while (
(_socket.State == WebSocketState.Open || _socket.State == WebSocketState.Connecting)
&& receiveresult.MessageType != WebSocketMessageType.Close);
Closed?.Invoke(this, EventArgs.Empty);
if (_socket.State == WebSocketState.Open
|| _socket.State == WebSocketState.CloseReceived
|| _socket.State == WebSocketState.CloseSent)
{
await _socket.CloseAsync(
WebSocketCloseStatus.NormalClosure,
string.Empty,
cancellationToken).ConfigureAwait(false);
}
}
private async Task ProcessInternal(PipeReader reader)
{
ReadResult result = await reader.ReadAsync().ConfigureAwait(false);
ReadOnlySequence<byte> buffer = result.Buffer;
if (OnReceive == null)
{
// Tell the PipeReader how much of the buffer we have consumed
reader.AdvanceTo(buffer.End);
return;
}
cancellationToken.ThrowIfCancellationRequested();
WebSocketMessage<object> stub;
try
{
return _socket.SendAsync(text, true, cancellationToken);
if (buffer.IsSingleSegment)
{
stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buffer.FirstSpan, _jsonOptions);
}
else
{
var buf = ArrayPool<byte>.Shared.Rent(Convert.ToInt32(buffer.Length));
try
{
buffer.CopyTo(buf);
stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buf, _jsonOptions);
}
finally
{
ArrayPool<byte>.Shared.Return(buf);
}
}
}
catch (JsonException ex)
{
// Tell the PipeReader how much of the buffer we have consumed
reader.AdvanceTo(buffer.End);
_logger.LogError(ex, "Error processing web socket message");
return;
}
// Tell the PipeReader how much of the buffer we have consumed
reader.AdvanceTo(buffer.End);
_logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub);
var info = new WebSocketMessageInfo
{
MessageType = stub.MessageType,
Data = stub.Data?.ToString(), // Data can be null
Connection = this
};
if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
{
await SendKeepAliveResponse();
}
else
{
await OnReceive(info).ConfigureAwait(false);
}
}
private Task SendKeepAliveResponse()
{
LastKeepAliveDate = DateTime.UtcNow;
return SendAsync(
new WebSocketMessage<string>
{
MessageId = Guid.NewGuid(),
MessageType = "KeepAlive"
}, CancellationToken.None);
}
/// <inheritdoc />

View File

@@ -11,12 +11,18 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.IO;
using Emby.Server.Implementations.Library;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO
{
public class LibraryMonitor : ILibraryMonitor
{
private readonly ILogger<LibraryMonitor> _logger;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
/// <summary>
/// The file system watchers.
/// </summary>
@@ -32,38 +38,6 @@ namespace Emby.Server.Implementations.IO
/// </summary>
private readonly ConcurrentDictionary<string, string> _tempIgnoredPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Any file name ending in any of these will be ignored by the watchers.
/// </summary>
private static readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"small.jpg",
"albumart.jpg",
// WMC temp recording directories that will constantly be written to
"TempRec",
"TempSBE"
};
private static readonly string[] _alwaysIgnoreSubstrings = new string[]
{
// Synology
"eaDir",
"#recycle",
".wd_tv",
".actors"
};
private static readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
// thumbs.db
".db",
// bts sync files
".bts",
".sync"
};
/// <summary>
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
/// </summary>
@@ -113,34 +87,23 @@ namespace Emby.Server.Implementations.IO
}
catch (Exception ex)
{
Logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
_logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
}
}
}
/// <summary>
/// Gets or sets the logger.
/// </summary>
/// <value>The logger.</value>
private ILogger Logger { get; set; }
private ILibraryManager LibraryManager { get; set; }
private IServerConfigurationManager ConfigurationManager { get; set; }
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
/// </summary>
public LibraryMonitor(
ILoggerFactory loggerFactory,
ILogger<LibraryMonitor> logger,
ILibraryManager libraryManager,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem)
{
LibraryManager = libraryManager;
Logger = loggerFactory.CreateLogger(GetType().Name);
ConfigurationManager = configurationManager;
_libraryManager = libraryManager;
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
}
@@ -151,7 +114,7 @@ namespace Emby.Server.Implementations.IO
return false;
}
var options = LibraryManager.GetLibraryOptions(item);
var options = _libraryManager.GetLibraryOptions(item);
if (options != null)
{
@@ -163,12 +126,12 @@ namespace Emby.Server.Implementations.IO
public void Start()
{
LibraryManager.ItemAdded += OnLibraryManagerItemAdded;
LibraryManager.ItemRemoved += OnLibraryManagerItemRemoved;
_libraryManager.ItemAdded += OnLibraryManagerItemAdded;
_libraryManager.ItemRemoved += OnLibraryManagerItemRemoved;
var pathsToWatch = new List<string>();
var paths = LibraryManager
var paths = _libraryManager
.RootFolder
.Children
.Where(IsLibraryMonitorEnabled)
@@ -261,7 +224,7 @@ namespace Emby.Server.Implementations.IO
if (!Directory.Exists(path))
{
// Seeing a crash in the mono runtime due to an exception being thrown on a different thread
Logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path);
_logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path);
return;
}
@@ -297,17 +260,16 @@ namespace Emby.Server.Implementations.IO
if (_fileSystemWatchers.TryAdd(path, newWatcher))
{
newWatcher.EnableRaisingEvents = true;
Logger.LogInformation("Watching directory " + path);
_logger.LogInformation("Watching directory " + path);
}
else
{
DisposeWatcher(newWatcher, false);
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error watching path: {path}", path);
_logger.LogError(ex, "Error watching path: {path}", path);
}
});
}
@@ -333,7 +295,7 @@ namespace Emby.Server.Implementations.IO
{
using (watcher)
{
Logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path);
_logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path);
watcher.Created -= OnWatcherChanged;
watcher.Deleted -= OnWatcherChanged;
@@ -372,7 +334,7 @@ namespace Emby.Server.Implementations.IO
var ex = e.GetException();
var dw = (FileSystemWatcher)sender;
Logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path);
_logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path);
DisposeWatcher(dw, true);
}
@@ -390,7 +352,7 @@ namespace Emby.Server.Implementations.IO
}
catch (Exception ex)
{
Logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath);
_logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath);
}
}
@@ -401,12 +363,7 @@ namespace Emby.Server.Implementations.IO
throw new ArgumentNullException(nameof(path));
}
var filename = Path.GetFileName(path);
var monitorPath = !string.IsNullOrEmpty(filename) &&
!_alwaysIgnoreFiles.Contains(filename) &&
!_alwaysIgnoreExtensions.Contains(Path.GetExtension(path)) &&
_alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1);
var monitorPath = !IgnorePatterns.ShouldIgnore(path);
// Ignore certain files
var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList();
@@ -416,13 +373,13 @@ namespace Emby.Server.Implementations.IO
{
if (_fileSystem.AreEqual(i, path))
{
Logger.LogDebug("Ignoring change to {Path}", path);
_logger.LogDebug("Ignoring change to {Path}", path);
return true;
}
if (_fileSystem.ContainsSubPath(i, path))
{
Logger.LogDebug("Ignoring change to {Path}", path);
_logger.LogDebug("Ignoring change to {Path}", path);
return true;
}
@@ -430,12 +387,11 @@ namespace Emby.Server.Implementations.IO
var parent = Path.GetDirectoryName(i);
if (!string.IsNullOrEmpty(parent) && _fileSystem.AreEqual(parent, path))
{
Logger.LogDebug("Ignoring change to {Path}", path);
_logger.LogDebug("Ignoring change to {Path}", path);
return true;
}
return false;
}))
{
monitorPath = false;
@@ -485,7 +441,7 @@ namespace Emby.Server.Implementations.IO
}
}
var newRefresher = new FileRefresher(path, ConfigurationManager, LibraryManager, Logger);
var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger);
newRefresher.Completed += NewRefresher_Completed;
_activeRefreshers.Add(newRefresher);
}
@@ -502,8 +458,8 @@ namespace Emby.Server.Implementations.IO
/// </summary>
public void Stop()
{
LibraryManager.ItemAdded -= OnLibraryManagerItemAdded;
LibraryManager.ItemRemoved -= OnLibraryManagerItemRemoved;
_libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
_libraryManager.ItemRemoved -= OnLibraryManagerItemRemoved;
foreach (var watcher in _fileSystemWatchers.Values.ToList())
{

View File

@@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.IO
/// </summary>
public class ManagedFileSystem : IFileSystem
{
protected ILogger Logger;
protected ILogger<ManagedFileSystem> Logger;
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
private readonly string _tempPath;
@@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.IO
{
result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
//if (!result.IsDirectory)
// if (!result.IsDirectory)
//{
// result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
//}
@@ -628,6 +628,7 @@ namespace Emby.Server.Implementations.IO
{
return false;
}
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
});
}
@@ -682,6 +683,7 @@ namespace Emby.Server.Implementations.IO
{
return false;
}
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
});
}

View File

@@ -1,3 +1,7 @@
#pragma warning disable CS1591
using System;
namespace Emby.Server.Implementations
{
public interface IStartupOptions
@@ -36,5 +40,10 @@ namespace Emby.Server.Implementations
/// Gets the value of the --plugin-manifest-url command line option.
/// </summary>
string PluginManifestUrl { get; }
/// <summary>
/// Gets the value of the --published-server-url command line option.
/// </summary>
Uri PublishedServerUrl { get; }
}
}

View File

@@ -0,0 +1,60 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Images
{
/// <summary>
/// Class ArtistImageProvider.
/// </summary>
public class ArtistImageProvider : BaseDynamicImageProvider<MusicArtist>
{
/// <summary>
/// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Get children objects used to create an artist image.
/// </summary>
/// <param name="item">The artist used to create the image.</param>
/// <returns>Any relevant children objects.</returns>
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
return Array.Empty<BaseItem>();
// TODO enable this when BaseDynamicImageProvider objects are configurable
// return _libraryManager.GetItemList(new InternalItemsQuery
// {
// ArtistIds = new[] { item.Id },
// IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
// OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
// Limit = 4,
// Recursive = true,
// ImageTypes = new[] { ImageType.Primary },
// DtoOptions = new DtoOptions(false)
// });
}
}
}

View File

@@ -194,7 +194,8 @@ namespace Emby.Server.Implementations.Images
return outputPath;
}
protected virtual string CreateImage(BaseItem item,
protected virtual string CreateImage(
BaseItem item,
IReadOnlyCollection<BaseItem> itemsWithImages,
string outputPathWithoutExtension,
ImageType imageType,
@@ -214,7 +215,12 @@ namespace Emby.Server.Implementations.Images
if (imageType == ImageType.Primary)
{
if (item is UserView || item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum)
if (item is UserView
|| item is Playlist
|| item is MusicGenre
|| item is Genre
|| item is PhotoAlbum
|| item is MusicArtist)
{
return CreateSquareCollage(item, itemsWithImages, outputPath);
}
@@ -225,7 +231,7 @@ namespace Emby.Server.Implementations.Images
throw new ArgumentException("Unexpected image type", nameof(imageType));
}
public bool HasChanged(BaseItem item, IDirectoryService directoryServicee)
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
if (!Supports(item))
{
@@ -236,6 +242,7 @@ namespace Emby.Server.Implementations.Images
{
return true;
}
if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb))
{
return true;

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;
@@ -11,7 +13,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.UserViews
namespace Emby.Server.Implementations.Images
{
public class CollectionFolderImageProvider : BaseDynamicImageProvider<CollectionFolder>
{
@@ -69,7 +71,6 @@ namespace Emby.Server.Implementations.UserViews
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
},
IncludeItemTypes = includeItemTypes
});
}

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;
@@ -14,18 +16,16 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.UserViews
namespace Emby.Server.Implementations.Images
{
public class DynamicImageProvider : BaseDynamicImageProvider<UserView>
{
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager)
public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager)
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
_userManager = userManager;
_libraryManager = libraryManager;
}
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
@@ -78,7 +78,6 @@ namespace Emby.Server.Implementations.UserViews
}
return i;
}).GroupBy(x => x.Id)
.Select(x => x.First());

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration;
@@ -11,7 +13,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.UserViews
namespace Emby.Server.Implementations.Images
{
public abstract class BaseFolderImageProvider<T> : BaseDynamicImageProvider<T>
where T : Folder, new()
@@ -75,16 +77,12 @@ namespace Emby.Server.Implementations.UserViews
return false;
}
var folder = item as Folder;
if (folder != null)
if (item is Folder && item.IsTopParent)
{
if (folder.IsTopParent)
{
return false;
}
return false;
}
return true;
//return item.SourceType == SourceType.Library;
}
}

View File

@@ -1,6 +1,6 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
@@ -9,66 +9,21 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Playlists
namespace Emby.Server.Implementations.Images
{
public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist>
{
public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
}
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (Playlist)item;
return playlist.GetManageableItems()
.Select(i =>
{
var subItem = i.Item2;
var episode = subItem as Episode;
if (episode != null)
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))
{
return series;
}
}
if (subItem.HasImage(ImageType.Primary))
{
return subItem;
}
var parent = subItem.GetOwner() ?? subItem.GetParent();
if (parent != null && parent.HasImage(ImageType.Primary))
{
if (parent is MusicAlbum)
{
return parent;
}
}
return null;
})
.Where(i => i != null)
.GroupBy(x => x.Id)
.Select(x => x.First())
.ToList();
}
}
/// <summary>
/// Class MusicGenreImageProvider.
/// </summary>
public class MusicGenreImageProvider : BaseDynamicImageProvider<MusicGenre>
{
/// <summary>
/// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
@@ -76,6 +31,11 @@ namespace Emby.Server.Implementations.Playlists
_libraryManager = libraryManager;
}
/// <summary>
/// Get children objects used to create an music genre image.
/// </summary>
/// <param name="item">The music genre used to create the image.</param>
/// <returns>Any relevant children objects.</returns>
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
return _libraryManager.GetItemList(new InternalItemsQuery
@@ -91,8 +51,14 @@ namespace Emby.Server.Implementations.Playlists
}
}
/// <summary>
/// Class GenreImageProvider.
/// </summary>
public class GenreImageProvider : BaseDynamicImageProvider<Genre>
{
/// <summary>
/// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
@@ -100,6 +66,11 @@ namespace Emby.Server.Implementations.Playlists
_libraryManager = libraryManager;
}
/// <summary>
/// Get children objects used to create an genre image.
/// </summary>
/// <param name="item">The genre used to create the image.</param>
/// <returns>Any relevant children objects.</returns>
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
return _libraryManager.GetItemList(new InternalItemsQuery

View File

@@ -0,0 +1,66 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Images
{
public class PlaylistImageProvider : BaseDynamicImageProvider<Playlist>
{
public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
}
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (Playlist)item;
return playlist.GetManageableItems()
.Select(i =>
{
var subItem = i.Item2;
var episode = subItem as Episode;
if (episode != null)
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))
{
return series;
}
}
if (subItem.HasImage(ImageType.Primary))
{
return subItem;
}
var parent = subItem.GetOwner() ?? subItem.GetParent();
if (parent != null && parent.HasImage(ImageType.Primary))
{
if (parent is MusicAlbum)
{
return parent;
}
}
return null;
})
.Where(i => i != null)
.GroupBy(x => x.Id)
.Select(x => x.First())
.ToList();
}
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
@@ -10,38 +8,12 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library
{
/// <summary>
/// Provides the core resolver ignore rules
/// Provides the core resolver ignore rules.
/// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Any folder named in this list will be ignored
/// </summary>
private static readonly string[] _ignoreFolders =
{
"metadata",
"ps3_update",
"ps3_vprm",
"extrafanart",
"extrathumbs",
".actors",
".wd_tv",
// Synology
"@eaDir",
"eaDir",
"#recycle",
// Qnap
"@Recycle",
".@__thumb",
"$RECYCLE.BIN",
"System Volume Information",
".grab",
};
/// <summary>
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
/// </summary>
@@ -60,23 +32,15 @@ namespace Emby.Server.Implementations.Library
return false;
}
var filename = fileInfo.Name;
// Ignore hidden files on UNIX
if (Environment.OSVersion.Platform != PlatformID.Win32NT
&& filename[0] == '.')
if (IgnorePatterns.ShouldIgnore(fileInfo.FullName))
{
return true;
}
var filename = fileInfo.Name;
if (fileInfo.IsDirectory)
{
// Ignore any folders in our list
if (_ignoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
{
return true;
}
if (parent != null)
{
// Ignore trailer folders but allow it at the collection level
@@ -109,11 +73,6 @@ namespace Emby.Server.Implementations.Library
return true;
}
}
// Ignore samples
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
return m.Success;
}
return false;

View File

@@ -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 ArgumentNullException(nameof(resolvedUser));
}
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());
}
}
}

View File

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

View File

@@ -12,11 +12,13 @@ namespace Emby.Server.Implementations.Library
public class ExclusiveLiveStream : ILiveStream
{
public int ConsumerCount { get; set; }
public string OriginalStreamId { get; set; }
public string TunerHostId => null;
public bool EnableStreamSharing { get; set; }
public MediaSourceInfo MediaSource { get; set; }
public string UniqueId { get; private set; }

View File

@@ -0,0 +1,74 @@
using System.Linq;
using DotNet.Globbing;
namespace Emby.Server.Implementations.Library
{
/// <summary>
/// Glob patterns for files to ignore.
/// </summary>
public static class IgnorePatterns
{
/// <summary>
/// Files matching these glob patterns will be ignored.
/// </summary>
public static readonly string[] Patterns = new string[]
{
"**/small.jpg",
"**/albumart.jpg",
"**/*sample*",
// Directories
"**/metadata/**",
"**/ps3_update/**",
"**/ps3_vprm/**",
"**/extrafanart/**",
"**/extrathumbs/**",
"**/.actors/**",
"**/.wd_tv/**",
"**/lost+found/**",
// WMC temp recording directories that will constantly be written to
"**/TempRec/**",
"**/TempSBE/**",
// Synology
"**/eaDir/**",
"**/@eaDir/**",
"**/#recycle/**",
// Qnap
"**/@Recycle/**",
"**/.@__thumb/**",
"**/$RECYCLE.BIN/**",
"**/System Volume Information/**",
"**/.grab/**",
// Unix hidden files and directories
"**/.*/**",
// thumbs.db
"**/thumbs.db",
// bts sync files
"**/*.bts",
"**/*.sync",
};
private static readonly GlobOptions _globOptions = new GlobOptions
{
Evaluation = {
CaseInsensitive = true
}
};
private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
/// <summary>
/// Returns true if the supplied path should be ignored.
/// </summary>
public static bool ShouldIgnore(string path)
{
return _globs.Any(g => g.IsMatch(path));
}
}
}

View File

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

View File

@@ -17,14 +17,16 @@ 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.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
@@ -35,6 +37,7 @@ using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -44,19 +47,43 @@ using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Providers.MediaInfo;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
using VideoResolver = Emby.Naming.Video.VideoResolver;
namespace Emby.Server.Implementations.Library
{
/// <summary>
/// Class LibraryManager
/// Class LibraryManager.
/// </summary>
public class LibraryManager : ILibraryManager
{
private readonly ILogger<LibraryManager> _logger;
private readonly ITaskManager _taskManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataRepository;
private readonly IServerConfigurationManager _configurationManager;
private readonly Lazy<ILibraryMonitor> _libraryMonitorFactory;
private readonly Lazy<IProviderManager> _providerManagerFactory;
private readonly Lazy<IUserViewManager> _userviewManagerFactory;
private readonly IServerApplicationHost _appHost;
private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly IItemRepository _itemRepository;
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
private readonly IImageProcessor _imageProcessor;
private NamingOptions _namingOptions;
private string[] _videoFileExtensions;
private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
private IProviderManager ProviderManager => _providerManagerFactory.Value;
private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
/// <summary>
/// Gets or sets the postscan tasks.
/// </summary>
@@ -70,13 +97,13 @@ namespace Emby.Server.Implementations.Library
private IIntroProvider[] IntroProviders { get; set; }
/// <summary>
/// Gets or sets the list of entity resolution ignore rules
/// Gets or sets the list of entity resolution ignore rules.
/// </summary>
/// <value>The entity resolution ignore rules.</value>
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
/// <summary>
/// Gets or sets the list of currently registered entity resolvers
/// Gets or sets the list of currently registered entity resolvers.
/// </summary>
/// <value>The entity resolvers enumerable.</value>
private IItemResolver[] EntityResolvers { get; set; }
@@ -89,12 +116,6 @@ namespace Emby.Server.Implementations.Library
/// <value>The comparers.</value>
private IBaseItemComparer[] Comparers { get; set; }
/// <summary>
/// Gets or sets the active item repository
/// </summary>
/// <value>The item repository.</value>
public IItemRepository ItemRepository { get; set; }
/// <summary>
/// Occurs when [item added].
/// </summary>
@@ -110,90 +131,56 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// The _task manager
/// </summary>
private readonly ITaskManager _taskManager;
/// <summary>
/// The _user manager
/// </summary>
private readonly IUserManager _userManager;
/// <summary>
/// The _user data repository
/// </summary>
private readonly IUserDataManager _userDataRepository;
/// <summary>
/// Gets or sets the configuration manager.
/// </summary>
/// <value>The configuration manager.</value>
private IServerConfigurationManager ConfigurationManager { get; set; }
private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
private readonly Func<IProviderManager> _providerManagerFactory;
private readonly Func<IUserViewManager> _userviewManager;
public bool IsScanRunning { get; private set; }
private IServerApplicationHost _appHost;
private readonly IMediaEncoder _mediaEncoder;
/// <summary>
/// The _library items cache
/// </summary>
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
/// <summary>
/// Gets the library items cache.
/// </summary>
/// <value>The library items cache.</value>
private ConcurrentDictionary<Guid, BaseItem> LibraryItemsCache => _libraryItemsCache;
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
/// </summary>
/// <param name="appHost">The application host</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="logger">The logger.</param>
/// <param name="taskManager">The task manager.</param>
/// <param name="userManager">The user manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="userDataRepository">The user data repository.</param>
/// <param name="libraryMonitorFactory">The library monitor.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="providerManagerFactory">The provider manager.</param>
/// <param name="userviewManagerFactory">The userview manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
/// <param name="itemRepository">The item repository.</param>
/// <param name="imageProcessor">The image processor.</param>
public LibraryManager(
IServerApplicationHost appHost,
ILoggerFactory loggerFactory,
ILogger<LibraryManager> logger,
ITaskManager taskManager,
IUserManager userManager,
IServerConfigurationManager configurationManager,
IUserDataManager userDataRepository,
Func<ILibraryMonitor> libraryMonitorFactory,
Lazy<ILibraryMonitor> libraryMonitorFactory,
IFileSystem fileSystem,
Func<IProviderManager> providerManagerFactory,
Func<IUserViewManager> userviewManager,
IMediaEncoder mediaEncoder)
Lazy<IProviderManager> providerManagerFactory,
Lazy<IUserViewManager> userviewManagerFactory,
IMediaEncoder mediaEncoder,
IItemRepository itemRepository,
IImageProcessor imageProcessor)
{
_appHost = appHost;
_logger = loggerFactory.CreateLogger(nameof(LibraryManager));
_logger = logger;
_taskManager = taskManager;
_userManager = userManager;
ConfigurationManager = configurationManager;
_configurationManager = configurationManager;
_userDataRepository = userDataRepository;
_libraryMonitorFactory = libraryMonitorFactory;
_fileSystem = fileSystem;
_providerManagerFactory = providerManagerFactory;
_userviewManager = userviewManager;
_userviewManagerFactory = userviewManagerFactory;
_mediaEncoder = mediaEncoder;
_itemRepository = itemRepository;
_imageProcessor = imageProcessor;
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
RecordConfigurationValues(configurationManager.Configuration);
}
@@ -222,12 +209,12 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// The _root folder
/// The _root folder.
/// </summary>
private volatile AggregateFolder _rootFolder;
/// <summary>
/// The _root folder sync lock
/// The _root folder sync lock.
/// </summary>
private readonly object _rootFolderSyncLock = new object();
@@ -272,7 +259,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
private void ConfigurationUpdated(object sender, EventArgs e)
{
var config = ConfigurationManager.Configuration;
var config = _configurationManager.Configuration;
var wizardChanged = config.IsStartupWizardCompleted != _wizardCompleted;
@@ -306,7 +293,7 @@ namespace Emby.Server.Implementations.Library
}
}
LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
_libraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
}
public void DeleteItem(BaseItem item, DeleteOptions options)
@@ -437,10 +424,10 @@ namespace Emby.Server.Implementations.Library
item.SetParent(null);
ItemRepository.DeleteItem(item.Id);
_itemRepository.DeleteItem(item.Id);
foreach (var child in children)
{
ItemRepository.DeleteItem(child.Id);
_itemRepository.DeleteItem(child.Id);
}
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
@@ -509,15 +496,15 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(type));
}
if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal))
if (key.StartsWith(_configurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal))
{
// Try to normalize paths located underneath program-data in an attempt to make them more portable
key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length)
key = key.Substring(_configurationManager.ApplicationPaths.ProgramDataPath.Length)
.TrimStart(new[] { '/', '\\' })
.Replace("/", "\\");
}
if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds)
{
key = key.ToLowerInvariant();
}
@@ -527,8 +514,8 @@ namespace Emby.Server.Implementations.Library
return key.GetMD5();
}
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, bool allowIgnorePath = true)
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent, allowIgnorePath: allowIgnorePath);
private BaseItem ResolvePath(
FileSystemMetadata fileInfo,
@@ -536,7 +523,8 @@ namespace Emby.Server.Implementations.Library
IItemResolver[] resolvers,
Folder parent = null,
string collectionType = null,
LibraryOptions libraryOptions = null)
LibraryOptions libraryOptions = null,
bool allowIgnorePath = true)
{
if (fileInfo == null)
{
@@ -550,7 +538,7 @@ namespace Emby.Server.Implementations.Library
collectionType = GetContentTypeOverride(fullPath, true);
}
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
{
Parent = parent,
Path = fullPath,
@@ -560,7 +548,7 @@ namespace Emby.Server.Implementations.Library
};
// Return null if ignore rules deem that we should do so
if (IgnoreFile(args.FileInfo, args.Parent))
if (allowIgnorePath && IgnoreFile(args.FileInfo, args.Parent))
{
return null;
}
@@ -639,7 +627,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Determines whether a path should be ignored based on its contents - called after the contents have been read
/// Determines whether a path should be ignored based on its contents - called after the contents have been read.
/// </summary>
/// <param name="args">The args.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
@@ -720,11 +708,13 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception>
public AggregateFolder CreateRootFolder()
{
var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
var rootFolderPath = _configurationManager.ApplicationPaths.RootFolderPath;
Directory.CreateDirectory(rootFolderPath);
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))).DeepCopy<Folder, AggregateFolder>();
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath), allowIgnorePath: false))
.DeepCopy<Folder, AggregateFolder>();
// In case program data folder was moved
if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal))
@@ -734,7 +724,7 @@ namespace Emby.Server.Implementations.Library
}
// Add in the plug-in folders
var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists");
var path = Path.Combine(_configurationManager.ApplicationPaths.DataPath, "playlists");
Directory.CreateDirectory(path);
@@ -786,7 +776,7 @@ namespace Emby.Server.Implementations.Library
{
if (_userRootFolder == null)
{
var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
_logger.LogDebug("Creating userRootPath at {path}", userRootPath);
Directory.CreateDirectory(userRootPath);
@@ -805,7 +795,7 @@ namespace Emby.Server.Implementations.Library
if (tmpItem == null)
{
_logger.LogDebug("Creating new userRootFolder with DeepCopy");
tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy<Folder, UserRootFolder>();
tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath), allowIgnorePath: false)).DeepCopy<Folder, UserRootFolder>();
}
// In case program data folder was moved
@@ -919,7 +909,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Gets a Genre
/// Gets a Genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Genre}.</returns>
@@ -980,7 +970,7 @@ namespace Emby.Server.Implementations.Library
where T : BaseItem, new()
{
var path = getPathFn(name);
var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
var forceCaseInsensitiveId = _configurationManager.Configuration.EnableNormalizedItemByNameIds;
return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
}
@@ -994,13 +984,13 @@ namespace Emby.Server.Implementations.Library
public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
{
// Ensure the location is available.
Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath);
return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
}
/// <summary>
/// Reloads the root media folder
/// Reloads the root media folder.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -1031,7 +1021,7 @@ namespace Emby.Server.Implementations.Library
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
IsScanRunning = true;
_libraryMonitorFactory().Stop();
LibraryMonitor.Stop();
try
{
@@ -1039,7 +1029,7 @@ namespace Emby.Server.Implementations.Library
}
finally
{
_libraryMonitorFactory().Start();
LibraryMonitor.Start();
IsScanRunning = false;
}
}
@@ -1148,7 +1138,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(percent * 100);
}
ItemRepository.UpdateInheritedValues(cancellationToken);
_itemRepository.UpdateInheritedValues(cancellationToken);
progress.Report(100);
}
@@ -1168,9 +1158,9 @@ namespace Emby.Server.Implementations.Library
var topLibraryFolders = GetUserRootFolder().Children.ToList();
_logger.LogDebug("Getting refreshQueue");
var refreshQueue = includeRefreshState ? _providerManagerFactory().GetRefreshQueue() : null;
var refreshQueue = includeRefreshState ? ProviderManager.GetRefreshQueue() : null;
return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath)
return _fileSystem.GetDirectoryPaths(_configurationManager.ApplicationPaths.DefaultUserViewsPath)
.Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue))
.ToList();
}
@@ -1245,7 +1235,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException("Guid can't be empty", nameof(id));
}
if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
if (_libraryItemsCache.TryGetValue(id, out BaseItem item))
{
return item;
}
@@ -1276,7 +1266,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User, allowExternalContent);
}
return ItemRepository.GetItemList(query);
return _itemRepository.GetItemList(query);
}
public List<BaseItem> GetItemList(InternalItemsQuery query)
@@ -1300,7 +1290,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User);
}
return ItemRepository.GetCount(query);
return _itemRepository.GetCount(query);
}
public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
@@ -1315,7 +1305,7 @@ namespace Emby.Server.Implementations.Library
}
}
return ItemRepository.GetItemList(query);
return _itemRepository.GetItemList(query);
}
public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
@@ -1327,12 +1317,12 @@ namespace Emby.Server.Implementations.Library
if (query.EnableTotalRecordCount)
{
return ItemRepository.GetItems(query);
return _itemRepository.GetItems(query);
}
return new QueryResult<BaseItem>
{
Items = ItemRepository.GetItemList(query).ToArray()
Items = _itemRepository.GetItemList(query).ToArray()
};
}
@@ -1343,7 +1333,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User);
}
return ItemRepository.GetItemIdsList(query);
return _itemRepository.GetItemIdsList(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
@@ -1354,7 +1344,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetStudios(query);
return _itemRepository.GetStudios(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
@@ -1365,7 +1355,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetGenres(query);
return _itemRepository.GetGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
@@ -1376,7 +1366,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetMusicGenres(query);
return _itemRepository.GetMusicGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
@@ -1387,7 +1377,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetAllArtists(query);
return _itemRepository.GetAllArtists(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
@@ -1398,7 +1388,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetArtists(query);
return _itemRepository.GetArtists(query);
}
private void SetTopParentOrAncestorIds(InternalItemsQuery query)
@@ -1439,7 +1429,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetAlbumArtists(query);
return _itemRepository.GetAlbumArtists(query);
}
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
@@ -1460,10 +1450,10 @@ namespace Emby.Server.Implementations.Library
if (query.EnableTotalRecordCount)
{
return ItemRepository.GetItems(query);
return _itemRepository.GetItems(query);
}
var list = ItemRepository.GetItemList(query);
var list = _itemRepository.GetItemList(query);
return new QueryResult<BaseItem>
{
@@ -1509,7 +1499,7 @@ namespace Emby.Server.Implementations.Library
string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) &&
query.ItemIds.Length == 0)
{
var userViews = _userviewManager().GetUserViews(new UserViewQuery
var userViews = UserViewManager.GetUserViews(new UserViewQuery
{
UserId = user.Id,
IncludeHidden = true,
@@ -1553,7 +1543,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)
@@ -1809,7 +1800,7 @@ namespace Emby.Server.Implementations.Library
// Don't iterate multiple times
var itemsList = items.ToList();
ItemRepository.SaveItems(itemsList, cancellationToken);
_itemRepository.SaveItems(itemsList, cancellationToken);
foreach (var item in itemsList)
{
@@ -1844,10 +1835,90 @@ namespace Emby.Server.Implementations.Library
}
}
public void UpdateImages(BaseItem item)
private bool ImageNeedsRefresh(ItemImageInfo image)
{
ItemRepository.SaveImages(item);
if (image.Path != null && image.IsLocalFile)
{
if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash))
{
return true;
}
try
{
return _fileSystem.GetLastWriteTimeUtc(image.Path) != image.DateModified;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot get file info for {0}", image.Path);
return false;
}
}
return image.Path != null && !image.IsLocalFile;
}
public void UpdateImages(BaseItem item, bool forceUpdate = false)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
if (outdated.Length == 0)
{
RegisterItem(item);
return;
}
foreach (var img in outdated)
{
var image = img;
if (!img.IsLocalFile)
{
try
{
var index = item.GetImageIndex(img);
image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult();
}
catch (ArgumentException)
{
_logger.LogWarning("Cannot get image index for {0}", img.Path);
continue;
}
catch (InvalidOperationException)
{
_logger.LogWarning("Cannot fetch image from {0}", img.Path);
continue;
}
}
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
image.Width = size.Width;
image.Height = size.Height;
try
{
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path);
image.BlurHash = string.Empty;
}
try
{
image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot update DateModified for {0}", image.Path);
}
}
_itemRepository.SaveImages(item);
RegisterItem(item);
}
@@ -1863,15 +1934,15 @@ namespace Emby.Server.Implementations.Library
{
if (item.IsFileProtocol)
{
_providerManagerFactory().SaveMetadata(item, updateReason);
ProviderManager.SaveMetadata(item, updateReason);
}
item.DateLastSaved = DateTime.UtcNow;
RegisterItem(item);
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
}
ItemRepository.SaveItems(itemsList, cancellationToken);
_itemRepository.SaveItems(itemsList, cancellationToken);
if (ItemUpdated != null)
{
@@ -1947,7 +2018,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>BaseItem.</returns>
public BaseItem RetrieveItem(Guid id)
{
return ItemRepository.RetrieveItem(id);
return _itemRepository.RetrieveItem(id);
}
public List<Folder> GetCollectionFolders(BaseItem item)
@@ -2066,7 +2137,7 @@ namespace Emby.Server.Implementations.Library
private string GetContentTypeOverride(string path, bool inherit)
{
var nameValuePair = ConfigurationManager.Configuration.ContentTypes
var nameValuePair = _configurationManager.Configuration.ContentTypes
.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
|| (inherit && !string.IsNullOrEmpty(i.Name)
&& _fileSystem.ContainsSubPath(i.Name, path)));
@@ -2115,7 +2186,7 @@ namespace Emby.Server.Implementations.Library
string sortName)
{
var path = Path.Combine(
ConfigurationManager.ApplicationPaths.InternalMetadataPath,
_configurationManager.ApplicationPaths.InternalMetadataPath,
"views",
_fileSystem.GetValidFilename(viewType));
@@ -2147,7 +2218,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
ProviderManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}
return item;
@@ -2165,7 +2236,7 @@ namespace Emby.Server.Implementations.Library
var id = GetNewItemId(idValues, typeof(UserView));
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var item = GetItemById(id) as UserView;
@@ -2202,7 +2273,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
_providerManagerFactory().QueueRefresh(
ProviderManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -2269,7 +2340,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
_providerManagerFactory().QueueRefresh(
ProviderManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -2303,7 +2374,7 @@ namespace Emby.Server.Implementations.Library
var id = GetNewItemId(idValues, typeof(UserView));
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var item = GetItemById(id) as UserView;
@@ -2346,7 +2417,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
_providerManagerFactory().QueueRefresh(
ProviderManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -2524,7 +2595,7 @@ namespace Emby.Server.Implementations.Library
Anime series don't generally have a season in their file name, however,
tvdb needs a season to correctly get the metadata.
Hence, a null season needs to be filled with something. */
//FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
// FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
episode.ParentIndexNumber = 1;
}
@@ -2675,8 +2746,8 @@ namespace Emby.Server.Implementations.Library
}
}
var metadataPath = ConfigurationManager.Configuration.MetadataPath;
var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath;
var metadataPath = _configurationManager.Configuration.MetadataPath;
var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath;
if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
{
@@ -2687,7 +2758,7 @@ namespace Emby.Server.Implementations.Library
}
}
foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
foreach (var map in _configurationManager.Configuration.PathSubstitutions)
{
if (!string.IsNullOrWhiteSpace(map.From))
{
@@ -2713,10 +2784,12 @@ namespace Emby.Server.Implementations.Library
{
throw new ArgumentNullException(nameof(path));
}
if (string.IsNullOrWhiteSpace(from))
{
throw new ArgumentNullException(nameof(from));
}
if (string.IsNullOrWhiteSpace(to))
{
throw new ArgumentNullException(nameof(to));
@@ -2756,7 +2829,7 @@ namespace Emby.Server.Implementations.Library
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
{
return ItemRepository.GetPeople(query);
return _itemRepository.GetPeople(query);
}
public List<PersonInfo> GetPeople(BaseItem item)
@@ -2779,7 +2852,7 @@ namespace Emby.Server.Implementations.Library
public List<Person> GetPeopleItems(InternalPeopleQuery query)
{
return ItemRepository.GetPeopleNames(query).Select(i =>
return _itemRepository.GetPeopleNames(query).Select(i =>
{
try
{
@@ -2790,13 +2863,12 @@ namespace Emby.Server.Implementations.Library
_logger.LogError(ex, "Error getting person");
return null;
}
}).Where(i => i != null).ToList();
}
public List<string> GetPeopleNames(InternalPeopleQuery query)
{
return ItemRepository.GetPeopleNames(query);
return _itemRepository.GetPeopleNames(query);
}
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
@@ -2806,7 +2878,7 @@ namespace Emby.Server.Implementations.Library
return;
}
ItemRepository.UpdatePeople(item.Id, people);
_itemRepository.UpdatePeople(item.Id, people);
}
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
@@ -2817,7 +2889,7 @@ namespace Emby.Server.Implementations.Library
{
_logger.LogDebug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url);
await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
await ProviderManager.SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
@@ -2850,7 +2922,7 @@ namespace Emby.Server.Implementations.Library
name = _fileSystem.GetValidFilename(name);
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, name);
while (Directory.Exists(virtualFolderPath))
@@ -2869,7 +2941,7 @@ namespace Emby.Server.Implementations.Library
}
}
_libraryMonitorFactory().Stop();
LibraryMonitor.Stop();
try
{
@@ -2904,7 +2976,7 @@ namespace Emby.Server.Implementations.Library
{
// Need to add a delay here or directory watchers may still pick up the changes
await Task.Delay(1000).ConfigureAwait(false);
_libraryMonitorFactory().Start();
LibraryMonitor.Start();
}
}
}
@@ -2920,7 +2992,7 @@ namespace Emby.Server.Implementations.Library
private static bool ValidateNetworkPath(string path)
{
//if (Environment.OSVersion.Platform == PlatformID.Win32NT)
// if (Environment.OSVersion.Platform == PlatformID.Win32NT)
//{
// // We can't validate protocol-based paths, so just allow them
// if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
@@ -2964,7 +3036,7 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The network path does not exist.");
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var shortcutFilename = Path.GetFileNameWithoutExtension(path);
@@ -3007,7 +3079,7 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The network path does not exist.");
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
@@ -3060,7 +3132,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(name));
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var path = Path.Combine(rootFolderPath, name);
@@ -3069,7 +3141,7 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The media folder does not exist");
}
_libraryMonitorFactory().Stop();
LibraryMonitor.Stop();
try
{
@@ -3089,7 +3161,7 @@ namespace Emby.Server.Implementations.Library
{
// Need to add a delay here or directory watchers may still pick up the changes
await Task.Delay(1000).ConfigureAwait(false);
_libraryMonitorFactory().Start();
LibraryMonitor.Start();
}
}
}
@@ -3103,7 +3175,7 @@ namespace Emby.Server.Implementations.Library
var removeList = new List<NameValuePair>();
foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
foreach (var contentType in _configurationManager.Configuration.ContentTypes)
{
if (string.IsNullOrWhiteSpace(contentType.Name))
{
@@ -3118,11 +3190,11 @@ namespace Emby.Server.Implementations.Library
if (removeList.Count > 0)
{
ConfigurationManager.Configuration.ContentTypes = ConfigurationManager.Configuration.ContentTypes
_configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes
.Except(removeList)
.ToArray();
.ToArray();
ConfigurationManager.SaveConfiguration();
_configurationManager.SaveConfiguration();
}
}
@@ -3133,7 +3205,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(mediaPath));
}
var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
if (!Directory.Exists(virtualFolderPath))

View File

@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library
{
mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath);
//_logger.LogDebug("Found cached media info");
// _logger.LogDebug("Found cached media info");
}
catch
{
@@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.Library
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
_json.SerializeToFile(mediaInfo, cacheFilePath);
//_logger.LogDebug("Saved media info to {0}", cacheFilePath);
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
}
@@ -148,17 +148,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;

View File

@@ -7,6 +7,8 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
@@ -14,7 +16,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;
@@ -33,13 +34,13 @@ namespace Emby.Server.Implementations.Library
private readonly ILibraryManager _libraryManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly ILogger<MediaSourceManager> _logger;
private readonly IUserDataManager _userDataManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths;
private IMediaSourceProvider[] _providers;
private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager;
private readonly Func<IMediaEncoder> _mediaEncoder;
private ILocalizationManager _localizationManager;
private IApplicationPaths _appPaths;
public MediaSourceManager(
IItemRepository itemRepo,
@@ -47,16 +48,16 @@ namespace Emby.Server.Implementations.Library
ILocalizationManager localizationManager,
IUserManager userManager,
ILibraryManager libraryManager,
ILoggerFactory loggerFactory,
ILogger<MediaSourceManager> logger,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IUserDataManager userDataManager,
Func<IMediaEncoder> mediaEncoder)
IMediaEncoder mediaEncoder)
{
_itemRepo = itemRepo;
_userManager = userManager;
_libraryManager = libraryManager;
_logger = loggerFactory.CreateLogger(nameof(MediaSourceManager));
_logger = logger;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
@@ -190,10 +191,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);
}
}
}
@@ -207,22 +205,27 @@ namespace Emby.Server.Implementations.Library
{
return MediaProtocol.Rtsp;
}
if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Rtmp;
}
if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Http;
}
if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Rtp;
}
if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Ftp;
}
if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Udp;
@@ -352,7 +355,9 @@ namespace Emby.Server.Implementations.Library
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, 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 +368,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)
{
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,11 +399,11 @@ 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)
@@ -435,7 +441,6 @@ namespace Emby.Server.Implementations.Library
}
return 1;
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
.ThenByDescending(i =>
{
@@ -496,7 +501,7 @@ namespace Emby.Server.Implementations.Library
// hack - these two values were taken from LiveTVMediaSourceProvider
string cacheKey = request.OpenToken;
await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths)
await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _appPaths)
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
.ConfigureAwait(false);
}
@@ -521,11 +526,7 @@ namespace Emby.Server.Implementations.Library
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
}
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse
{
MediaSource = clone
}, liveStream as IDirectStreamProvider);
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);
}
private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio)
@@ -538,7 +539,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.RunTimeTicks = null;
}
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
if (audioStream == null || audioStream.Index == -1)
{
@@ -549,7 +550,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
}
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
if (videoStream != null)
{
if (!videoStream.BitRate.HasValue)
@@ -560,17 +561,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;
@@ -621,12 +619,11 @@ namespace Emby.Server.Implementations.Library
if (liveStreamInfo is IDirectStreamProvider)
{
var info = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest
var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
{
MediaSource = mediaSource,
ExtractChapters = false,
MediaType = DlnaProfileType.Video
}, cancellationToken).ConfigureAwait(false);
mediaSource.MediaStreams = info.MediaStreams;
@@ -652,7 +649,7 @@ namespace Emby.Server.Implementations.Library
{
mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath);
//_logger.LogDebug("Found cached media info");
// _logger.LogDebug("Found cached media info");
}
catch (Exception ex)
{
@@ -674,20 +671,21 @@ 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)
{
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
_jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
//_logger.LogDebug("Saved media info to {0}", cacheFilePath);
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
}
@@ -753,17 +751,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;

View File

@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Configuration;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library
@@ -35,7 +35,8 @@ namespace Emby.Server.Implementations.Library
return null;
}
public static int? GetDefaultSubtitleStreamIndex(List<MediaStream> streams,
public static int? GetDefaultSubtitleStreamIndex(
List<MediaStream> streams,
string[] preferredLanguages,
SubtitlePlaybackMode mode,
string audioTrackLanguage)
@@ -115,7 +116,8 @@ namespace Emby.Server.Implementations.Library
.ThenBy(i => i.Index);
}
public static void SetSubtitleStreamScores(List<MediaStream> streams,
public static void SetSubtitleStreamScores(
List<MediaStream> streams,
string[] preferredLanguages,
SubtitlePlaybackMode mode,
string audioTrackLanguage)

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -10,6 +11,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
namespace Emby.Server.Implementations.Library
{
@@ -75,7 +77,6 @@ namespace Emby.Server.Implementations.Library
{
return Guid.Empty;
}
}).Where(i => !i.Equals(Guid.Empty)).ToArray();
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
@@ -105,32 +106,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);
}

View File

@@ -1,3 +1,5 @@
#nullable enable
using System;
using System.Text.RegularExpressions;
@@ -12,24 +14,24 @@ namespace Emby.Server.Implementations.Library
/// Gets the attribute value.
/// </summary>
/// <param name="str">The STR.</param>
/// <param name="attrib">The attrib.</param>
/// <param name="attribute">The attrib.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">attrib</exception>
public static string GetAttributeValue(this string str, string attrib)
/// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
public static string? GetAttributeValue(this string str, string attribute)
{
if (string.IsNullOrEmpty(str))
if (str.Length == 0)
{
throw new ArgumentNullException(nameof(str));
throw new ArgumentException("String can't be empty.", nameof(str));
}
if (string.IsNullOrEmpty(attrib))
if (attribute.Length == 0)
{
throw new ArgumentNullException(nameof(attrib));
throw new ArgumentException("String can't be empty.", nameof(attribute));
}
string srch = "[" + attrib + "=";
string srch = "[" + attribute + "=";
int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
if (start > -1)
if (start != -1)
{
start += srch.Length;
int end = str.IndexOf(']', start);
@@ -37,9 +39,9 @@ namespace Emby.Server.Implementations.Library
}
// for imdbid we also accept pattern matching
if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
{
var m = Regex.Match(str, "tt\\d{7}", RegexOptions.IgnoreCase);
var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
return m.Success ? m.Value : null;
}

View File

@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Ensures DateCreated and DateModified have values
/// Ensures DateCreated and DateModified have values.
/// </summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="item">The item.</param>
@@ -118,10 +118,12 @@ namespace Emby.Server.Implementations.Library
{
throw new ArgumentNullException(nameof(fileSystem));
}
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
if (args == null)
{
throw new ArgumentNullException(nameof(args));

View File

@@ -209,8 +209,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
Name = parseName ?
resolvedItem.Name :
Path.GetFileNameWithoutExtension(firstMedia.Path),
//AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
//LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
// AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
// LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
};
result.Items.Add(libraryItem);

View File

@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// </summary>
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
{
private readonly ILogger _logger;
private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <param name="logger">The logger.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
public MusicAlbumResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager)
public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_logger = logger;
_fileSystem = fileSystem;
@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
// Args points to an album if parent is an Artist folder or it directly contains music
if (args.IsDirectory)
{
// if (args.Parent is MusicArtist) return true; //saves us from testing children twice
// if (args.Parent is MusicArtist) return true; // saves us from testing children twice
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
{
return true;
@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
IEnumerable<FileSystemMetadata> list,
bool allowSubfolders,
IDirectoryService directoryService,
ILogger logger,
ILogger<MusicAlbumResolver> logger,
IFileSystem fileSystem,
ILibraryManager libraryManager)
{

View File

@@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// </summary>
public class MusicArtistResolver : ItemResolver<MusicArtist>
{
private readonly ILogger _logger;
private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
@@ -23,12 +23,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <summary>
/// Initializes a new instance of the <see cref="MusicArtistResolver"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="logger">The logger for the created <see cref="MusicAlbumResolver"/> instances.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="config">The configuration manager.</param>
public MusicArtistResolver(
ILogger<MusicArtistResolver> logger,
ILogger<MusicAlbumResolver> logger,
IFileSystem fileSystem,
ILibraryManager libraryManager,
IServerConfigurationManager config)

View File

@@ -292,7 +292,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
return true;
//var blurayExtensions = new[]
// var blurayExtensions = new[]
//{
// ".mts",
// ".m2ts",
@@ -300,7 +300,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
// ".mpls"
//};
//return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
// return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
}
}
}

View File

@@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
{
public class BookResolver : MediaBrowser.Controller.Resolvers.ItemResolver<Book>
{
private readonly string[] _validExtensions = { ".pdf", ".epub", ".mobi", ".cbr", ".cbz", ".azw3" };
private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".opf", ".pdf" };
protected override Book Resolve(ItemResolveArgs args)
{

View File

@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
public virtual ResolverPriority Priority => ResolverPriority.First;
/// <summary>
/// Sets initial values on the newly resolved item
/// Sets initial values on the newly resolved item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>

View File

@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrEmpty(id))
{
item.SetProviderId(MetadataProviders.Tmdb, id);
item.SetProviderId(MetadataProvider.Tmdb, id);
}
}
}

View File

@@ -350,7 +350,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrWhiteSpace(tmdbid))
{
item.SetProviderId(MetadataProviders.Tmdb, tmdbid);
item.SetProviderId(MetadataProvider.Tmdb, tmdbid);
}
}
@@ -361,7 +361,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrWhiteSpace(imdbid))
{
item.SetProviderId(MetadataProviders.Imdb, imdbid);
item.SetProviderId(MetadataProvider.Imdb, imdbid);
}
}
}

View File

@@ -41,10 +41,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
return new AggregateFolder();
}
if (string.Equals(args.Path, _appPaths.DefaultUserViewsPath, StringComparison.OrdinalIgnoreCase))
{
return new UserRootFolder(); //if we got here and still a root - must be user root
return new UserRootFolder(); // if we got here and still a root - must be user root
}
if (args.IsVf)
{
return new CollectionFolder
@@ -73,7 +75,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
return false;
}
})
.Select(i => _fileSystem.GetFileNameWithoutExtension(i))
.FirstOrDefault();

View File

@@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
episode.SeriesId = series.Id;
episode.SeriesName = series.Name;
}
if (season != null)
{
episode.SeasonId = season.Id;

View File

@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
private readonly ILogger _logger;
private readonly ILogger<SeasonResolver> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="SeasonResolver"/> class.
@@ -94,7 +94,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
_localization.GetLocalizedString("NameSeasonNumber"),
seasonNumber,
args.GetLibraryOptions().PreferredMetadataLanguage);
}
return season;

View File

@@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
public class SeriesResolver : FolderResolver<Series>
{
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
private readonly ILogger<SeriesResolver> _logger;
private readonly ILibraryManager _libraryManager;
/// <summary>
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
//if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
// if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
//{
// return new Series
// {
@@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
IEnumerable<FileSystemMetadata> fileSystemChildren,
IDirectoryService directoryService,
IFileSystem fileSystem,
ILogger logger,
ILogger<SeriesResolver> logger,
ILibraryManager libraryManager,
bool isTvContentType)
{
@@ -217,7 +217,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
if (!string.IsNullOrEmpty(id))
{
item.SetProviderId(MetadataProviders.Tvdb, id);
item.SetProviderId(MetadataProvider.Tvdb, id);
}
}
}

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -12,21 +13,22 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
using Microsoft.Extensions.Logging;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library
{
public class SearchEngine : ISearchEngine
{
private readonly ILogger<SearchEngine> _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
public SearchEngine(ILoggerFactory loggerFactory, ILibraryManager libraryManager, IUserManager userManager)
public SearchEngine(ILogger<SearchEngine> logger, ILibraryManager libraryManager, IUserManager userManager)
{
_logger = logger;
_libraryManager = libraryManager;
_userManager = userManager;
_logger = loggerFactory.CreateLogger("SearchEngine");
}
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
@@ -192,6 +194,7 @@ namespace Emby.Server.Implementations.Library
{
searchQuery.AncestorIds = new[] { searchQuery.ParentId };
}
searchQuery.ParentId = Guid.Empty;
searchQuery.IncludeItemsByName = true;
searchQuery.IncludeItemTypes = Array.Empty<string>();
@@ -205,7 +208,6 @@ namespace Emby.Server.Implementations.Library
return mediaItems.Select(i => new SearchHintInfo
{
Item = i
}).ToList();
}
}

View File

@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -13,6 +14,7 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
namespace Emby.Server.Implementations.Library
{
@@ -26,27 +28,26 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary<string, UserItemData> _userData =
new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger;
private readonly ILogger<UserDataManager> _logger;
private readonly IServerConfigurationManager _config;
private readonly IUserManager _userManager;
private readonly IUserDataRepository _repository;
private Func<IUserManager> _userManager;
public UserDataManager(ILoggerFactory loggerFactory, IServerConfigurationManager config, Func<IUserManager> userManager)
public UserDataManager(
ILogger<UserDataManager> logger,
IServerConfigurationManager config,
IUserManager userManager,
IUserDataRepository repository)
{
_logger = logger;
_config = config;
_logger = loggerFactory.CreateLogger(GetType().Name);
_userManager = userManager;
_repository = repository;
}
/// <summary>
/// Gets or sets the repository.
/// </summary>
/// <value>The repository.</value>
public IUserDataRepository Repository { get; set; }
public void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
var user = _userManager().GetUserById(userId);
var user = _userManager.GetUserById(userId);
SaveUserData(user, item, userData, reason, cancellationToken);
}
@@ -71,7 +72,7 @@ namespace Emby.Server.Implementations.Library
foreach (var key in keys)
{
Repository.SaveUserData(userId, key, userData, cancellationToken);
_repository.SaveUserData(userId, key, userData, cancellationToken);
}
var cacheKey = GetCacheKey(userId, item.Id);
@@ -96,26 +97,26 @@ namespace Emby.Server.Implementations.Library
/// <returns></returns>
public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
{
var user = _userManager().GetUserById(userId);
var user = _userManager.GetUserById(userId);
Repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
_repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
}
/// <summary>
/// Retrieve all user data for the given user
/// Retrieve all user data for the given user.
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public List<UserItemData> GetAllUserData(Guid userId)
{
var user = _userManager().GetUserById(userId);
var user = _userManager.GetUserById(userId);
return Repository.GetAllUserData(user.InternalId);
return _repository.GetAllUserData(user.InternalId);
}
public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
{
var user = _userManager().GetUserById(userId);
var user = _userManager.GetUserById(userId);
return GetUserData(user, itemId, keys);
}
@@ -131,7 +132,7 @@ namespace Emby.Server.Implementations.Library
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
{
var userData = Repository.GetUserData(internalUserId, keys);
var userData = _repository.GetUserData(internalUserId, keys);
if (userData != null)
{
@@ -187,7 +188,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Converts a UserItemData to a DTOUserItemData
/// Converts a UserItemData to a DTOUserItemData.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>DtoUserItemData.</returns>
@@ -241,7 +242,7 @@ namespace Emby.Server.Implementations.Library
{
// Enforce MinResumeDuration
var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds;
if (durationSeconds < _config.Configuration.MinResumeDurationSeconds)
if (durationSeconds < _config.Configuration.MinResumeDurationSeconds && !(item is Book))
{
positionTicks = 0;
data.Played = playedToCompletion = true;

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
@@ -17,6 +19,8 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library
{
@@ -125,12 +129,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 +169,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)))
{
@@ -270,7 +280,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 +342,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)

View File

@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly ILogger<ArtistsValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="itemRepo">The item repository.</param>
public ArtistsPostScanTask(
ILibraryManager libraryManager,
ILogger<ArtistsPostScanTask> logger,
ILogger<ArtistsValidator> logger,
IItemRepository itemRepo)
{
_libraryManager = libraryManager;

View File

@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly ILogger<ArtistsValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
@@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
public ArtistsValidator(ILibraryManager libraryManager, ILogger<ArtistsValidator> logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
@@ -98,7 +98,6 @@ namespace Emby.Server.Implementations.Library.Validators
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
}, false);
}

View File

@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly ILogger<GenresValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="itemRepo">The item repository.</param>
public GenresPostScanTask(
ILibraryManager libraryManager,
ILogger<GenresPostScanTask> logger,
ILogger<GenresValidator> logger,
IItemRepository itemRepo)
{
_libraryManager = libraryManager;

View File

@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly ILogger<GenresValidator> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="GenresValidator"/> class.
@@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;

View File

@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly ILogger<MusicGenresValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="itemRepo">The item repository.</param>
public MusicGenresPostScanTask(
ILibraryManager libraryManager,
ILogger<MusicGenresPostScanTask> logger,
ILogger<MusicGenresValidator> logger,
IItemRepository itemRepo)
{
_libraryManager = libraryManager;

View File

@@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly ILogger<MusicGenresValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
@@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
public MusicGenresValidator(ILibraryManager libraryManager, ILogger<MusicGenresValidator> logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;

View File

@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly ILogger<StudiosValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="itemRepo">The item repository.</param>
public StudiosPostScanTask(
ILibraryManager libraryManager,
ILogger<StudiosPostScanTask> logger,
ILogger<StudiosValidator> logger,
IItemRepository itemRepo)
{
_libraryManager = libraryManager;

View File

@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly ILogger<StudiosValidator> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="StudiosValidator" /> class.
@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
public StudiosValidator(ILibraryManager libraryManager, ILogger<StudiosValidator> logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
@@ -92,7 +92,6 @@ namespace Emby.Server.Implementations.Library.Validators
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
}, false);
}

View File

@@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private const int TunerDiscoveryDurationMs = 3000;
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly ILogger<EmbyTV> _logger;
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
private readonly IJsonSerializer _jsonSerializer;
@@ -140,11 +140,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
if (string.Equals(e.Key, "livetv", StringComparison.OrdinalIgnoreCase))
{
OnRecordingFoldersChanged();
await CreateRecordingFolders().ConfigureAwait(false);
}
}
@@ -155,11 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return CreateRecordingFolders();
}
private async void OnRecordingFoldersChanged()
{
await CreateRecordingFolders().ConfigureAwait(false);
}
internal async Task CreateRecordingFolders()
{
try
@@ -1059,7 +1054,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var stream = new MediaSourceInfo
{
EncoderPath = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
EncoderPath = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
EncoderProtocol = MediaProtocol.Http,
Path = info.Path,
Protocol = MediaProtocol.File,
@@ -1334,7 +1329,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
await CreateRecordingFolders().ConfigureAwait(false);
TriggerRefresh(recordPath);
EnforceKeepUpTo(timer, seriesPath);
await EnforceKeepUpTo(timer, seriesPath).ConfigureAwait(false);
};
await recorder.Record(directStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false);
@@ -1494,7 +1489,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return item;
}
private async void EnforceKeepUpTo(TimerInfo timer, string seriesPath)
private async Task EnforceKeepUpTo(TimerInfo timer, string seriesPath)
{
if (string.IsNullOrWhiteSpace(timer.SeriesTimerId))
{
@@ -1552,7 +1547,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IsFolder = false,
Recursive = true,
DtoOptions = new DtoOptions(true)
})
.Where(i => i.IsFileProtocol && File.Exists(i.Path))
.Skip(seriesTimer.KeepUpTo - 1)
@@ -1898,22 +1892,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteStartDocument(true);
writer.WriteStartElement("tvshow");
string id;
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out id))
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out id))
{
writer.WriteElementString("id", id);
}
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id))
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out id))
{
writer.WriteElementString("imdb_id", id);
}
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out id))
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out id))
{
writer.WriteElementString("tmdbid", id);
}
if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out id))
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out id))
{
writer.WriteElementString("zap2itid", id);
}
@@ -2080,14 +2074,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteElementString("credits", person);
}
var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection);
var tmdbCollection = item.GetProviderId(MetadataProvider.TmdbCollection);
if (!string.IsNullOrEmpty(tmdbCollection))
{
writer.WriteElementString("collectionnumber", tmdbCollection);
}
var imdb = item.GetProviderId(MetadataProviders.Imdb);
var imdb = item.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(imdb))
{
if (!isSeriesEpisode)
@@ -2101,7 +2095,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
lockData = false;
}
var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
var tvdb = item.GetProviderId(MetadataProvider.Tvdb);
if (!string.IsNullOrEmpty(tvdb))
{
writer.WriteElementString("tvdbid", tvdb);
@@ -2110,7 +2104,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
lockData = false;
}
var tmdb = item.GetProviderId(MetadataProviders.Tmdb);
var tmdb = item.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(tmdb))
{
writer.WriteElementString("tmdbid", tmdb);

View File

@@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
onStarted();
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
_ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
@@ -183,7 +183,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";
//var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
// var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
// " -f mp4 -movflags frag_keyframe+empty_moov" :
// string.Empty;
@@ -206,13 +206,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return "-codec:a:0 copy";
//var audioChannels = 2;
//var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
//if (audioStream != null)
// var audioChannels = 2;
// var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
// if (audioStream != null)
//{
// audioChannels = audioStream.Channels ?? audioChannels;
//}
//return "-codec:a:0 aac -strict experimental -ab 320000";
// return "-codec:a:0 aac -strict experimental -ab 320000";
}
private static bool EncodeVideo(MediaSourceInfo mediaSource)
@@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
private async void StartStreamingLog(Stream source, Stream target)
private async Task StartStreamingLog(Stream source, Stream target)
{
try
{

View File

@@ -56,7 +56,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
name += " " + info.EpisodeTitle;
}
}
else if (info.IsMovie && info.ProductionYear != null)
{
name += " (" + info.ProductionYear + ")";

View File

@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (startDate < now)
{
TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo> { Argument = item });
TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo>(item));
return;
}
@@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
if (timer != null)
{
TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo> { Argument = timer });
TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
}
}

View File

@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
public class SchedulesDirect : IListingsProvider
{
private readonly ILogger _logger;
private readonly ILogger<SchedulesDirect> _logger;
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
@@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var programsInfo = new List<ProgramInfo>();
foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
{
//_logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
// _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
// " which corresponds to channel " + channelNumber + " and program id " +
// schedule.programID + " which says it has images? " +
// programDict[schedule.programID].hasImageArtwork);
@@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
//programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
// GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
// GetProgramImage(ApiUrl, data, "Banner-LOT", false);
@@ -212,6 +212,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
channelNumber = map.channel;
}
if (string.IsNullOrWhiteSpace(channelNumber))
{
channelNumber = map.atscMajor + "." + map.atscMinor;
@@ -276,7 +277,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CommunityRating = null,
EpisodeTitle = episodeTitle,
Audio = audioType,
//IsNew = programInfo.@new ?? false,
// IsNew = programInfo.@new ?? false,
IsRepeat = programInfo.@new == null,
IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase),
ImageUrl = details.primaryImage,
@@ -342,7 +343,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
info.SeriesId = programId.Substring(0, 10);
info.SeriesProviderIds[MetadataProviders.Zap2It.ToString()] = info.SeriesId;
info.SeriesProviderIds[MetadataProvider.Zap2It.ToString()] = info.SeriesId;
if (details.metadata != null)
{
@@ -400,6 +401,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
}
return date;
}
@@ -622,6 +624,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_lastErrorResponse = DateTime.UtcNow;
}
}
throw;
}
finally
@@ -701,7 +704,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CancellationToken = cancellationToken,
LogErrorResponseBody = true
};
//_logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
// _logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
// httpOptions.RequestContent);
using (var response = await Post(httpOptions, false, null).ConfigureAwait(false))
@@ -805,11 +808,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
throw new ArgumentException("Username is required");
}
if (string.IsNullOrEmpty(info.Password))
{
throw new ArgumentException("Password is required");
}
}
if (validateListings)
{
if (string.IsNullOrEmpty(info.ListingsId))
@@ -932,24 +937,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Token
{
public int code { get; set; }
public string message { get; set; }
public string serverID { get; set; }
public string token { get; set; }
}
public class Lineup
{
public string lineup { get; set; }
public string name { get; set; }
public string transport { get; set; }
public string location { get; set; }
public string uri { get; set; }
}
public class Lineups
{
public int code { get; set; }
public string serverID { get; set; }
public string datetime { get; set; }
public List<Lineup> lineups { get; set; }
}
@@ -957,8 +973,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Headends
{
public string headend { get; set; }
public string transport { get; set; }
public string location { get; set; }
public List<Lineup> lineups { get; set; }
}
@@ -967,59 +986,83 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Map
{
public string stationID { get; set; }
public string channel { get; set; }
public string logicalChannelNumber { get; set; }
public int uhfVhf { get; set; }
public int atscMajor { get; set; }
public int atscMinor { get; set; }
}
public class Broadcaster
{
public string city { get; set; }
public string state { get; set; }
public string postalcode { get; set; }
public string country { get; set; }
}
public class Logo
{
public string URL { get; set; }
public int height { get; set; }
public int width { get; set; }
public string md5 { get; set; }
}
public class Station
{
public string stationID { get; set; }
public string name { get; set; }
public string callsign { get; set; }
public List<string> broadcastLanguage { get; set; }
public List<string> descriptionLanguage { get; set; }
public Broadcaster broadcaster { get; set; }
public string affiliate { get; set; }
public Logo logo { get; set; }
public bool? isCommercialFree { get; set; }
}
public class Metadata
{
public string lineup { get; set; }
public string modified { get; set; }
public string transport { get; set; }
}
public class Channel
{
public List<Map> map { get; set; }
public List<Station> stations { get; set; }
public Metadata metadata { get; set; }
}
public class RequestScheduleForChannel
{
public string stationID { get; set; }
public List<string> date { get; set; }
}
@@ -1029,29 +1072,43 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Rating
{
public string body { get; set; }
public string code { get; set; }
}
public class Multipart
{
public int partNumber { get; set; }
public int totalParts { get; set; }
}
public class Program
{
public string programID { get; set; }
public string airDateTime { get; set; }
public int duration { get; set; }
public string md5 { get; set; }
public List<string> audioProperties { get; set; }
public List<string> videoProperties { get; set; }
public List<Rating> ratings { get; set; }
public bool? @new { get; set; }
public Multipart multipart { get; set; }
public string liveTapeDelay { get; set; }
public bool premiere { get; set; }
public bool repeat { get; set; }
public string isPremiereOrFinale { get; set; }
}
@@ -1060,16 +1117,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class MetadataSchedule
{
public string modified { get; set; }
public string md5 { get; set; }
public string startDate { get; set; }
public string endDate { get; set; }
public int days { get; set; }
}
public class Day
{
public string stationID { get; set; }
public List<Program> programs { get; set; }
public MetadataSchedule metadata { get; set; }
public Day()
@@ -1092,24 +1155,28 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class Description100
{
public string descriptionLanguage { get; set; }
public string description { get; set; }
}
public class Description1000
{
public string descriptionLanguage { get; set; }
public string description { get; set; }
}
public class DescriptionsProgram
{
public List<Description100> description100 { get; set; }
public List<Description1000> description1000 { get; set; }
}
public class Gracenote
{
public int season { get; set; }
public int episode { get; set; }
}
@@ -1121,104 +1188,154 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class ContentRating
{
public string body { get; set; }
public string code { get; set; }
}
public class Cast
{
public string billingOrder { get; set; }
public string role { get; set; }
public string nameId { get; set; }
public string personId { get; set; }
public string name { get; set; }
public string characterName { get; set; }
}
public class Crew
{
public string billingOrder { get; set; }
public string role { get; set; }
public string nameId { get; set; }
public string personId { get; set; }
public string name { get; set; }
}
public class QualityRating
{
public string ratingsBody { get; set; }
public string rating { get; set; }
public string minRating { get; set; }
public string maxRating { get; set; }
public string increment { get; set; }
}
public class Movie
{
public string year { get; set; }
public int duration { get; set; }
public List<QualityRating> qualityRating { get; set; }
}
public class Recommendation
{
public string programID { get; set; }
public string title120 { get; set; }
}
public class ProgramDetails
{
public string audience { get; set; }
public string programID { get; set; }
public List<Title> titles { get; set; }
public EventDetails eventDetails { get; set; }
public DescriptionsProgram descriptions { get; set; }
public string originalAirDate { get; set; }
public List<string> genres { get; set; }
public string episodeTitle150 { get; set; }
public List<MetadataPrograms> metadata { get; set; }
public List<ContentRating> contentRating { get; set; }
public List<Cast> cast { get; set; }
public List<Crew> crew { get; set; }
public string entityType { get; set; }
public string showType { get; set; }
public bool hasImageArtwork { get; set; }
public string primaryImage { get; set; }
public string thumbImage { get; set; }
public string backdropImage { get; set; }
public string bannerImage { get; set; }
public string imageID { get; set; }
public string md5 { get; set; }
public List<string> contentAdvisory { get; set; }
public Movie movie { get; set; }
public List<Recommendation> recommendations { get; set; }
}
public class Caption
{
public string content { get; set; }
public string lang { get; set; }
}
public class ImageData
{
public string width { get; set; }
public string height { get; set; }
public string uri { get; set; }
public string size { get; set; }
public string aspect { get; set; }
public string category { get; set; }
public string text { get; set; }
public string primary { get; set; }
public string tier { get; set; }
public Caption caption { get; set; }
}
public class ShowImages
{
public string programID { get; set; }
public List<ImageData> data { get; set; }
}
}
}
}

View File

@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
private readonly ILogger<XmlTvListingsProvider> _logger;
private readonly IFileSystem _fileSystem;
private readonly IZipClient _zipClient;
@@ -224,6 +224,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
uniqueString = "-" + programInfo.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture);
}
if (programInfo.EpisodeNumber.HasValue)
{
uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture);

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