Merge branch 'master' into websocket

This commit is contained in:
Bond_009
2020-05-02 00:54:04 +02:00
1246 changed files with 23673 additions and 14062 deletions

View File

@@ -1,6 +1,3 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -9,7 +6,6 @@ using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
@@ -29,7 +25,10 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
public class ActivityLogEntryPoint : IServerEntryPoint
/// <summary>
/// Entry point for the activity logger.
/// </summary>
public sealed class ActivityLogEntryPoint : IServerEntryPoint
{
private readonly ILogger _logger;
private readonly IInstallationManager _installationManager;
@@ -39,22 +38,20 @@ namespace Emby.Server.Implementations.Activity
private readonly ILocalizationManager _localization;
private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager;
private readonly IServerApplicationHost _appHost;
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="deviceManager">The device 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,
@@ -64,8 +61,7 @@ namespace Emby.Server.Implementations.Activity
ILocalizationManager localization,
IInstallationManager installationManager,
ISubtitleManager subManager,
IUserManager userManager,
IServerApplicationHost appHost)
IUserManager userManager)
{
_logger = logger;
_sessionManager = sessionManager;
@@ -76,9 +72,9 @@ namespace Emby.Server.Implementations.Activity
_installationManager = installationManager;
_subManager = subManager;
_userManager = userManager;
_appHost = appHost;
}
/// <inheritdoc />
public Task RunAsync()
{
_taskManager.TaskCompleted += OnTaskCompleted;
@@ -141,7 +137,7 @@ namespace Emby.Server.Implementations.Activity
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
e.Provider,
Notifications.Notifications.GetItemName(e.Item)),
Notifications.NotificationEntryPoint.GetItemName(e.Item)),
Type = "SubtitleDownloadFailure",
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
ShortOverview = e.Exception.Message
@@ -173,7 +169,12 @@ namespace Emby.Server.Implementations.Activity
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
user.Name,
GetItemName(item),
e.DeviceName),
Type = GetPlaybackStoppedNotificationType(item.MediaType),
UserId = user.Id
});
@@ -264,31 +265,20 @@ namespace Emby.Server.Implementations.Activity
private 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(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOfflineFromDevice"),
session.UserName,
session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
{
Name = name,
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOfflineFromDevice"),
session.UserName,
session.DeviceName),
Type = "SessionEnded",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -387,31 +377,20 @@ namespace Emby.Server.Implementations.Activity
private 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(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOnlineFromDevice"),
session.UserName,
session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
{
Name = name,
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOnlineFromDevice"),
session.UserName,
session.DeviceName),
Type = "SessionStarted",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -421,7 +400,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -433,8 +412,8 @@ namespace Emby.Server.Implementations.Activity
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
e.Argument.Item2.versionStr),
Overview = e.Argument.Item2.description
e.Argument.Item2.version),
Overview = e.Argument.Item2.changelog
});
}
@@ -450,7 +429,7 @@ namespace Emby.Server.Implementations.Activity
});
}
private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -462,7 +441,7 @@ namespace Emby.Server.Implementations.Activity
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
e.Argument.versionStr)
e.Argument.version)
});
}
@@ -490,8 +469,8 @@ namespace Emby.Server.Implementations.Activity
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;
}
@@ -533,6 +512,7 @@ namespace Emby.Server.Implementations.Activity
private void CreateLogEntry(ActivityLogEntry entry)
=> _activityManager.Create(entry);
/// <inheritdoc />
public void Dispose()
{
_taskManager.TaskCompleted -= OnTaskCompleted;
@@ -564,7 +544,7 @@ namespace Emby.Server.Implementations.Activity
/// <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;
@@ -578,7 +558,7 @@ namespace Emby.Server.Implementations.Activity
{
int years = days / DaysInYear;
values.Add(CreateValueString(years, "year"));
days = days % DaysInYear;
days %= DaysInYear;
}
// Number of months
@@ -586,7 +566,7 @@ namespace Emby.Server.Implementations.Activity
{
int months = days / DaysInMonth;
values.Add(CreateValueString(months, "month"));
days = days % DaysInMonth;
days %= DaysInMonth;
}
// Number of days

View File

@@ -1,34 +1,33 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Linq;
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
{
/// <summary>
/// The activity log manager.
/// </summary>
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)
/// <summary>
/// Initializes a new instance of the <see cref="ActivityManager"/> class.
/// </summary>
/// <param name="repo">The activity repository.</param>
/// <param name="userManager">The user manager.</param>
public ActivityManager(IActivityRepository repo, IUserManager userManager)
{
_logger = loggerFactory.CreateLogger(nameof(ActivityManager));
_repo = repo;
_userManager = userManager;
}
/// <inheritdoc />
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
public void Create(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;
@@ -38,6 +37,7 @@ namespace Emby.Server.Implementations.Activity
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
}
/// <inheritdoc />
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
@@ -61,6 +61,7 @@ namespace Emby.Server.Implementations.Activity
return result;
}
/// <inheritdoc />
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
return GetActivityLogEntries(minDate, null, startIndex, limit);

View File

@@ -1,6 +1,3 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -16,18 +13,31 @@ using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Activity
{
/// <summary>
/// The activity log repository.
/// </summary>
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
private readonly IFileSystem _fileSystem;
public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
/// <summary>
/// Initializes a new instance of the <see cref="ActivityRepository"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="appPaths">The server application paths.</param>
/// <param name="fileSystem">The filesystem.</param>
public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
: base(logger)
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
_fileSystem = fileSystem;
}
/// <summary>
/// Initializes the <see cref="ActivityRepository"/>.
/// </summary>
public void Initialize()
{
try
@@ -46,16 +56,14 @@ namespace Emby.Server.Implementations.Activity
private void InitializeInternal()
{
using (var connection = GetConnection())
using var connection = GetConnection();
connection.RunQueries(new[]
{
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"
});
"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);
}
TryMigrate(connection);
}
private void TryMigrate(ManagedConnection connection)
@@ -77,8 +85,7 @@ namespace Emby.Server.Implementations.Activity
}
}
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
/// <inheritdoc />
public void Create(ActivityLogEntry entry)
{
if (entry == null)
@@ -86,37 +93,38 @@ namespace Emby.Server.Implementations.Activity
throw new ArgumentNullException(nameof(entry));
}
using (var connection = GetConnection())
using var connection = GetConnection();
connection.RunInTransaction(db =>
{
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))
{
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.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
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);
}
statement.MoveNext();
}, TransactionMode);
}
/// <summary>
/// Adds the provided <see cref="ActivityLogEntry"/> to this repository.
/// </summary>
/// <param name="entry">The activity log entry.</param>
/// <exception cref="ArgumentNullException">If entry is null.</exception>
public void Update(ActivityLogEntry entry)
{
if (entry == null)
@@ -124,38 +132,35 @@ namespace Emby.Server.Implementations.Activity
throw new ArgumentNullException(nameof(entry));
}
using (var connection = GetConnection())
using var connection = GetConnection();
connection.RunInTransaction(db =>
{
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))
{
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.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
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);
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
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);
}
statement.MoveNext();
}, TransactionMode);
}
/// <inheritdoc />
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var commandText = BaseActivitySelectText;
@@ -165,16 +170,10 @@ namespace Emby.Server.Implementations.Activity
{
whereClauses.Add("DateCreated>=@DateCreated");
}
if (hasUserId.HasValue)
{
if (hasUserId.Value)
{
whereClauses.Add("UserId not null");
}
else
{
whereClauses.Add("UserId is null");
}
whereClauses.Add(hasUserId.Value ? "UserId not null" : "UserId is null");
}
var whereTextWithoutPaging = whereClauses.Count == 0 ?
@@ -205,7 +204,7 @@ namespace Emby.Server.Implementations.Activity
if (limit.HasValue)
{
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
}
var statementTexts = new[]
@@ -217,38 +216,33 @@ namespace Emby.Server.Implementations.Activity
var list = new List<ActivityLogEntry>();
var result = new QueryResult<ActivityLogEntry>();
using (var connection = GetConnection(true))
{
connection.RunInTransaction(
db =>
using var connection = GetConnection(true);
connection.RunInTransaction(
db =>
{
var statements = PrepareAll(db, statementTexts).ToList();
using (var statement = statements[0])
{
var statements = PrepareAll(db, statementTexts).ToList();
using (var statement = statements[0])
if (minDate.HasValue)
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
foreach (var row in statement.ExecuteQuery())
{
list.Add(GetEntry(row));
}
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
using (var statement = statements[1])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
list.AddRange(statement.ExecuteQuery().Select(GetEntry));
}
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
using (var statement = statements[1])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
},
ReadTransactionMode);
}
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
},
ReadTransactionMode);
result.Items = list;
return result;
@@ -305,7 +299,7 @@ namespace Emby.Server.Implementations.Activity
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
}
return info;

View File

@@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration;
namespace Emby.Server.Implementations.AppBase
{
/// <summary>
/// Provides a base class to hold common application paths used by both the Ui and Server.
/// Provides a base class to hold common application paths used by both the UI and Server.
/// This can be subclassed to add application-specific paths.
/// </summary>
public abstract class BaseApplicationPaths : IApplicationPaths
@@ -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,
@@ -37,10 +42,7 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The program data path.</value>
public string ProgramDataPath { get; }
/// <summary>
/// Gets the path to the web UI resources folder.
/// </summary>
/// <value>The web UI resources path.</value>
/// <inheritdoc/>
public string WebPath { get; }
/// <summary>

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,14 +1,15 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
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

@@ -1,51 +1,48 @@
using System;
using MediaBrowser.Controller;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Browser
{
/// <summary>
/// Class BrowserLauncher.
/// Assists in opening application URLs in an external browser.
/// </summary>
public static class BrowserLauncher
{
/// <summary>
/// Opens the dashboard page.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="appHost">The app host.</param>
private static void OpenDashboardPage(string page, IServerApplicationHost appHost)
{
var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page;
OpenUrl(appHost, url);
}
/// <summary>
/// Opens the web client.
/// Opens the home page of the web client.
/// </summary>
/// <param name="appHost">The app host.</param>
public static void OpenWebApp(IServerApplicationHost appHost)
{
OpenDashboardPage("index.html", appHost);
TryOpenUrl(appHost, "/web/index.html");
}
/// <summary>
/// Opens the URL.
/// Opens the swagger API page.
/// </summary>
/// <param name="appHost">The application host instance.</param>
/// <param name="appHost">The app host.</param>
public static void OpenSwaggerPage(IServerApplicationHost appHost)
{
TryOpenUrl(appHost, "/swagger/index.html");
}
/// <summary>
/// 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 OpenUrl(IServerApplicationHost appHost, string url)
private static void TryOpenUrl(IServerApplicationHost appHost, string url)
{
try
{
appHost.LaunchUrl(url);
string baseUrl = appHost.GetLocalApiUrl("localhost");
appHost.LaunchUrl(baseUrl + url);
}
catch (NotSupportedException)
{
}
catch (Exception)
catch (Exception ex)
{
var logger = appHost.Resolve<ILogger>();
logger?.LogError(ex, "Failed to open browser window with URL {URL}", url);
}
}
}

View File

@@ -1,8 +1,6 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Channels;
@@ -12,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;
@@ -28,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,6 +1,3 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -12,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);
@@ -33,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;
@@ -47,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,6 +1,3 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -30,10 +27,11 @@ using Microsoft.Extensions.Logging;
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;
@@ -44,11 +42,28 @@ namespace Emby.Server.Implementations.Channels
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="loggerFactory">The logger factory.</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,
@@ -58,7 +73,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;
@@ -66,13 +81,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);
@@ -81,15 +100,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);
@@ -98,6 +118,7 @@ namespace Emby.Server.Implementations.Channels
return channel is ISupportsMediaProbe;
}
/// <inheritdoc />
public Task DeleteItem(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -124,11 +145,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)
@@ -147,15 +173,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();
}
@@ -172,7 +196,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
}).ToList();
}
@@ -189,9 +212,9 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
}).ToList();
}
if (query.IsFavorite.HasValue)
{
var val = query.IsFavorite.Value;
@@ -216,7 +239,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
}).ToList();
}
@@ -227,6 +249,7 @@ namespace Emby.Server.Implementations.Channels
{
all = all.Skip(query.StartIndex.Value).ToList();
}
if (query.Limit.HasValue)
{
all = all.Take(query.Limit.Value).ToList();
@@ -249,6 +272,7 @@ namespace Emby.Server.Implementations.Channels
};
}
/// <inheritdoc />
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
{
var user = query.UserId.Equals(Guid.Empty)
@@ -257,11 +281,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>
@@ -273,6 +295,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();
@@ -306,14 +334,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)
@@ -342,8 +363,8 @@ namespace Emby.Server.Implementations.Channels
}
catch
{
}
return;
}
@@ -352,6 +373,7 @@ namespace Emby.Server.Implementations.Channels
_jsonSerializer.SerializeToFile(mediaSources, path);
}
/// <inheritdoc />
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
{
IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item);
@@ -361,16 +383,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);
@@ -385,9 +411,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))
@@ -410,7 +433,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;
}
@@ -445,18 +468,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);
@@ -473,51 +499,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))
@@ -531,15 +562,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)
{
@@ -568,9 +611,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);
@@ -589,6 +634,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();
@@ -615,7 +661,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)
{
@@ -641,10 +687,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);
@@ -652,17 +700,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
@@ -673,7 +724,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,
@@ -685,13 +737,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)
{
@@ -708,12 +759,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);
}
}
}
@@ -721,6 +775,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);
@@ -736,7 +791,6 @@ namespace Emby.Server.Implementations.Channels
return result;
}
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
User user,
string externalFolderId,
@@ -744,7 +798,7 @@ namespace Emby.Server.Implementations.Channels
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);
@@ -762,11 +816,9 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
}
catch (IOException)
{
}
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -786,16 +838,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
@@ -834,7 +884,8 @@ namespace Emby.Server.Implementations.Channels
}
}
private string GetChannelDataCachePath(IChannel channel,
private string GetChannelDataCachePath(
IChannel channel,
string userId,
string externalFolderId,
ChannelItemSortField? sortField,
@@ -844,8 +895,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;
}
@@ -859,6 +909,7 @@ namespace Emby.Server.Implementations.Channels
{
filename += "-sortField-" + sortField.Value;
}
if (sortDescending)
{
filename += "-sortDescending";
@@ -866,7 +917,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,
@@ -920,60 +972,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;
@@ -982,7 +1006,6 @@ namespace Emby.Server.Implementations.Channels
{
item.RunTimeTicks = null;
}
else if (isNew || !enableMediaProbe)
{
item.RunTimeTicks = info.RunTimeTicks;
@@ -1015,26 +1038,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();
}
@@ -1058,6 +1079,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))
@@ -1065,16 +1087,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;
}
@@ -1083,24 +1106,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))
@@ -1157,7 +1179,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,6 +1,3 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Linq;
using System.Threading;
@@ -12,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);
@@ -35,14 +45,6 @@ namespace Emby.Server.Implementations.Channels
return Task.CompletedTask;
}
public static string GetUserDistinctValue(User user)
{
var channels = user.Policy.EnabledChannels
.OrderBy(i => i);
return string.Join("|", channels);
}
private void CleanDatabase(CancellationToken cancellationToken)
{
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
@@ -75,19 +77,23 @@ namespace Emby.Server.Implementations.Channels
{
cancellationToken.ThrowIfCancellationRequested();
_libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
}, false);
_libraryManager.DeleteItem(
item,
new DeleteOptions
{
DeleteFileLocation = false
},
false);
}
// Finally, delete the channel itself
_libraryManager.DeleteItem(channel, new DeleteOptions
{
DeleteFileLocation = false
}, false);
_libraryManager.DeleteItem(
channel,
new DeleteOptions
{
DeleteFileLocation = false
},
false);
}
}
}

View File

@@ -1,6 +1,3 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
using System.Threading;
@@ -8,60 +5,84 @@ 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;
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 ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
public RefreshChannelsScheduledTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
/// <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,
ILogger<RefreshChannelsScheduledTask> logger,
ILibraryManager libraryManager,
ILocalizationManager localization)
{
_channelManager = channelManager;
_userManager = userManager;
_logger = logger;
_libraryManager = libraryManager;
_localization = localization;
}
public string Name => "Refresh Channels";
/// <inheritdoc />
public string Name => _localization.GetLocalizedString("TasksRefreshChannels");
public string Description => "Refreshes internet channel information.";
/// <inheritdoc />
public string Description => _localization.GetLocalizedString("TasksRefreshChannelsDescription");
public string Category => "Internet Channels";
/// <inheritdoc />
public string Category => _localization.GetLocalizedString("TasksChannelsCategory");
/// <inheritdoc />
public bool IsHidden => ((ChannelManager)_channelManager).Channels.Length == 0;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public string Key => "RefreshInternetChannels";
/// <inheritdoc />
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
var manager = (ChannelManager)_channelManager;
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);
}
/// <summary>
/// Creates the triggers that define when the task will run
/// </summary>
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[] {
return new[]
{
// Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
new TaskTriggerInfo
{
Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks
}
};
}
public string Key => "RefreshInternetChannels";
}
}

View File

@@ -1,7 +1,3 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
@@ -17,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,
@@ -28,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
@@ -39,6 +46,7 @@ namespace Emby.Server.Implementations.Collections
return base.Supports(item);
}
/// <inheritdoc />
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (BoxSet)item;
@@ -50,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))
@@ -82,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,6 +1,3 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -24,6 +21,9 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Collections
{
/// <summary>
/// The collection manager.
/// </summary>
public class CollectionManager : ICollectionManager
{
private readonly ILibraryManager _libraryManager;
@@ -34,6 +34,16 @@ namespace Emby.Server.Implementations.Collections
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,
@@ -52,8 +62,13 @@ namespace Emby.Server.Implementations.Collections
_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)
@@ -110,11 +125,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;
@@ -179,11 +195,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)));
@@ -192,7 +210,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");
@@ -247,11 +264,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;
@@ -290,10 +309,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
{
@@ -302,6 +324,7 @@ namespace Emby.Server.Implementations.Collections
});
}
/// <inheritdoc />
public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
{
var results = new Dictionary<Guid, BaseItem>();
@@ -310,9 +333,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;
}
@@ -342,13 +363,25 @@ 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;
public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger 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,
ILogger<CollectionManagerEntryPoint> logger)
{
_collectionManager = (CollectionManager)collectionManager;
_config = config;

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Emby.Server.Implementations.AppBase;
@@ -68,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;
@@ -133,7 +131,7 @@ namespace Emby.Server.Implementations.Configuration
var newPath = newConfig.MetadataPath;
if (!string.IsNullOrWhiteSpace(newPath)
&& !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal))
&& !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal))
{
// Validate
if (!Directory.Exists(newPath))

View File

@@ -1,16 +1,27 @@
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
{
/// <summary>
/// Static class containing the default configuration options for the web server.
/// </summary>
public static class ConfigurationOptions
{
public static Dictionary<string, string> Configuration => new Dictionary<string, string>
/// <summary>
/// Gets a new copy of the default configuration options.
/// </summary>
public static Dictionary<string, string> DefaultConfiguration => new Dictionary<string, string>
{
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
{ "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" },
{ HostWebClientKey, bool.TrueString },
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" }
{ FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
};
}
}

View File

@@ -31,7 +31,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 +56,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 +72,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 />

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Threading;
@@ -15,7 +14,7 @@ namespace Emby.Server.Implementations.Data
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger)
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
{
_libraryManager = libraryManager;
_logger = logger;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@@ -1,11 +1,8 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using MediaBrowser.Model.Serialization;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
@@ -110,25 +107,6 @@ namespace Emby.Server.Implementations.Data
return null;
}
/// <summary>
/// Serializes to bytes.
/// </summary>
/// <returns>System.Byte[][].</returns>
/// <exception cref="ArgumentNullException">obj</exception>
public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}
using (var stream = new MemoryStream())
{
json.SerializeToStream(obj, stream);
return stream.ToArray();
}
}
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
{
var commandText = string.Format(
@@ -288,7 +266,7 @@ namespace Emby.Server.Implementations.Data
}
}
public static void TryBind(this IStatement statement, string name, byte[] value)
public static void TryBind(this IStatement statement, string name, ReadOnlySpan<byte> value)
{
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
{
@@ -384,11 +362,11 @@ namespace Emby.Server.Implementations.Data
}
}
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement)
{
while (This.MoveNext())
while (statement.MoveNext())
{
yield return This.Current;
yield return statement.Current;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -26,7 +25,7 @@ namespace Emby.Server.Implementations.Data
IServerApplicationPaths appPaths)
: base(logger)
{
_jsonOptions = JsonDefaults.GetOptions();;
_jsonOptions = JsonDefaults.GetOptions();
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Globalization;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -39,10 +38,11 @@ namespace Emby.Server.Implementations.Devices
private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
private readonly IAuthenticationRepository _authRepo;
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
private readonly object _cameraUploadSyncLock = new object();
@@ -66,10 +66,9 @@ namespace Emby.Server.Implementations.Devices
_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");
@@ -142,11 +141,10 @@ namespace Emby.Server.Implementations.Devices
public QueryResult<DeviceInfo> GetDevices(DeviceQuery query)
{
var sessions = _authRepo.Get(new AuthenticationInfoQuery
IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
{
//UserId = query.UserId
HasUser = true
}).Items;
// TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
@@ -154,23 +152,19 @@ namespace Emby.Server.Implementations.Devices
{
var val = query.SupportsSync.Value;
sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val).ToArray();
sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val);
}
if (!query.UserId.Equals(Guid.Empty))
{
var user = _userManager.GetUserById(query.UserId);
sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)).ToArray();
sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
}
var array = sessions.Select(ToDeviceInfo).ToArray();
return new QueryResult<DeviceInfo>
{
Items = array,
TotalRecordCount = array.Length
};
return new QueryResult<DeviceInfo>(array);
}
private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo)
@@ -186,7 +180,7 @@ namespace Emby.Server.Implementations.Devices
LastUserName = authInfo.UserName,
Name = authInfo.DeviceName,
DateLastActivity = authInfo.DateLastActivity,
IconUrl = caps == null ? null : caps.IconUrl
IconUrl = caps?.IconUrl
};
}
@@ -243,7 +237,7 @@ namespace Emby.Server.Implementations.Devices
try
{
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
await stream.CopyToAsync(fs).ConfigureAwait(false);
}
@@ -412,7 +406,10 @@ namespace Emby.Server.Implementations.Devices
private readonly IServerConfigurationManager _config;
private ILogger _logger;
public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger)
public DeviceManagerEntryPoint(
IDeviceManager deviceManager,
IServerConfigurationManager config,
ILogger<DeviceManagerEntryPoint> logger)
{
_deviceManager = (DeviceManager)deviceManager;
_config = config;

View File

@@ -1,153 +0,0 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Diagnostics;
namespace Emby.Server.Implementations.Diagnostics
{
public class CommonProcess : IProcess
{
private readonly Process _process;
private bool _disposed = false;
private bool _hasExited;
public CommonProcess(ProcessOptions options)
{
StartInfo = options;
var startInfo = new ProcessStartInfo
{
Arguments = options.Arguments,
FileName = options.FileName,
WorkingDirectory = options.WorkingDirectory,
UseShellExecute = options.UseShellExecute,
CreateNoWindow = options.CreateNoWindow,
RedirectStandardError = options.RedirectStandardError,
RedirectStandardInput = options.RedirectStandardInput,
RedirectStandardOutput = options.RedirectStandardOutput,
ErrorDialog = options.ErrorDialog
};
if (options.IsHidden)
{
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
}
_process = new Process
{
StartInfo = startInfo
};
if (options.EnableRaisingEvents)
{
_process.EnableRaisingEvents = true;
_process.Exited += OnProcessExited;
}
}
public event EventHandler Exited;
public ProcessOptions StartInfo { get; }
public StreamWriter StandardInput => _process.StandardInput;
public StreamReader StandardError => _process.StandardError;
public StreamReader StandardOutput => _process.StandardOutput;
public int ExitCode => _process.ExitCode;
private bool HasExited
{
get
{
if (_hasExited)
{
return true;
}
try
{
_hasExited = _process.HasExited;
}
catch (InvalidOperationException)
{
_hasExited = true;
}
return _hasExited;
}
}
public void Start()
{
_process.Start();
}
public void Kill()
{
_process.Kill();
}
public bool WaitForExit(int timeMs)
{
return _process.WaitForExit(timeMs);
}
public Task<bool> WaitForExitAsync(int timeMs)
{
// Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
if (HasExited)
{
return Task.FromResult(true);
}
timeMs = Math.Max(0, timeMs);
var tcs = new TaskCompletionSource<bool>();
var cancellationToken = new CancellationTokenSource(timeMs).Token;
_process.Exited += (sender, args) => tcs.TrySetResult(true);
cancellationToken.Register(() => tcs.TrySetResult(HasExited));
return tcs.Task;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_process?.Dispose();
}
_disposed = true;
}
private void OnProcessExited(object sender, EventArgs e)
{
_hasExited = true;
Exited?.Invoke(this, e);
}
}
}

View File

@@ -1,15 +0,0 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Diagnostics;
namespace Emby.Server.Implementations.Diagnostics
{
public class ProcessFactory : IProcessFactory
{
public IProcess Create(ProcessOptions options)
{
return new CommonProcess(options);
}
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -39,21 +38,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;
@@ -61,7 +62,7 @@ namespace Emby.Server.Implementations.Dto
_providerManager = providerManager;
_appHost = appHost;
_mediaSourceManager = mediaSourceManager;
_livetvManager = livetvManager;
_livetvManagerFactory = livetvManagerFactory;
}
/// <summary>
@@ -126,12 +127,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;
@@ -143,12 +144,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);
}
@@ -224,7 +225,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);
}
@@ -255,7 +256,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)
{
@@ -1046,7 +1047,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;
@@ -1057,30 +1058,19 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.SpecialFeatureCount))
{
if (allExtras == null)
{
allExtras = item.GetExtras().ToArray();
}
allExtras = item.GetExtras().ToArray();
dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value));
}
if (options.ContainsField(ItemFields.LocalTrailerCount))
{
int trailerCount = 0;
if (allExtras == null)
{
allExtras = item.GetExtras().ToArray();
}
trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer);
allExtras ??= item.GetExtras().ToArray();
dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
if (item is IHasTrailers hasTrailers)
{
trailerCount += hasTrailers.GetTrailerCount();
dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
}
dto.LocalTrailerCount = trailerCount;
}
// Add EpisodeInfo
@@ -1362,56 +1352,33 @@ namespace Emby.Server.Implementations.Dto
return null;
}
var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary).ToArray();
ImageDimensions size;
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
if (defaultAspectRatio > 0)
{
if (supportedEnhancers.Length == 0)
{
return defaultAspectRatio;
}
int dummyWidth = 200;
int dummyHeight = Convert.ToInt32(dummyWidth / defaultAspectRatio);
size = new ImageDimensions(dummyWidth, dummyHeight);
return defaultAspectRatio;
}
else
if (!imageInfo.IsLocalFile)
{
if (!imageInfo.IsLocalFile)
return null;
}
try
{
size = _imageProcessor.GetImageDimensions(item, imageInfo);
if (size.Width <= 0 || size.Height <= 0)
{
return null;
}
try
{
size = _imageProcessor.GetImageDimensions(item, imageInfo);
if (size.Width <= 0 || size.Height <= 0)
{
return null;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
return null;
}
}
foreach (var enhancer in supportedEnhancers)
catch (Exception ex)
{
try
{
size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in image enhancer: {0}", enhancer.GetType().Name);
}
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
return null;
}
var width = size.Width;

View File

@@ -1,5 +1,10 @@
<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" />
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
@@ -29,14 +34,15 @@
<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.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.0" />
<PackageReference Include="Mono.Nat" Version="2.0.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
<PackageReference Include="sharpcompress" Version="0.24.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
<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="Mono.Nat" Version="2.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
<PackageReference Include="sharpcompress" Version="0.25.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,128 +0,0 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
public class AutomaticRestartEntryPoint : IServerEntryPoint
{
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly ITaskManager _iTaskManager;
private readonly ISessionManager _sessionManager;
private readonly IServerConfigurationManager _config;
private readonly ILiveTvManager _liveTvManager;
private Timer _timer;
public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager)
{
_appHost = appHost;
_logger = logger;
_iTaskManager = iTaskManager;
_sessionManager = sessionManager;
_config = config;
_liveTvManager = liveTvManager;
}
public Task RunAsync()
{
if (_appHost.CanSelfRestart)
{
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
}
return Task.CompletedTask;
}
void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
{
DisposeTimer();
if (_appHost.HasPendingRestart)
{
_timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
}
}
private async void TimerCallback(object state)
{
if (_config.Configuration.EnableAutomaticRestart)
{
var isIdle = await IsIdle().ConfigureAwait(false);
if (isIdle)
{
DisposeTimer();
_logger.LogInformation("Automatically restarting the system because it is idle and a restart is required.");
try
{
_appHost.Restart();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error restarting server");
}
}
}
}
private async Task<bool> IsIdle()
{
if (_iTaskManager.ScheduledTasks.Any(i => i.State != TaskState.Idle))
{
return false;
}
if (_liveTvManager.Services.Count == 1)
{
try
{
var timers = await _liveTvManager.GetTimers(new TimerQuery(), CancellationToken.None).ConfigureAwait(false);
if (timers.Items.Any(i => i.Status == RecordingStatus.InProgress))
{
return false;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting timers");
}
}
var now = DateTime.UtcNow;
return !_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 30);
}
public void Dispose()
{
_appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
DisposeTimer();
}
private void DisposeTimer()
{
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
}
}
}

View File

@@ -1,7 +1,7 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Text;
@@ -27,10 +27,10 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly object _createdRulesLock = new object();
private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
private Timer _timer;
private string _lastConfigIdentifier;
private string _configIdentifier;
private bool _disposed = false;
@@ -61,6 +61,7 @@ namespace Emby.Server.Implementations.EntryPoints
return new StringBuilder(32)
.Append(config.EnableUPnP).Append(Separator)
.Append(config.PublicPort).Append(Separator)
.Append(config.PublicHttpsPort).Append(Separator)
.Append(_appHost.HttpPort).Append(Separator)
.Append(_appHost.HttpsPort).Append(Separator)
.Append(_appHost.EnableHttps).Append(Separator)
@@ -70,7 +71,10 @@ namespace Emby.Server.Implementations.EntryPoints
private void OnConfigurationUpdated(object sender, EventArgs e)
{
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
var oldConfigIdentifier = _configIdentifier;
_configIdentifier = GetConfigIdentifier();
if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
{
Stop();
Start();
@@ -94,21 +98,19 @@ namespace Emby.Server.Implementations.EntryPoints
return;
}
_logger.LogDebug("Starting NAT discovery");
_logger.LogInformation("Starting NAT discovery");
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
NatUtility.StartDiscovery();
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
_lastConfigIdentifier = GetConfigIdentifier();
}
private void Stop()
{
_logger.LogDebug("Stopping NAT discovery");
_logger.LogInformation("Stopping NAT discovery");
NatUtility.StopDiscovery();
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
@@ -118,26 +120,16 @@ namespace Emby.Server.Implementations.EntryPoints
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
}
private void ClearCreatedRules(object state)
{
lock (_createdRulesLock)
{
_createdRules.Clear();
}
}
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
}
private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
{
try
{
var device = e.Device;
CreateRules(device);
await CreateRules(e.Device).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -145,7 +137,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
private async void CreateRules(INatDevice device)
private Task CreateRules(INatDevice device)
{
if (_disposed)
{
@@ -154,50 +146,46 @@ namespace Emby.Server.Implementations.EntryPoints
// On some systems the device discovered event seems to fire repeatedly
// This check will help ensure we're not trying to port map the same device over and over
var address = device.DeviceEndpoint;
lock (_createdRulesLock)
if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
{
if (!_createdRules.Contains(address))
{
_createdRules.Add(address);
}
else
{
return;
}
return Task.CompletedTask;
}
try
{
await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating http port map");
return;
}
return Task.WhenAll(CreatePortMaps(device));
}
try
private IEnumerable<Task> CreatePortMaps(INatDevice device)
{
yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
if (_appHost.EnableHttps)
{
await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating https port map");
yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
}
}
private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
{
_logger.LogDebug(
"Creating port map on local port {0} to public port {1} with device {2}",
"Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
privatePort,
publicPort,
device.DeviceEndpoint);
return device.CreatePortMapAsync(
new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
try
{
var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.",
privatePort,
publicPort,
device.DeviceEndpoint);
}
}
/// <inheritdoc />

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -16,7 +15,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
@@ -57,7 +55,12 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IProviderManager _providerManager;
public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, IProviderManager providerManager)
public LibraryChangedNotifier(
ILibraryManager libraryManager,
ISessionManager sessionManager,
IUserManager userManager,
ILogger<LibraryChangedNotifier> logger,
IProviderManager providerManager)
{
_libraryManager = libraryManager;
_sessionManager = sessionManager;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Linq;
@@ -13,14 +12,18 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
public class RecordingNotifier : IServerEntryPoint
public sealed class RecordingNotifier : IServerEntryPoint
{
private readonly ILiveTvManager _liveTvManager;
private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
public RecordingNotifier(ISessionManager sessionManager, IUserManager userManager, ILogger logger, ILiveTvManager liveTvManager)
public RecordingNotifier(
ISessionManager sessionManager,
IUserManager userManager,
ILogger<RecordingNotifier> logger,
ILiveTvManager liveTvManager)
{
_sessionManager = sessionManager;
_userManager = userManager;
@@ -28,32 +31,33 @@ namespace Emby.Server.Implementations.EntryPoints
_liveTvManager = liveTvManager;
}
/// <inheritdoc />
public Task RunAsync()
{
_liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled;
_liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled;
_liveTvManager.TimerCreated += _liveTvManager_TimerCreated;
_liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated;
_liveTvManager.TimerCancelled += OnLiveTvManagerTimerCancelled;
_liveTvManager.SeriesTimerCancelled += OnLiveTvManagerSeriesTimerCancelled;
_liveTvManager.TimerCreated += OnLiveTvManagerTimerCreated;
_liveTvManager.SeriesTimerCreated += OnLiveTvManagerSeriesTimerCreated;
return Task.CompletedTask;
}
private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("SeriesTimerCreated", e.Argument);
}
private void _liveTvManager_TimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("TimerCreated", e.Argument);
}
private void _liveTvManager_SeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("SeriesTimerCancelled", e.Argument);
}
private void _liveTvManager_TimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("TimerCancelled", e.Argument);
}
@@ -64,11 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints
try
{
await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None);
}
catch (ObjectDisposedException)
{
// TODO Log exception or Investigate and properly fix.
await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -76,12 +76,13 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
/// <inheritdoc />
public void Dispose()
{
_liveTvManager.TimerCancelled -= _liveTvManager_TimerCancelled;
_liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled;
_liveTvManager.TimerCreated -= _liveTvManager_TimerCreated;
_liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated;
_liveTvManager.TimerCancelled -= OnLiveTvManagerTimerCancelled;
_liveTvManager.SeriesTimerCancelled -= OnLiveTvManagerSeriesTimerCancelled;
_liveTvManager.TimerCreated -= OnLiveTvManagerTimerCreated;
_liveTvManager.SeriesTimerCreated -= OnLiveTvManagerSeriesTimerCreated;
}
}
}

View File

@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
@@ -15,21 +14,17 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
{
private readonly ILogger _logger;
/// <summary>
/// The user manager.
/// </summary>
private readonly IUserManager _userManager;
private IFileSystem _fileSystem;
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
/// </summary>
public RefreshUsersMetadata(ILogger logger, IUserManager userManager, IFileSystem fileSystem)
public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
{
_logger = logger;
_userManager = userManager;
_fileSystem = fileSystem;
}

View File

@@ -2,64 +2,77 @@ using System.Threading.Tasks;
using Emby.Server.Implementations.Browser;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Plugins;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
namespace Emby.Server.Implementations.EntryPoints
{
/// <summary>
/// Class StartupWizard.
/// </summary>
public class StartupWizard : IServerEntryPoint
public sealed class StartupWizard : IServerEntryPoint
{
/// <summary>
/// The app host.
/// </summary>
private readonly IServerApplicationHost _appHost;
/// <summary>
/// The user manager.
/// </summary>
private readonly ILogger _logger;
private IServerConfigurationManager _config;
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="logger">The logger.</param>
/// <param name="appConfig">The application configuration.</param>
/// <param name="config">The configuration manager.</param>
public StartupWizard(IServerApplicationHost appHost, ILogger logger, IServerConfigurationManager config)
/// <param name="startupOptions">The application startup options.</param>
public StartupWizard(
IServerApplicationHost appHost,
IConfiguration appConfig,
IServerConfigurationManager config,
IStartupOptions startupOptions)
{
_appHost = appHost;
_logger = logger;
_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 (!_config.Configuration.IsStartupWizardCompleted)
// Always launch the startup wizard if possible when it has not been completed
if (!_config.Configuration.IsStartupWizardCompleted && _appConfig.HostWebClient())
{
BrowserLauncher.OpenWebApp(_appHost);
return;
}
// 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

@@ -1,10 +1,8 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Udp;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
@@ -12,7 +10,7 @@ namespace Emby.Server.Implementations.EntryPoints
/// <summary>
/// Class UdpServerEntryPoint.
/// </summary>
public class UdpServerEntryPoint : IServerEntryPoint
public sealed class UdpServerEntryPoint : IServerEntryPoint
{
/// <summary>
/// The port of the UDP server.
@@ -23,69 +21,50 @@ namespace Emby.Server.Implementations.EntryPoints
/// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly ISocketFactory _socketFactory;
private readonly IServerApplicationHost _appHost;
private readonly IJsonSerializer _json;
/// <summary>
/// The UDP server.
/// </summary>
private UdpServer _udpServer;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed = false;
/// <summary>
/// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
/// </summary>
public UdpServerEntryPoint(
ILogger logger,
IServerApplicationHost appHost,
IJsonSerializer json,
ISocketFactory socketFactory)
ILogger<UdpServerEntryPoint> logger,
IServerApplicationHost appHost)
{
_logger = logger;
_appHost = appHost;
_json = json;
_socketFactory = socketFactory;
}
/// <inheritdoc />
public Task RunAsync()
public async Task RunAsync()
{
var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory);
try
{
udpServer.Start(PortNumber);
_udpServer = udpServer;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to start UDP Server");
}
return Task.CompletedTask;
_udpServer = new UdpServer(_logger, _appHost);
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
if (_disposed)
{
if (_udpServer != null)
{
_udpServer.Dispose();
}
return;
}
_cancellationTokenSource.Cancel();
_udpServer.Dispose();
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
_udpServer = null;
_disposed = true;
}
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -13,39 +12,38 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
public class UserDataChangeNotifier : IServerEntryPoint
public sealed class UserDataChangeNotifier : IServerEntryPoint
{
private const int UpdateDuration = 500;
private readonly ISessionManager _sessionManager;
private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager;
private readonly IUserManager _userManager;
private readonly object _syncLock = new object();
private Timer UpdateTimer { get; set; }
private const int UpdateDuration = 500;
private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>();
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager)
private readonly object _syncLock = new object();
private Timer _updateTimer;
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager)
{
_userDataManager = userDataManager;
_sessionManager = sessionManager;
_logger = logger;
_userManager = userManager;
}
public Task RunAsync()
{
_userDataManager.UserDataSaved += _userDataManager_UserDataSaved;
_userDataManager.UserDataSaved += OnUserDataManagerUserDataSaved;
return Task.CompletedTask;
}
void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e)
void OnUserDataManagerUserDataSaved(object sender, UserDataSaveEventArgs e)
{
if (e.SaveReason == UserDataSaveReason.PlaybackProgress)
{
@@ -54,14 +52,17 @@ namespace Emby.Server.Implementations.EntryPoints
lock (_syncLock)
{
if (UpdateTimer == null)
if (_updateTimer == null)
{
UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration,
Timeout.Infinite);
_updateTimer = new Timer(
UpdateTimerCallback,
null,
UpdateDuration,
Timeout.Infinite);
}
else
{
UpdateTimer.Change(UpdateDuration, Timeout.Infinite);
_updateTimer.Change(UpdateDuration, Timeout.Infinite);
}
if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem> keys))
@@ -97,10 +98,10 @@ namespace Emby.Server.Implementations.EntryPoints
var task = SendNotifications(changes, CancellationToken.None);
if (UpdateTimer != null)
if (_updateTimer != null)
{
UpdateTimer.Dispose();
UpdateTimer = null;
_updateTimer.Dispose();
_updateTimer = null;
}
}
}
@@ -145,13 +146,13 @@ namespace Emby.Server.Implementations.EntryPoints
public void Dispose()
{
if (UpdateTimer != null)
if (_updateTimer != null)
{
UpdateTimer.Dispose();
UpdateTimer = null;
_updateTimer.Dispose();
_updateTimer = null;
}
_userDataManager.UserDataSaved -= _userDataManager_UserDataSaved;
_userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved;
}
}
}

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;
@@ -24,7 +25,7 @@ namespace Emby.Server.Implementations.HttpClientManager
private readonly ILogger _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>
@@ -78,7 +79,7 @@ namespace Emby.Server.Implementations.HttpClientManager
if (!string.IsNullOrWhiteSpace(userInfo))
{
_logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url);
url = url.Replace(userInfo + '@', string.Empty);
url = url.Replace(userInfo + '@', string.Empty, StringComparison.Ordinal);
}
var request = new HttpRequestMessage(method, url);
@@ -91,18 +92,18 @@ 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)
{
case CompressionMethod.Deflate | CompressionMethod.Gzip:
case CompressionMethods.Deflate | CompressionMethods.Gzip:
request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" });
break;
case CompressionMethod.Deflate:
case CompressionMethods.Deflate:
request.Headers.Add(HeaderNames.AcceptEncoding, "deflate");
break;
case CompressionMethod.Gzip:
case CompressionMethods.Gzip:
request.Headers.Add(HeaderNames.AcceptEncoding, "gzip");
break;
default:
@@ -197,7 +198,7 @@ namespace Emby.Server.Implementations.HttpClientManager
if (File.Exists(responseCachePath)
&& _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
{
var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true);
var stream = new FileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
return new HttpResponseInfo
{
@@ -220,7 +221,7 @@ namespace Emby.Server.Implementations.HttpClientManager
FileMode.Create,
FileAccess.Write,
FileShare.None,
StreamDefaults.DefaultFileStreamBufferSize,
IODefaults.FileStreamBufferSize,
true))
{
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
@@ -239,15 +240,10 @@ namespace Emby.Server.Implementations.HttpClientManager
var httpWebRequest = GetRequestMessage(options, httpMethod);
if (options.RequestContentBytes != null
|| !string.IsNullOrEmpty(options.RequestContent)
if (!string.IsNullOrEmpty(options.RequestContent)
|| httpMethod == HttpMethod.Post)
{
if (options.RequestContentBytes != null)
{
httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes);
}
else if (options.RequestContent != null)
if (options.RequestContent != null)
{
httpWebRequest.Content = new StringContent(
options.RequestContent,

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -12,8 +11,8 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
@@ -72,7 +71,7 @@ namespace Emby.Server.Implementations.HttpServer
SetRangeValues();
}
FileShare = FileShareMode.Read;
FileShare = FileShare.Read;
Cookies = new List<Cookie>();
}
@@ -94,7 +93,7 @@ namespace Emby.Server.Implementations.HttpServer
public List<Cookie> Cookies { get; private set; }
public FileShareMode FileShare { get; set; }
public FileShare FileShare { get; set; }
/// <summary>
/// Gets the options.
@@ -222,17 +221,17 @@ namespace Emby.Server.Implementations.HttpServer
}
}
public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShare fileShare, CancellationToken cancellationToken)
{
var fileOpenOptions = FileOpenOptions.SequentialScan;
var fileOptions = FileOptions.SequentialScan;
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
fileOpenOptions |= FileOpenOptions.Asynchronous;
fileOptions |= FileOptions.Asynchronous;
}
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, fileOptions))
{
if (offset > 0)
{
@@ -245,7 +244,7 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
await fs.CopyToAsync(stream, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
await fs.CopyToAsync(stream, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -16,15 +15,18 @@ 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;
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.Net.Http.Headers;
using ServiceStack.Text.Jsv;
@@ -33,6 +35,12 @@ namespace Emby.Server.Implementations.HttpServer
{
public class HttpListenerHost : IHttpServer, IDisposable
{
/// <summary>
/// The key for a setting that specifies the default redirect path
/// to use for requests where the URL base prefix is invalid or missing.
/// </summary>
public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly IServerConfigurationManager _config;
@@ -43,7 +51,11 @@ namespace Emby.Server.Implementations.HttpServer
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 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>();
private bool _disposed = false;
@@ -55,24 +67,34 @@ namespace Emby.Server.Implementations.HttpServer
INetworkManager networkManager,
IJsonSerializer jsonSerializer,
IXmlSerializer xmlSerializer,
ILoggerFactory loggerFactory)
IHttpListener socketListener,
ILocalizationManager localizationManager,
ServiceController serviceController,
IHostEnvironment hostEnvironment)
{
_appHost = applicationHost;
_logger = logger;
_config = config;
_defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
_defaultRedirectPath = configuration[DefaultRedirectKey];
_baseUrlPrefix = _config.Configuration.BaseUrl;
_networkManager = networkManager;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
_loggerFactory = loggerFactory;
_socketListener = socketListener;
ServiceController = serviceController;
_socketListener.WebSocketConnected = OnWebSocketConnected;
_hostEnvironment = hostEnvironment;
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
Instance = this;
ResponseFilters = Array.Empty<Action<IRequest, HttpResponse, object>>();
GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
}
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
public Action<IRequest, HttpResponse, object>[] ResponseFilters { get; set; }
public static HttpListenerHost Instance { get; protected set; }
@@ -81,9 +103,7 @@ namespace Emby.Server.Implementations.HttpServer
public string GlobalResponse { get; set; }
public ServiceController ServiceController { get; private set; }
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
public ServiceController ServiceController { get; }
public object CreateInstance(Type type)
{
@@ -92,7 +112,7 @@ namespace Emby.Server.Implementations.HttpServer
private static string NormalizeUrlPath(string path)
{
if (path.StartsWith("/"))
if (path.Length > 0 && path[0] == '/')
{
// If the path begins with a leading slash, just return it as-is
return path;
@@ -132,13 +152,13 @@ namespace Emby.Server.Implementations.HttpServer
public Type GetServiceTypeByRequest(Type requestType)
{
ServiceOperationsMap.TryGetValue(requestType, out var serviceType);
_serviceOperationsMap.TryGetValue(requestType, out var serviceType);
return serviceType;
}
public void AddServiceInfo(Type serviceType, Type requestType)
{
ServiceOperationsMap[requestType] = serviceType;
_serviceOperationsMap[requestType] = serviceType;
}
private List<IHasRequestFilter> GetRequestFilterAttributes(Type requestDtoType)
@@ -168,7 +188,7 @@ namespace Emby.Server.Implementations.HttpServer
else
{
var inners = agg.InnerExceptions;
if (inners != null && inners.Count > 0)
if (inners.Count > 0)
{
return GetActualException(inners[0]);
}
@@ -183,7 +203,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;
@@ -192,55 +213,52 @@ namespace Emby.Server.Implementations.HttpServer
}
}
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace)
private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog)
{
try
bool ignoreStackTrace =
ex is SocketException
|| ex is IOException
|| ex is OperationCanceledException
|| ex is SecurityException
|| ex is AuthenticationException
|| ex is FileNotFoundException;
if (ignoreStackTrace)
{
ex = GetActualException(ex);
if (logExceptionStackTrace)
{
_logger.LogError(ex, "Error processing request");
}
else
{
_logger.LogError("Error processing request: {Message}", ex.Message);
}
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)");
_logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
}
var httpRes = httpReq.Response;
if (httpRes.HasStarted)
{
return;
}
httpRes.StatusCode = statusCode;
var errContent = NormalizeExceptionMessage(ex) ?? string.Empty;
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;
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)
@@ -305,7 +323,7 @@ namespace Emby.Server.Implementations.HttpServer
return true;
}
host = host ?? string.Empty;
host ??= string.Empty;
if (_networkManager.IsInPrivateAddressSpace(host))
{
@@ -392,14 +410,14 @@ namespace Emby.Server.Implementations.HttpServer
}
/// <summary>
/// Overridable method that can be used to implement a custom handler
/// Overridable method that can be used to implement a custom handler.
/// </summary>
private async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
var httpRes = httpReq.Response;
string urlToLog = null;
string urlToLog = GetUrlToLog(urlString);
string remoteIp = httpReq.RemoteIp;
try
@@ -445,13 +463,11 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
urlToLog = GetUrlToLog(urlString);
if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.IsNullOrEmpty(localPath)
|| !localPath.StartsWith(_baseUrlPrefix))
|| !localPath.StartsWith(_baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
{
// Always redirect back to the default path if the base prefix is invalid or missing
_logger.LogDebug("Normalizing a URL at {0}", localPath);
@@ -478,22 +494,35 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
await ErrorHandler(new FileNotFoundException(), httpReq, false).ConfigureAwait(false);
throw new FileNotFoundException();
}
}
catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException)
catch (Exception requestEx)
{
await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
}
catch (SecurityException ex)
{
await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
}
catch (Exception ex)
{
var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
try
{
var requestInnerEx = GetActualException(requestEx);
var statusCode = GetStatusCode(requestInnerEx);
await ErrorHandler(ex, httpReq, logException).ConfigureAwait(false);
// Do not handle 500 server exceptions manually when in development mode
// The framework-defined development exception page will be returned instead
if (statusCode == 500 && _hostEnvironment.IsDevelopment())
{
throw;
}
await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog).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
{
@@ -582,17 +611,15 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Adds the rest handlers.
/// </summary>
/// <param name="services">The services.</param>
/// <param name="listeners"></param>
/// <param name="urlPrefixes"></param>
public void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listeners, IEnumerable<string> urlPrefixes)
/// <param name="serviceTypes">The service types to register with the <see cref="ServiceController"/>.</param>
/// <param name="listeners">The web socket listeners.</param>
/// <param name="urlPrefixes">The URL prefixes. See <see cref="UrlPrefixes"/>.</param>
public void Init(IEnumerable<Type> serviceTypes, IEnumerable<IWebSocketListener> listeners, IEnumerable<string> urlPrefixes)
{
_webSocketListeners = listeners.ToArray();
UrlPrefixes = urlPrefixes.ToArray();
ServiceController = new ServiceController();
var types = services.Select(r => r.GetType());
ServiceController.Init(this, types);
ServiceController.Init(this, serviceTypes);
ResponseFilters = new Action<IRequest, HttpResponse, object>[]
{
@@ -684,7 +711,10 @@ namespace Emby.Server.Implementations.HttpServer
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (_disposed)
{
return;
}
if (disposing)
{

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -29,6 +28,12 @@ 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>
@@ -421,7 +426,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))
{
@@ -440,7 +449,7 @@ namespace Emby.Server.Implementations.HttpServer
public Task<object> GetStaticFileResult(IRequest requestContext,
string path,
FileShareMode fileShare = FileShareMode.Read)
FileShare fileShare = FileShare.Read)
{
if (string.IsNullOrEmpty(path))
{
@@ -464,7 +473,7 @@ namespace Emby.Server.Implementations.HttpServer
throw new ArgumentException("Path can't be empty.", nameof(options));
}
if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)
if (fileShare != FileShare.Read && fileShare != FileShare.ReadWrite)
{
throw new ArgumentException("FileShare must be either Read or ReadWrite");
}
@@ -492,9 +501,9 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="path">The path.</param>
/// <param name="fileShare">The file share.</param>
/// <returns>Stream.</returns>
private Stream GetFileStream(string path, FileShareMode fileShare)
private Stream GetFileStream(string path, FileShare fileShare)
{
return _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShare);
return new FileStream(path, FileMode.Open, FileAccess.Read, fileShare);
}
public Task<object> GetStaticResult(IRequest requestContext,
@@ -630,7 +639,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);
}
}

View File

@@ -0,0 +1,39 @@
#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,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@@ -82,6 +82,10 @@ namespace Emby.Server.Implementations.HttpServer
{
return null;
}
else if (inString.Length == 0)
{
return inString;
}
var newString = new StringBuilder(inString.Length);

View File

@@ -1,8 +1,8 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Linq;
using System.Security.Authentication;
using Emby.Server.Implementations.SocketSharp;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -69,7 +69,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)
@@ -109,18 +109,12 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (user.Policy.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))
{
throw new SecurityException("User account has been disabled.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
throw new SecurityException("User account has been disabled.");
}
if (!user.Policy.IsAdministrator
@@ -129,10 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
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.");
}
}
@@ -191,10 +182,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (user == null || !user.Policy.IsAdministrator)
{
throw new SecurityException("User does not have admin access.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
throw new SecurityException("User does not have admin access.");
}
}
@@ -202,10 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (user == null || !user.Policy.EnableContentDeletion)
{
throw new SecurityException("User does not have delete access.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
throw new SecurityException("User does not have delete access.");
}
}
@@ -213,10 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (user == null || !user.Policy.EnableContentDownloading)
{
throw new SecurityException("User does not have download access.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
throw new SecurityException("User does not have download access.");
}
}
}
@@ -231,14 +213,14 @@ 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))

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using MediaBrowser.Controller.Entities;

View File

@@ -1,12 +1,13 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
namespace Emby.Server.Implementations.IO
{
public class ExtendedFileSystemInfo
{
public bool IsHidden { get; set; }
public bool IsReadOnly { get; set; }
public bool Exists { get; set; }
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -15,27 +14,29 @@ namespace Emby.Server.Implementations.IO
{
public class FileRefresher : IDisposable
{
private ILogger Logger { get; set; }
private ILibraryManager LibraryManager { get; set; }
private IServerConfigurationManager ConfigurationManager { get; set; }
private readonly List<string> _affectedPaths = new List<string>();
private Timer _timer;
private readonly object _timerLock = new object();
public string Path { get; private set; }
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _configurationManager;
public event EventHandler<EventArgs> Completed;
private readonly List<string> _affectedPaths = new List<string>();
private readonly object _timerLock = new object();
private Timer _timer;
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
{
logger.LogDebug("New file refresher created for {0}", path);
Path = path;
ConfigurationManager = configurationManager;
LibraryManager = libraryManager;
Logger = logger;
_configurationManager = configurationManager;
_libraryManager = libraryManager;
_logger = logger;
AddPath(path);
}
public event EventHandler<EventArgs> Completed;
public string Path { get; private set; }
private void AddAffectedPath(string path)
{
if (string.IsNullOrEmpty(path))
@@ -80,11 +81,11 @@ namespace Emby.Server.Implementations.IO
if (_timer == null)
{
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
}
else
{
_timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
_timer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
}
}
}
@@ -93,7 +94,7 @@ namespace Emby.Server.Implementations.IO
{
lock (_timerLock)
{
Logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
_logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
Path = path;
AddAffectedPath(path);
@@ -116,7 +117,7 @@ namespace Emby.Server.Implementations.IO
paths = _affectedPaths.ToList();
}
Logger.LogDebug("Timer stopped.");
_logger.LogDebug("Timer stopped.");
DisposeTimer();
Completed?.Invoke(this, EventArgs.Empty);
@@ -127,7 +128,7 @@ namespace Emby.Server.Implementations.IO
}
catch (Exception ex)
{
Logger.LogError(ex, "Error processing directory changes");
_logger.LogError(ex, "Error processing directory changes");
}
}
@@ -147,7 +148,7 @@ namespace Emby.Server.Implementations.IO
continue;
}
Logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
_logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
try
{
@@ -158,11 +159,11 @@ namespace Emby.Server.Implementations.IO
// For now swallow and log.
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
// Should we remove it from it's parent?
Logger.LogError(ex, "Error refreshing {name}", item.Name);
_logger.LogError(ex, "Error refreshing {name}", item.Name);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error refreshing {name}", item.Name);
_logger.LogError(ex, "Error refreshing {name}", item.Name);
}
}
}
@@ -178,7 +179,7 @@ namespace Emby.Server.Implementations.IO
while (item == null && !string.IsNullOrEmpty(path))
{
item = LibraryManager.FindByPath(path, null);
item = _libraryManager.FindByPath(path, null);
path = System.IO.Path.GetDirectoryName(path);
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -18,6 +17,11 @@ namespace Emby.Server.Implementations.IO
{
public class LibraryMonitor : ILibraryMonitor
{
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
/// <summary>
/// The file system watchers.
/// </summary>
@@ -114,34 +118,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;
}
@@ -152,7 +145,7 @@ namespace Emby.Server.Implementations.IO
return false;
}
var options = LibraryManager.GetLibraryOptions(item);
var options = _libraryManager.GetLibraryOptions(item);
if (options != null)
{
@@ -164,12 +157,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)
@@ -262,7 +255,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;
}
@@ -298,7 +291,7 @@ namespace Emby.Server.Implementations.IO
if (_fileSystemWatchers.TryAdd(path, newWatcher))
{
newWatcher.EnableRaisingEvents = true;
Logger.LogInformation("Watching directory " + path);
_logger.LogInformation("Watching directory " + path);
}
else
{
@@ -308,7 +301,7 @@ namespace Emby.Server.Implementations.IO
}
catch (Exception ex)
{
Logger.LogError(ex, "Error watching path: {path}", path);
_logger.LogError(ex, "Error watching path: {path}", path);
}
});
}
@@ -334,7 +327,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;
@@ -373,7 +366,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);
}
@@ -391,7 +384,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);
}
}
@@ -417,13 +410,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;
}
@@ -431,7 +424,7 @@ 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;
}
@@ -486,7 +479,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);
}
@@ -503,8 +496,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

@@ -1,10 +1,9 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
@@ -17,7 +16,7 @@ using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Server.Implementations.IO
{
/// <summary>
/// Class ManagedFileSystem
/// Class ManagedFileSystem.
/// </summary>
public class ManagedFileSystem : IFileSystem
{
@@ -80,20 +79,20 @@ namespace Emby.Server.Implementations.IO
public virtual string MakeAbsolutePath(string folderPath, string filePath)
{
if (string.IsNullOrWhiteSpace(filePath)
// stream
|| filePath.Contains("://"))
// path is actually a stream
if (string.IsNullOrWhiteSpace(filePath) || filePath.Contains("://", StringComparison.Ordinal))
{
return filePath;
}
if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/')
{
return filePath; // absolute local path
// absolute local path
return filePath;
}
// unc path
if (filePath.StartsWith("\\\\"))
if (filePath.StartsWith("\\\\", StringComparison.Ordinal))
{
return filePath;
}
@@ -101,13 +100,16 @@ namespace Emby.Server.Implementations.IO
var firstChar = filePath[0];
if (firstChar == '/')
{
// For this we don't really know.
// for this we don't really know
return filePath;
}
if (firstChar == '\\') //relative path
// relative path
if (firstChar == '\\')
{
filePath = filePath.Substring(1);
}
try
{
return Path.GetFullPath(Path.Combine(folderPath, filePath));
@@ -131,11 +133,7 @@ namespace Emby.Server.Implementations.IO
/// </summary>
/// <param name="shortcutPath">The shortcut path.</param>
/// <param name="target">The target.</param>
/// <exception cref="ArgumentNullException">
/// shortcutPath
/// or
/// target
/// </exception>
/// <exception cref="ArgumentNullException">The shortcutPath or target is null.</exception>
public virtual void CreateShortcut(string shortcutPath, string target)
{
if (string.IsNullOrEmpty(shortcutPath))
@@ -281,11 +279,11 @@ namespace Emby.Server.Implementations.IO
}
/// <summary>
/// Takes a filename and removes invalid characters
/// Takes a filename and removes invalid characters.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">filename</exception>
/// <exception cref="ArgumentNullException">The filename is null.</exception>
public virtual string GetValidFilename(string filename)
{
var builder = new StringBuilder(filename);
@@ -366,90 +364,9 @@ namespace Emby.Server.Implementations.IO
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
}
/// <summary>
/// Gets the file stream.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="mode">The mode.</param>
/// <param name="access">The access.</param>
/// <param name="share">The share.</param>
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
/// <returns>FileStream.</returns>
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
{
if (isAsync)
{
return GetFileStream(path, mode, access, share, FileOpenOptions.Asynchronous);
}
return GetFileStream(path, mode, access, share, FileOpenOptions.None);
}
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
=> new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 4096, GetFileOptions(fileOpenOptions));
private static FileOptions GetFileOptions(FileOpenOptions mode)
{
var val = (int)mode;
return (FileOptions)val;
}
private static FileMode GetFileMode(FileOpenMode mode)
{
switch (mode)
{
//case FileOpenMode.Append:
// return FileMode.Append;
case FileOpenMode.Create:
return FileMode.Create;
case FileOpenMode.CreateNew:
return FileMode.CreateNew;
case FileOpenMode.Open:
return FileMode.Open;
case FileOpenMode.OpenOrCreate:
return FileMode.OpenOrCreate;
//case FileOpenMode.Truncate:
// return FileMode.Truncate;
default:
throw new Exception("Unrecognized FileOpenMode");
}
}
private static FileAccess GetFileAccess(FileAccessMode mode)
{
switch (mode)
{
//case FileAccessMode.ReadWrite:
// return FileAccess.ReadWrite;
case FileAccessMode.Write:
return FileAccess.Write;
case FileAccessMode.Read:
return FileAccess.Read;
default:
throw new Exception("Unrecognized FileAccessMode");
}
}
private static FileShare GetFileShare(FileShareMode mode)
{
switch (mode)
{
case FileShareMode.ReadWrite:
return FileShare.ReadWrite;
case FileShareMode.Write:
return FileShare.Write;
case FileShareMode.Read:
return FileShare.Read;
case FileShareMode.None:
return FileShare.None;
default:
throw new Exception("Unrecognized FileShareMode");
}
}
public virtual void SetHidden(string path, bool isHidden)
{
if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
if (OperatingSystem.Id != OperatingSystemId.Windows)
{
return;
}
@@ -473,7 +390,7 @@ namespace Emby.Server.Implementations.IO
public virtual void SetReadOnly(string path, bool isReadOnly)
{
if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
if (OperatingSystem.Id != OperatingSystemId.Windows)
{
return;
}
@@ -497,7 +414,7 @@ namespace Emby.Server.Implementations.IO
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
{
if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
if (OperatingSystem.Id != OperatingSystemId.Windows)
{
return;
}
@@ -670,11 +587,11 @@ namespace Emby.Server.Implementations.IO
// some drives on linux have no actual size or are used for other purposes
return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram)
.Select(d => new FileSystemMetadata
{
Name = d.Name,
FullName = d.RootDirectory.FullName,
IsDirectory = true
}).ToList();
{
Name = d.Name,
FullName = d.RootDirectory.FullName,
IsDirectory = true
}).ToList();
}
public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
@@ -780,7 +697,7 @@ namespace Emby.Server.Implementations.IO
public virtual void SetExecutable(string path)
{
if (OperatingSystem.Id == MediaBrowser.Model.System.OperatingSystemId.Darwin)
if (OperatingSystem.Id == OperatingSystemId.Darwin)
{
RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.IO;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Buffers;

View File

@@ -3,33 +3,38 @@ namespace Emby.Server.Implementations
public interface IStartupOptions
{
/// <summary>
/// --ffmpeg
/// Gets the value of the --ffmpeg command line option.
/// </summary>
string FFmpegPath { get; }
/// <summary>
/// --service
/// Gets the value of the --service command line option.
/// </summary>
bool IsService { get; }
/// <summary>
/// --noautorunwebapp
/// Gets the value of the --noautorunwebapp command line option.
/// </summary>
bool NoAutoRunWebApp { get; }
/// <summary>
/// --package-name
/// Gets the value of the --package-name command line option.
/// </summary>
string PackageName { get; }
/// <summary>
/// --restartpath
/// Gets the value of the --restartpath command line option.
/// </summary>
string RestartPath { get; }
/// <summary>
/// --restartargs
/// Gets the value of the --restartargs command line option.
/// </summary>
string RestartArgs { get; }
/// <summary>
/// Gets the value of the --plugin-manifest-url command line option.
/// </summary>
string PluginManifestUrl { get; }
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library
{
if (resolvedUser == null)
{
throw new ArgumentNullException(nameof(resolvedUser));
throw new AuthenticationException($"Specified user does not exist.");
}
bool success = false;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Globalization;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -29,14 +28,15 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Net;
@@ -54,6 +54,29 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public class LibraryManager : ILibraryManager
{
private readonly ILogger _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 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>
@@ -86,12 +109,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>
@@ -107,87 +124,47 @@ 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;
/// <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>
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)
Lazy<IProviderManager> providerManagerFactory,
Lazy<IUserViewManager> userviewManagerFactory,
IMediaEncoder mediaEncoder,
IItemRepository itemRepository)
{
_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;
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
RecordConfigurationValues(configurationManager.Configuration);
}
@@ -266,7 +243,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;
@@ -300,7 +277,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)
@@ -431,10 +408,10 @@ namespace Emby.Server.Implementations.Library
item.SetParent(null);
ItemRepository.DeleteItem(item.Id, CancellationToken.None);
_itemRepository.DeleteItem(item.Id);
foreach (var child in children)
{
ItemRepository.DeleteItem(child.Id, CancellationToken.None);
_itemRepository.DeleteItem(child.Id);
}
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
@@ -503,15 +480,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();
}
@@ -544,7 +521,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,
@@ -708,13 +685,13 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Creates the root media folder
/// Creates the root media folder.
/// </summary>
/// <returns>AggregateFolder.</returns>
/// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded</exception>
/// <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);
@@ -728,7 +705,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);
@@ -780,7 +757,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);
@@ -822,7 +799,6 @@ namespace Emby.Server.Implementations.Library
{
// If this returns multiple items it could be tricky figuring out which one is correct.
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
@@ -842,7 +818,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Gets a Person
/// Gets the person.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Person}.</returns>
@@ -852,7 +828,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Gets a Studio
/// Gets the studio.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Studio}.</returns>
@@ -877,7 +853,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Gets a Genre
/// Gets the genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Genre}.</returns>
@@ -887,7 +863,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Gets the genre.
/// Gets the music genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{MusicGenre}.</returns>
@@ -897,7 +873,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
/// Gets a Year
/// Gets the year.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>Task{Year}.</returns>
@@ -938,7 +914,6 @@ namespace Emby.Server.Implementations.Library
IncludeItemTypes = new[] { typeof(T).Name },
Name = name,
DtoOptions = options
}).Cast<MusicArtist>()
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
.Cast<T>()
@@ -976,7 +951,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);
}
@@ -990,7 +965,7 @@ 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);
}
@@ -1027,7 +1002,7 @@ namespace Emby.Server.Implementations.Library
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
IsScanRunning = true;
_libraryMonitorFactory().Stop();
LibraryMonitor.Stop();
try
{
@@ -1035,7 +1010,7 @@ namespace Emby.Server.Implementations.Library
}
finally
{
_libraryMonitorFactory().Start();
LibraryMonitor.Start();
IsScanRunning = false;
}
}
@@ -1074,9 +1049,9 @@ namespace Emby.Server.Implementations.Library
var innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(pct => progress.Report(pct * .96));
innerProgress.RegisterAction(pct => progress.Report(pct * 0.96));
// Now validate the entire media library
// Validate the entire media library
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
progress.Report(96);
@@ -1085,7 +1060,6 @@ namespace Emby.Server.Implementations.Library
innerProgress.RegisterAction(pct => progress.Report(96 + (pct * .04)));
// Run post-scan tasks
await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false);
progress.Report(100);
@@ -1136,7 +1110,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
_logger.LogError(ex, "Error running postscan task");
_logger.LogError(ex, "Error running post-scan task");
}
numComplete++;
@@ -1145,7 +1119,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(percent * 100);
}
ItemRepository.UpdateInheritedValues(cancellationToken);
_itemRepository.UpdateInheritedValues(cancellationToken);
progress.Report(100);
}
@@ -1165,11 +1139,10 @@ 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))
.OrderBy(i => i.Name)
.ToList();
}
@@ -1243,7 +1216,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;
}
@@ -1274,7 +1247,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)
@@ -1298,7 +1271,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)
@@ -1313,7 +1286,7 @@ namespace Emby.Server.Implementations.Library
}
}
return ItemRepository.GetItemList(query);
return _itemRepository.GetItemList(query);
}
public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
@@ -1325,12 +1298,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()
};
}
@@ -1341,7 +1314,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)
@@ -1352,7 +1325,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetStudios(query);
return _itemRepository.GetStudios(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
@@ -1363,7 +1336,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetGenres(query);
return _itemRepository.GetGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
@@ -1374,7 +1347,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetMusicGenres(query);
return _itemRepository.GetMusicGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
@@ -1385,7 +1358,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetAllArtists(query);
return _itemRepository.GetAllArtists(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
@@ -1396,30 +1369,37 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetArtists(query);
return _itemRepository.GetArtists(query);
}
private void SetTopParentOrAncestorIds(InternalItemsQuery query)
{
if (query.AncestorIds.Length == 0)
var ancestorIds = query.AncestorIds;
int len = ancestorIds.Length;
if (len == 0)
{
return;
}
var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList();
if (parents.All(i => i is ICollectionFolder || i is UserView))
var parents = new BaseItem[len];
for (int i = 0; i < len; i++)
{
// Optimize by querying against top level views
query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
query.AncestorIds = Array.Empty<Guid>();
// Prevent searching in all libraries due to empty filter
if (query.TopParentIds.Length == 0)
parents[i] = GetItemById(ancestorIds[i]);
if (!(parents[i] is ICollectionFolder || parents[i] is UserView))
{
query.TopParentIds = new[] { Guid.NewGuid() };
return;
}
}
// Optimize by querying against top level views
query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
query.AncestorIds = Array.Empty<Guid>();
// Prevent searching in all libraries due to empty filter
if (query.TopParentIds.Length == 0)
{
query.TopParentIds = new[] { Guid.NewGuid() };
}
}
public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
@@ -1430,7 +1410,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
return ItemRepository.GetAlbumArtists(query);
return _itemRepository.GetAlbumArtists(query);
}
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
@@ -1451,10 +1431,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>
{
@@ -1500,7 +1480,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,
@@ -1580,7 +1560,7 @@ namespace Emby.Server.Implementations.Library
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
{
var tasks = IntroProviders
.OrderBy(i => i.GetType().Name.IndexOf("Default", StringComparison.OrdinalIgnoreCase) == -1 ? 0 : 1)
.OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
.Take(1)
.Select(i => GetIntros(i, item, user));
@@ -1800,7 +1780,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)
{
@@ -1837,7 +1817,7 @@ namespace Emby.Server.Implementations.Library
public void UpdateImages(BaseItem item)
{
ItemRepository.SaveImages(item);
_itemRepository.SaveImages(item);
RegisterItem(item);
}
@@ -1854,7 +1834,7 @@ namespace Emby.Server.Implementations.Library
{
if (item.IsFileProtocol)
{
_providerManagerFactory().SaveMetadata(item, updateReason);
ProviderManager.SaveMetadata(item, updateReason);
}
item.DateLastSaved = DateTime.UtcNow;
@@ -1862,7 +1842,7 @@ namespace Emby.Server.Implementations.Library
RegisterItem(item);
}
ItemRepository.SaveItems(itemsList, cancellationToken);
_itemRepository.SaveItems(itemsList, cancellationToken);
if (ItemUpdated != null)
{
@@ -1938,7 +1918,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)
@@ -2057,7 +2037,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)));
@@ -2106,7 +2086,7 @@ namespace Emby.Server.Implementations.Library
string sortName)
{
var path = Path.Combine(
ConfigurationManager.ApplicationPaths.InternalMetadataPath,
_configurationManager.ApplicationPaths.InternalMetadataPath,
"views",
_fileSystem.GetValidFilename(viewType));
@@ -2138,7 +2118,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;
@@ -2156,7 +2136,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;
@@ -2193,7 +2173,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
_providerManagerFactory().QueueRefresh(
ProviderManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -2260,7 +2240,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
_providerManagerFactory().QueueRefresh(
ProviderManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -2294,7 +2274,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;
@@ -2337,7 +2317,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
_providerManagerFactory().QueueRefresh(
ProviderManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -2355,36 +2335,25 @@ namespace Emby.Server.Implementations.Library
string videoPath,
string[] files)
{
new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
}
public bool IsVideoFile(string path, LibraryOptions libraryOptions)
/// <inheritdoc />
public bool IsVideoFile(string path)
{
var resolver = new VideoResolver(GetNamingOptions());
return resolver.IsVideoFile(path);
}
public bool IsVideoFile(string path)
{
return IsVideoFile(path, new LibraryOptions());
}
public bool IsAudioFile(string path, LibraryOptions libraryOptions)
{
var parser = new AudioFileParser(GetNamingOptions());
return parser.IsAudioFile(path);
}
/// <inheritdoc />
public bool IsAudioFile(string path)
{
return IsAudioFile(path, new LibraryOptions());
}
=> AudioFileParser.IsAudioFile(path, GetNamingOptions());
/// <inheritdoc />
public int? GetSeasonNumberFromPath(string path)
{
return new SeasonPathParser().Parse(path, true, true).SeasonNumber;
}
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
/// <inheritdoc />
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
{
var series = episode.Series;
@@ -2408,6 +2377,38 @@ namespace Emby.Server.Implementations.Library
episodeInfo = new Naming.TV.EpisodeInfo();
}
try
{
var libraryOptions = GetLibraryOptions(episode);
if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase))
{
// Read from metadata
var mediaInfo = _mediaEncoder.GetMediaInfo(new MediaInfoRequest
{
MediaSource = episode.GetMediaSources(false)[0],
MediaType = DlnaProfileType.Video
}, CancellationToken.None).GetAwaiter().GetResult();
if (mediaInfo.ParentIndexNumber > 0)
{
episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber;
}
if (mediaInfo.IndexNumber > 0)
{
episodeInfo.EpisodeNumber = mediaInfo.IndexNumber;
}
if (!string.IsNullOrEmpty(mediaInfo.ShowName))
{
episodeInfo.SeriesName = mediaInfo.ShowName;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error reading the episode informations with ffprobe. Episode: {EpisodeInfo}", episodeInfo.Path);
}
var changed = false;
if (episodeInfo.IsByDate)
@@ -2508,21 +2509,11 @@ namespace Emby.Server.Implementations.Library
}
public NamingOptions GetNamingOptions()
{
return GetNamingOptionsInternal();
}
private NamingOptions _namingOptions;
private string[] _videoFileExtensions;
private NamingOptions GetNamingOptionsInternal()
{
if (_namingOptions == null)
{
var options = new NamingOptions();
_namingOptions = options;
_videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
_namingOptions = new NamingOptions();
_videoFileExtensions = _namingOptions.VideoFileExtensions;
}
return _namingOptions;
@@ -2533,11 +2524,10 @@ namespace Emby.Server.Implementations.Library
var resolver = new VideoResolver(GetNamingOptions());
var result = resolver.CleanDateTime(name);
var cleanName = resolver.CleanString(result.Name);
return new ItemLookupInfo
{
Name = cleanName.Name,
Name = resolver.TryCleanString(result.Name, out var newName) ? newName.ToString() : result.Name,
Year = result.Year
};
}
@@ -2590,14 +2580,12 @@ namespace Emby.Server.Implementations.Library
}).OrderBy(i => i.Path);
}
private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" };
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var namingOptions = GetNamingOptions();
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
.Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
@@ -2658,8 +2646,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))
{
@@ -2670,7 +2658,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))
{
@@ -2739,7 +2727,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)
@@ -2762,7 +2750,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
{
@@ -2779,7 +2767,7 @@ namespace Emby.Server.Implementations.Library
public List<string> GetPeopleNames(InternalPeopleQuery query)
{
return ItemRepository.GetPeopleNames(query);
return _itemRepository.GetPeopleNames(query);
}
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
@@ -2789,7 +2777,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)
@@ -2800,7 +2788,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);
@@ -2833,7 +2821,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))
@@ -2852,7 +2840,7 @@ namespace Emby.Server.Implementations.Library
}
}
_libraryMonitorFactory().Stop();
LibraryMonitor.Stop();
try
{
@@ -2887,7 +2875,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();
}
}
}
@@ -2947,7 +2935,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);
@@ -2990,7 +2978,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);
@@ -3043,7 +3031,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);
@@ -3052,7 +3040,7 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The media folder does not exist");
}
_libraryMonitorFactory().Stop();
LibraryMonitor.Stop();
try
{
@@ -3072,7 +3060,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();
}
}
}
@@ -3086,7 +3074,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))
{
@@ -3101,11 +3089,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();
}
}
@@ -3116,7 +3104,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

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -34,13 +33,13 @@ namespace Emby.Server.Implementations.Library
private readonly ILibraryManager _libraryManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private IMediaSourceProvider[] _providers;
private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager;
private readonly Func<IMediaEncoder> _mediaEncoder;
private ILocalizationManager _localizationManager;
private IApplicationPaths _appPaths;
private readonly IMediaEncoder _mediaEncoder;
private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths;
private IMediaSourceProvider[] _providers;
public MediaSourceManager(
IItemRepository itemRepo,
@@ -48,16 +47,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;
@@ -497,7 +496,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);
}
@@ -622,7 +621,7 @@ 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,
@@ -675,7 +674,7 @@ 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,

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -36,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)
@@ -116,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

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

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

@@ -9,7 +9,7 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library
{
/// <summary>
/// Class ResolverHelper
/// Class ResolverHelper.
/// </summary>
public static class ResolverHelper
{
@@ -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

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -73,7 +72,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
// Return audio if the path is a file and has a matching extension
var libraryOptions = args.GetLibraryOptions();
var collectionType = args.GetCollectionType();
var isBooksCollectionType = string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase);
@@ -92,7 +90,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
if (LibraryManager.IsAudioFile(args.Path))
{
var extension = Path.GetExtension(args.Path);
@@ -105,7 +103,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
// For conflicting extensions, give priority to videos
if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path, libraryOptions))
if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path))
{
return null;
}
@@ -121,7 +119,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
item = new MediaBrowser.Controller.Entities.Audio.Audio();
}
else if (isBooksCollectionType)
{
item = new AudioBook();

View File

@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
@@ -76,15 +75,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
/// <summary>
/// Determine if the supplied file data points to a music album
/// Determine if the supplied file data points to a music album.
/// </summary>
public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions)
public bool IsMusicAlbum(string path, IDirectoryService directoryService)
{
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, libraryOptions, _libraryManager);
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager);
}
/// <summary>
/// Determine if the supplied resolve args should be considered a music album
/// Determine if the supplied resolve args should be considered a music album.
/// </summary>
/// <param name="args">The args.</param>
/// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns>
@@ -94,7 +93,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
if (args.IsDirectory)
{
// if (args.Parent is MusicArtist) return true; //saves us from testing children twice
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, args.GetLibraryOptions(), _libraryManager))
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
{
return true;
}
@@ -104,7 +103,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
/// <summary>
/// Determine if the supplied list contains what we should consider music
/// Determine if the supplied list contains what we should consider music.
/// </summary>
private bool ContainsMusic(
IEnumerable<FileSystemMetadata> list,
@@ -112,12 +111,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
IDirectoryService directoryService,
ILogger logger,
IFileSystem fileSystem,
LibraryOptions libraryOptions,
ILibraryManager libraryManager)
{
var discSubfolderCount = 0;
var notMultiDisc = false;
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var parser = new AlbumParser(namingOptions);
foreach (var fileSystemInfo in list)
{
if (fileSystemInfo.IsDirectory)
@@ -130,11 +130,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
var path = fileSystemInfo.FullName;
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager);
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
if (hasMusic)
{
if (IsMultiDiscFolder(path, libraryOptions))
if (parser.IsMultiPart(path))
{
logger.LogDebug("Found multi-disc folder: " + path);
discSubfolderCount++;
@@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
var fullName = fileSystemInfo.FullName;
if (libraryManager.IsAudioFile(fullName, libraryOptions))
if (libraryManager.IsAudioFile(fullName))
{
return true;
}
@@ -165,15 +165,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return discSubfolderCount > 0;
}
private bool IsMultiDiscFolder(string path, LibraryOptions libraryOptions)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var parser = new AlbumParser(namingOptions);
var result = parser.ParseMultiPart(path);
return result.IsMultiPart;
}
}
}

View File

@@ -27,7 +27,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="config">The configuration manager.</param>
public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config)
public MusicArtistResolver(
ILogger<MusicArtistResolver> logger,
IFileSystem fileSystem,
ILibraryManager libraryManager,
IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
@@ -80,14 +84,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
// Avoid mis-identifying top folders
if (args.Parent.IsRoot) return null;
if (args.Parent.IsRoot)
{
return null;
}
var directoryService = args.DirectoryService;
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
// If we contain an album assume we are an artist folder
return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null;
return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null;
}
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.IO;
@@ -80,6 +79,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
};
break;
}
if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService))
{
videoInfo = parser.ResolveDirectory(args.Path);
@@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
if (LibraryManager.IsVideoFile(args.Path, args.GetLibraryOptions()) || videoInfo.IsStub)
if (LibraryManager.IsVideoFile(args.Path) || videoInfo.IsStub)
{
var path = args.Path;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.IO;

View File

@@ -21,6 +21,28 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// </summary>
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
private string[] _validCollectionTypes = new[]
{
CollectionType.Movies,
CollectionType.HomeVideos,
CollectionType.MusicVideos,
CollectionType.Movies,
CollectionType.Photos
};
private readonly IImageProcessor _imageProcessor;
/// <summary>
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="imageProcessor">The image processor.</param>
public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
: base(libraryManager)
{
_imageProcessor = imageProcessor;
}
/// <summary>
/// Gets the priority.
/// </summary>
@@ -144,7 +166,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
foreach (var video in resolverResult)
{
var firstVideo = video.Files.First();
var firstVideo = video.Files[0];
var videoItem = new T
{
@@ -230,7 +252,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// Owned items will be caught by the plain video resolver
if (args.Parent == null)
{
//return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
// return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
@@ -275,7 +297,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
item = ResolveVideo<Movie>(args, true);
}
else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
@@ -319,7 +340,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
if (item is Movie || item is MusicVideo)
{
//we need to only look at the name of this actual item (not parents)
// We need to only look at the name of this actual item (not parents)
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
if (!string.IsNullOrEmpty(justName))
@@ -347,9 +368,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
/// <summary>
/// Finds a movie based on a child file system entries
/// Finds a movie based on a child file system entries.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>Movie.</returns>
private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
where T : Video, new()
@@ -377,6 +397,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
Set3DFormat(movie);
return movie;
}
if (IsBluRayDirectory(child.FullName, filename, directoryService))
{
var movie = new T
@@ -407,15 +428,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
// TODO: Allow GetMultiDiscMovie in here
const bool supportsMultiVersion = true;
const bool SupportsMultiVersion = true;
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
if (result.Items.Count == 1)
{
var videoPath = result.Items[0].Path;
var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, libraryOptions, videoPath, i.Name));
var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, videoPath, i.Name));
if (!hasPhotos)
{
@@ -425,8 +446,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return movie;
}
}
if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
else if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
{
return GetMultiDiscMovie<T>(multiDiscFolders, directoryService);
}
@@ -437,7 +457,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <summary>
/// Gets the multi disc movie.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="multiDiscFolders">The folders.</param>
/// <param name="directoryService">The directory service.</param>
/// <returns>``0.</returns>
@@ -451,7 +470,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
var subFileEntries = directoryService.GetFileSystemEntries(i);
var subfolders = subFileEntries
.Where(e => e.IsDirectory)
.Where(e => e.IsDirectory)
.ToList();
if (subfolders.Any(s => IsDvdDirectory(s.FullName, s.Name, directoryService)))
@@ -459,6 +478,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
videoTypes.Add(VideoType.Dvd);
return true;
}
if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService)))
{
videoTypes.Add(VideoType.BluRay);
@@ -476,7 +496,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
return false;
}).OrderBy(i => i).ToList();
// If different video types were found, don't allow this
@@ -491,24 +510,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
var resolver = new StackResolver(namingOptions);
var result = resolver.ResolveDirectories(folderPaths);
var result = new StackResolver(namingOptions).ResolveDirectories(folderPaths).ToList();
if (result.Stacks.Count != 1)
if (result.Count != 1)
{
return null;
}
int additionalPartsLen = folderPaths.Count - 1;
var additionalParts = new string[additionalPartsLen];
folderPaths.CopyTo(1, additionalParts, 0, additionalPartsLen);
var returnVideo = new T
{
Path = folderPaths[0],
AdditionalParts = folderPaths.Skip(1).ToArray(),
AdditionalParts = additionalParts,
VideoType = videoTypes[0],
Name = result.Stacks[0].Name
Name = result[0].Name
};
SetIsoType(returnVideo);
@@ -516,15 +535,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return returnVideo;
}
private string[] ValidCollectionTypes = new[]
{
CollectionType.Movies,
CollectionType.HomeVideos,
CollectionType.MusicVideos,
CollectionType.Movies,
CollectionType.Photos
};
private bool IsInvalid(Folder parent, string collectionType)
{
if (parent != null)
@@ -540,20 +550,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return false;
}
return !ValidCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
}
private IImageProcessor _imageProcessor;
/// <summary>
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="imageProcessor">The image processor.</param>
public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
: base(libraryManager)
{
_imageProcessor = imageProcessor;
return !_validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
}
}
}

View File

@@ -63,13 +63,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
if (!file.IsDirectory && PhotoResolver.IsImageFile(file.FullName, _imageProcessor))
{
var libraryOptions = args.GetLibraryOptions();
var filename = file.Name;
var ownedByMedia = false;
foreach (var siblingFile in files)
{
if (PhotoResolver.IsOwnedByMedia(_libraryManager, libraryOptions, siblingFile.FullName, filename))
if (PhotoResolver.IsOwnedByMedia(_libraryManager, siblingFile.FullName, filename))
{
ownedByMedia = true;
break;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -8,7 +7,6 @@ using System.Linq;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers
@@ -57,11 +55,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Make sure the image doesn't belong to a video file
var files = args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path));
var libraryOptions = args.GetLibraryOptions();
foreach (var file in files)
{
if (IsOwnedByMedia(_libraryManager, libraryOptions, file.FullName, filename))
if (IsOwnedByMedia(_libraryManager, file.FullName, filename))
{
return null;
}
@@ -78,17 +75,17 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
internal static bool IsOwnedByMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
internal static bool IsOwnedByMedia(ILibraryManager libraryManager, string file, string imageFilename)
{
if (libraryManager.IsVideoFile(file, libraryOptions))
if (libraryManager.IsVideoFile(file))
{
return IsOwnedByResolvedMedia(libraryManager, libraryOptions, file, imageFilename);
return IsOwnedByResolvedMedia(libraryManager, file, imageFilename);
}
return false;
}
internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, string file, string imageFilename)
=> imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase);
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)

View File

@@ -1,43 +1,37 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers
{
/// <summary>
/// <see cref="IItemResolver"/> for <see cref="Playlist"/> library items.
/// </summary>
public class PlaylistResolver : FolderResolver<Playlist>
{
private string[] SupportedCollectionTypes = new string[] {
private string[] _musicPlaylistCollectionTypes = new string[] {
string.Empty,
CollectionType.Music
};
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>BoxSet.</returns>
/// <inheritdoc/>
protected override Playlist Resolve(ItemResolveArgs args)
{
// It's a boxset if all of the following conditions are met:
// Is a Directory
// Contains [playlist] in the path
if (args.IsDirectory)
{
var filename = Path.GetFileName(args.Path);
if (string.IsNullOrEmpty(filename))
{
return null;
}
if (filename.IndexOf("[playlist]", StringComparison.OrdinalIgnoreCase) != -1)
// It's a boxset if the path is a directory with [playlist] in it's the name
// TODO: Should this use Path.GetDirectoryName() instead?
bool isBoxSet = Path.GetFileName(args.Path)
?.Contains("[playlist]", StringComparison.OrdinalIgnoreCase)
?? false;
if (isBoxSet)
{
return new Playlist
{
@@ -45,21 +39,32 @@ namespace Emby.Server.Implementations.Library.Resolvers
Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
};
}
}
else
{
if (SupportedCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
// It's a directory-based playlist if the directory contains a playlist file
var filePaths = Directory.EnumerateFiles(args.Path);
if (filePaths.Any(f => f.EndsWith(PlaylistXmlSaver.DefaultPlaylistFilename, StringComparison.OrdinalIgnoreCase)))
{
var extension = Path.GetExtension(args.Path);
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
return new Playlist
{
return new Playlist
{
Path = args.Path,
Name = Path.GetFileNameWithoutExtension(args.Path),
IsInMixedFolder = true
};
}
Path = args.Path,
Name = Path.GetFileName(args.Path)
};
}
}
// Check if this is a music playlist file
// It should have the correct collection type and a supported file extension
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
var extension = Path.GetExtension(args.Path);
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
return new Playlist
{
Path = args.Path,
Name = Path.GetFileNameWithoutExtension(args.Path),
IsInMixedFolder = true
};
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.IO;

View File

@@ -9,17 +9,12 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.TV
{
/// <summary>
/// Class SeasonResolver
/// Class SeasonResolver.
/// </summary>
public class SeasonResolver : FolderResolver<Season>
{
/// <summary>
/// The _config
/// </summary>
private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager;
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private readonly ILocalizationManager _localization;
private readonly ILogger _logger;
@@ -30,7 +25,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <param name="libraryManager">The library manager.</param>
/// <param name="localization">The localization</param>
/// <param name="logger">The logger</param>
public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, ILogger logger)
public SeasonResolver(
IServerConfigurationManager config,
ILibraryManager libraryManager,
ILocalizationManager localization,
ILogger<SeasonResolver> logger)
{
_config = config;
_libraryManager = libraryManager;
@@ -45,14 +44,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <returns>Season.</returns>
protected override Season Resolve(ItemResolveArgs args)
{
if (args.Parent is Series && args.IsDirectory)
if (args.Parent is Series series && args.IsDirectory)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var series = ((Series)args.Parent);
var path = args.Path;
var seasonParserResult = new SeasonPathParser().Parse(path, true, true);
var seasonParserResult = SeasonPathParser.Parse(path, true, true);
var season = new Season
{
@@ -74,7 +72,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{
if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue)
{
_logger.LogDebug("Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
_logger.LogDebug(
"Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
path,
episodeInfo.SeasonNumber.Value,
episodeInfo.EpisodeNumber.Value);
@@ -90,7 +89,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
season.Name = seasonNumber == 0 ?
args.LibraryOptions.SeasonZeroDisplayName :
string.Format(_localization.GetLocalizedString("NameSeasonNumber"), seasonNumber.ToString(UsCulture), args.GetLibraryOptions().PreferredMetadataLanguage);
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("NameSeasonNumber"),
seasonNumber,
args.GetLibraryOptions().PreferredMetadataLanguage);
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -9,7 +8,6 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
@@ -31,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <param name="fileSystem">The file system.</param>
/// <param name="logger">The logger.</param>
/// <param name="libraryManager">The library manager.</param>
public SeriesResolver(IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager)
public SeriesResolver(IFileSystem fileSystem, ILogger<SeriesResolver> logger, ILibraryManager libraryManager)
{
_fileSystem = fileSystem;
_logger = logger;
@@ -102,7 +100,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false))
if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, false))
{
return new Series
{
@@ -123,24 +121,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
IFileSystem fileSystem,
ILogger logger,
ILibraryManager libraryManager,
LibraryOptions libraryOptions,
bool isTvContentType)
{
foreach (var child in fileSystemChildren)
{
//if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
//{
// //logger.LogDebug("Igoring series file or folder marked hidden: {0}", child.FullName);
// continue;
//}
// Can't enforce this because files saved by Bitcasa are always marked System
//if ((attributes & FileAttributes.System) == FileAttributes.System)
//{
// logger.LogDebug("Igoring series subfolder marked system: {0}", child.FullName);
// continue;
//}
if (child.IsDirectory)
{
if (IsSeasonFolder(child.FullName, isTvContentType, libraryManager))
@@ -152,7 +136,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
else
{
string fullName = child.FullName;
if (libraryManager.IsVideoFile(fullName, libraryOptions))
if (libraryManager.IsVideoFile(fullName))
{
if (isTvContentType)
{
@@ -203,7 +187,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
{
var seasonNumber = new SeasonPathParser().Parse(path, isTvContentType, isTvContentType).SeasonNumber;
var seasonNumber = SeasonPathParser.Parse(path, isTvContentType, isTvContentType).SeasonNumber;
return seasonNumber.HasValue;
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -18,16 +17,15 @@ namespace Emby.Server.Implementations.Library
{
public class SearchEngine : ISearchEngine
{
private readonly ILogger _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)

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -29,25 +28,24 @@ namespace Emby.Server.Implementations.Library
private readonly ILogger _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);
}
@@ -72,7 +70,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);
@@ -97,9 +95,9 @@ 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>
@@ -109,14 +107,14 @@ namespace Emby.Server.Implementations.Library
/// <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);
}
@@ -132,7 +130,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)
{

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -21,6 +20,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
@@ -45,22 +45,14 @@ namespace Emby.Server.Implementations.Library
{
private readonly object _policySyncLock = new object();
private readonly object _configSyncLock = new object();
/// <summary>
/// The logger.
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// Gets the active user repository.
/// </summary>
/// <value>The user repository.</value>
private readonly ILogger _logger;
private readonly IUserRepository _userRepository;
private readonly IXmlSerializer _xmlSerializer;
private readonly IJsonSerializer _jsonSerializer;
private readonly INetworkManager _networkManager;
private readonly Func<IImageProcessor> _imageProcessorFactory;
private readonly Func<IDtoService> _dtoServiceFactory;
private readonly IImageProcessor _imageProcessor;
private readonly Lazy<IDtoService> _dtoServiceFactory;
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
private readonly ICryptoProvider _cryptoProvider;
@@ -75,13 +67,15 @@ namespace Emby.Server.Implementations.Library
private IPasswordResetProvider[] _passwordResetProviders;
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
private IDtoService DtoService => _dtoServiceFactory.Value;
public UserManager(
ILogger<UserManager> logger,
IUserRepository userRepository,
IXmlSerializer xmlSerializer,
INetworkManager networkManager,
Func<IImageProcessor> imageProcessorFactory,
Func<IDtoService> dtoServiceFactory,
IImageProcessor imageProcessor,
Lazy<IDtoService> dtoServiceFactory,
IServerApplicationHost appHost,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
@@ -91,7 +85,7 @@ namespace Emby.Server.Implementations.Library
_userRepository = userRepository;
_xmlSerializer = xmlSerializer;
_networkManager = networkManager;
_imageProcessorFactory = imageProcessorFactory;
_imageProcessor = imageProcessor;
_dtoServiceFactory = dtoServiceFactory;
_appHost = appHost;
_jsonSerializer = jsonSerializer;
@@ -265,6 +259,7 @@ namespace Emby.Server.Implementations.Library
{
if (string.IsNullOrWhiteSpace(username))
{
_logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint);
throw new ArgumentNullException(nameof(username));
}
@@ -291,10 +286,11 @@ namespace Emby.Server.Implementations.Library
&& authenticationProvider != null
&& !(authenticationProvider is DefaultAuthenticationProvider))
{
// We should trust the user that the authprovider says, not what was typed
// Trust the username returned by the authentication provider
username = updatedUsername;
// Search the database for the user again; the authprovider might have created it
// Search the database for the user again
// the authentication provider might have created it
user = Users
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
@@ -319,26 +315,26 @@ namespace Emby.Server.Implementations.Library
if (user == null)
{
_logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("Invalid username or password entered.");
}
if (user.Policy.IsDisabled)
{
throw new AuthenticationException(
string.Format(
CultureInfo.InvariantCulture,
"The {0} account is currently disabled. Please consult with your administrator.",
user.Name));
_logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator.");
}
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
throw new AuthenticationException("Forbidden.");
_logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
throw new SecurityException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed())
{
throw new AuthenticationException("User is not allowed access at this time.");
_logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
throw new SecurityException("User is not allowed access at this time.");
}
// Update LastActivityDate and LastLoginDate, then save
@@ -351,14 +347,14 @@ namespace Emby.Server.Implementations.Library
}
ResetInvalidLoginAttemptCount(user);
_logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name);
}
else
{
IncrementInvalidLoginAttemptCount(user);
_logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint);
}
_logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
return success ? user : null;
}
@@ -600,7 +596,7 @@ namespace Emby.Server.Implementations.Library
try
{
_dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user);
DtoService.AttachPrimaryImageAspectRatio(dto, user);
}
catch (Exception ex)
{
@@ -625,7 +621,7 @@ namespace Emby.Server.Implementations.Library
{
try
{
return _imageProcessorFactory().GetImageCacheTag(item, image);
return _imageProcessor.GetImageCacheTag(item, image);
}
catch (Exception ex)
{
@@ -667,7 +663,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException("Invalid username", nameof(newName));
}
if (user.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))
if (user.Name.Equals(newName, StringComparison.Ordinal))
{
throw new ArgumentException("The new and old names must be different.");
}
@@ -805,17 +801,17 @@ namespace Emby.Server.Implementations.Library
// Delete user config dir
lock (_configSyncLock)
lock (_policySyncLock)
{
try
lock (_policySyncLock)
{
Directory.Delete(user.ConfigurationDirectoryPath, true);
try
{
Directory.Delete(user.ConfigurationDirectoryPath, true);
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting user config dir: {Path}", user.ConfigurationDirectoryPath);
}
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting user config dir: {Path}", user.ConfigurationDirectoryPath);
}
}
_users.TryRemove(user.Id, out _);

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using System.Collections.Generic;

View File

@@ -25,7 +25,10 @@ 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 ArtistsPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
public ArtistsPostScanTask(
ILibraryManager libraryManager,
ILogger<ArtistsPostScanTask> logger,
IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;

View File

@@ -25,7 +25,10 @@ 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 GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
public GenresPostScanTask(
ILibraryManager libraryManager,
ILogger<GenresPostScanTask> logger,
IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;

View File

@@ -25,7 +25,10 @@ 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 MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
public MusicGenresPostScanTask(
ILibraryManager libraryManager,
ILogger<MusicGenresPostScanTask> logger,
IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;

View File

@@ -26,7 +26,10 @@ 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 StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
public StudiosPostScanTask(
ILibraryManager libraryManager,
ILogger<StudiosPostScanTask> logger,
IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.IO;
using System.Net.Http;
@@ -15,14 +17,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
private readonly IStreamHelper _streamHelper;
public DirectRecorder(ILogger logger, IHttpClient httpClient, IFileSystem fileSystem, IStreamHelper streamHelper)
public DirectRecorder(ILogger logger, IHttpClient httpClient, IStreamHelper streamHelper)
{
_logger = logger;
_httpClient = httpClient;
_fileSystem = fileSystem;
_streamHelper = streamHelper;
}
@@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
{
onStarted();
@@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
UserAgent = "Emby/3.0",
// Shouldn't matter but may cause issues
DecompressionMethod = CompressionMethod.None
DecompressionMethod = CompressionMethods.None
};
using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
@@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
{
onStarted();

View File

@@ -1,9 +1,9 @@
#pragma warning disable SA1600
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -26,11 +26,9 @@ using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
@@ -63,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly ILibraryManager _libraryManager;
private readonly IProviderManager _providerManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IProcessFactory _processFactory;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IStreamHelper _streamHelper;
@@ -81,7 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IServerApplicationHost appHost,
IStreamHelper streamHelper,
IMediaSourceManager mediaSourceManager,
ILogger logger,
ILogger<EmbyTV> logger,
IJsonSerializer jsonSerializer,
IHttpClient httpClient,
IServerConfigurationManager config,
@@ -90,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
ILibraryManager libraryManager,
ILibraryMonitor libraryMonitor,
IProviderManager providerManager,
IMediaEncoder mediaEncoder,
IProcessFactory processFactory)
IMediaEncoder mediaEncoder)
{
Current = this;
@@ -104,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_libraryMonitor = libraryMonitor;
_providerManager = providerManager;
_mediaEncoder = mediaEncoder;
_processFactory = processFactory;
_liveTvManager = (LiveTvManager)liveTvManager;
_jsonSerializer = jsonSerializer;
_mediaSourceManager = mediaSourceManager;
@@ -427,7 +422,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
foreach (NameValuePair mapping in mappings)
{
if (StringHelper.EqualsIgnoreCase(mapping.Name, channelId))
if (string.Equals(mapping.Name, channelId, StringComparison.OrdinalIgnoreCase))
{
return mapping.Value;
}
@@ -1064,7 +1059,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.GetLocalApiUrl("127.0.0.1", true) + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
EncoderProtocol = MediaProtocol.Http,
Path = info.Path,
Protocol = MediaProtocol.File,
@@ -1664,10 +1659,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
{
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
}
return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);
return new DirectRecorder(_logger, _httpClient, _streamHelper);
}
private void OnSuccessfulRecording(TimerInfo timer, string path)
@@ -1685,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
var process = _processFactory.Create(new ProcessOptions
var process = new Process
{
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
CreateNoWindow = true,
EnableRaisingEvents = true,
ErrorDialog = false,
FileName = options.RecordingPostProcessor,
IsHidden = true,
UseShellExecute = false
});
StartInfo = new ProcessStartInfo
{
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
CreateNoWindow = true,
ErrorDialog = false,
FileName = options.RecordingPostProcessor,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
},
EnableRaisingEvents = true
};
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
@@ -1714,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void Process_Exited(object sender, EventArgs e)
{
using (var process = (IProcess)sender)
using (var process = (Process)sender)
{
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
process.Dispose();
}
}
@@ -1888,7 +1884,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
var settings = new XmlWriterSettings
{
@@ -1952,7 +1948,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
var settings = new XmlWriterSettings
{

View File

@@ -1,8 +1,10 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -12,9 +14,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -24,33 +24,27 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public class EncodedRecorder : IRecorder
{
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationPaths _appPaths;
private bool _hasExited;
private Stream _logFileStream;
private string _targetPath;
private IProcess _process;
private readonly IProcessFactory _processFactory;
private Process _process;
private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _config;
public EncodedRecorder(
ILogger logger,
IFileSystem fileSystem,
IMediaEncoder mediaEncoder,
IServerApplicationPaths appPaths,
IJsonSerializer json,
IProcessFactory processFactory,
IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_json = json;
_processFactory = processFactory;
_config = config;
}
@@ -82,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_targetPath = targetFile;
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
var process = _processFactory.Create(new ProcessOptions
var processStartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
@@ -93,35 +87,37 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
FileName = _mediaEncoder.EncoderPath,
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
IsHidden = true,
ErrorDialog = false,
EnableRaisingEvents = true
});
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
};
_process = process;
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
_logger.LogInformation(commandLineLogMessage);
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
_logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
_logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
_process = new Process
{
StartInfo = processStartInfo,
EnableRaisingEvents = true
};
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
process.Start();
_process.Start();
cancellationToken.Register(Stop);
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);
@@ -295,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Processes the exited.
/// </summary>
private void OnFfMpegProcessExited(IProcess process, string inputFile)
private void OnFfMpegProcessExited(Process process, string inputFile)
{
_hasExited = true;
_logFileStream?.Dispose();
_logFileStream = null;
var exitCode = process.ExitCode;
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
if (exitCode == 0)
using (process)
{
_taskCompletionSource.TrySetResult(true);
}
else
{
_taskCompletionSource.TrySetException(
new Exception(
string.Format(
CultureInfo.InvariantCulture,
"Recording for {0} failed. Exit code {1}",
_targetPath,
exitCode)));
_hasExited = true;
_logFileStream?.Dispose();
_logFileStream = null;
var exitCode = process.ExitCode;
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
if (exitCode == 0)
{
_taskCompletionSource.TrySetResult(true);
}
else
{
_taskCompletionSource.TrySetException(
new Exception(
string.Format(
CultureInfo.InvariantCulture,
"Recording for {0} failed. Exit code {1}",
_targetPath,
exitCode)));
}
}
}

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Threading.Tasks;
using MediaBrowser.Controller.Plugins;
@@ -5,11 +7,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
public class EntryPoint : IServerEntryPoint
{
/// <inheritdoc />
public Task RunAsync()
{
return EmbyTV.Current.Start();
}
/// <inheritdoc />
public void Dispose()
{
}

View File

@@ -1,4 +1,3 @@
#pragma warning disable SA1600
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Threading;
using System.Threading.Tasks;

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Globalization;
using MediaBrowser.Controller.LiveTv;
@@ -21,7 +23,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (info.SeasonNumber.HasValue && info.EpisodeNumber.HasValue)
{
name += string.Format(" S{0}E{1}", info.SeasonNumber.Value.ToString("00", CultureInfo.InvariantCulture), info.EpisodeNumber.Value.ToString("00", CultureInfo.InvariantCulture));
name += string.Format(
CultureInfo.InvariantCulture,
" S{0}E{1}",
info.SeasonNumber.Value.ToString("00", CultureInfo.InvariantCulture),
info.EpisodeNumber.Value.ToString("00", CultureInfo.InvariantCulture));
addHyphen = false;
}
else if (info.OriginalAirDate.HasValue)
@@ -32,7 +38,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
else
{
name += " " + info.OriginalAirDate.Value.ToLocalTime().ToString("yyyy-MM-dd");
name += " " + info.OriginalAirDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
}
}
else
@@ -67,14 +73,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
date = date.ToLocalTime();
return string.Format("{0}_{1}_{2}_{3}_{4}_{5}",
return string.Format(
CultureInfo.InvariantCulture,
"{0}_{1}_{2}_{3}_{4}_{5}",
date.Year.ToString("0000", CultureInfo.InvariantCulture),
date.Month.ToString("00", CultureInfo.InvariantCulture),
date.Day.ToString("00", CultureInfo.InvariantCulture),
date.Hour.ToString("00", CultureInfo.InvariantCulture),
date.Minute.ToString("00", CultureInfo.InvariantCulture),
date.Second.ToString("00", CultureInfo.InvariantCulture)
);
date.Second.ToString("00", CultureInfo.InvariantCulture));
}
}
}

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Serialization;
@@ -12,6 +14,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
}
/// <inheritdoc />
public override void Add(SeriesTimerInfo item)
{
if (string.IsNullOrEmpty(item.Id))

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Globalization;

View File

@@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -30,7 +32,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
public SchedulesDirect(ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IApplicationHost appHost)
public SchedulesDirect(
ILogger<SchedulesDirect> logger,
IJsonSerializer jsonSerializer,
IHttpClient httpClient,
IApplicationHost appHost)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
@@ -629,7 +635,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo)
{
// Schedules direct requires that the client support compression and will return a 400 response without it
options.DecompressionMethod = CompressionMethod.Deflate;
options.DecompressionMethod = CompressionMethods.Deflate;
try
{
@@ -659,7 +665,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo)
{
// Schedules direct requires that the client support compression and will return a 400 response without it
options.DecompressionMethod = CompressionMethod.Deflate;
options.DecompressionMethod = CompressionMethods.Deflate;
try
{

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