mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-04 01:11:55 +01:00
Merge branch 'master' into httpclient
This commit is contained in:
@@ -30,13 +30,10 @@ namespace Emby.Server.Implementations.Activity
|
||||
public class ActivityLogEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly IInstallationManager _installationManager;
|
||||
|
||||
//private readonly ILogger _logger;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly IActivityManager _activityManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ISubtitleManager _subManager;
|
||||
private readonly IUserManager _userManager;
|
||||
@@ -61,41 +58,37 @@ namespace Emby.Server.Implementations.Activity
|
||||
|
||||
public Task RunAsync()
|
||||
{
|
||||
_taskManager.TaskCompleted += _taskManager_TaskCompleted;
|
||||
_taskManager.TaskCompleted += OnTaskCompleted;
|
||||
|
||||
_installationManager.PluginInstalled += _installationManager_PluginInstalled;
|
||||
_installationManager.PluginUninstalled += _installationManager_PluginUninstalled;
|
||||
_installationManager.PluginUpdated += _installationManager_PluginUpdated;
|
||||
_installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed;
|
||||
_installationManager.PluginInstalled += OnPluginInstalled;
|
||||
_installationManager.PluginUninstalled += OnPluginUninstalled;
|
||||
_installationManager.PluginUpdated += OnPluginUpdated;
|
||||
_installationManager.PackageInstallationFailed += OnPackageInstallationFailed;
|
||||
|
||||
_sessionManager.SessionStarted += _sessionManager_SessionStarted;
|
||||
_sessionManager.AuthenticationFailed += _sessionManager_AuthenticationFailed;
|
||||
_sessionManager.AuthenticationSucceeded += _sessionManager_AuthenticationSucceeded;
|
||||
_sessionManager.SessionEnded += _sessionManager_SessionEnded;
|
||||
_sessionManager.SessionStarted += OnSessionStarted;
|
||||
_sessionManager.AuthenticationFailed += OnAuthenticationFailed;
|
||||
_sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
|
||||
_sessionManager.SessionEnded += OnSessionEnded;
|
||||
|
||||
_sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
|
||||
_sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped;
|
||||
_sessionManager.PlaybackStart += OnPlaybackStart;
|
||||
_sessionManager.PlaybackStopped += OnPlaybackStopped;
|
||||
|
||||
//_subManager.SubtitlesDownloaded += _subManager_SubtitlesDownloaded;
|
||||
_subManager.SubtitleDownloadFailure += _subManager_SubtitleDownloadFailure;
|
||||
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
|
||||
|
||||
_userManager.UserCreated += _userManager_UserCreated;
|
||||
_userManager.UserPasswordChanged += _userManager_UserPasswordChanged;
|
||||
_userManager.UserDeleted += _userManager_UserDeleted;
|
||||
_userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated;
|
||||
_userManager.UserLockedOut += _userManager_UserLockedOut;
|
||||
_userManager.UserCreated += OnUserCreated;
|
||||
_userManager.UserPasswordChanged += OnUserPasswordChanged;
|
||||
_userManager.UserDeleted += OnUserDeleted;
|
||||
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
||||
_userManager.UserLockedOut += OnUserLockedOut;
|
||||
|
||||
//_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
||||
//_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
|
||||
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
|
||||
|
||||
_deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded;
|
||||
|
||||
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
||||
_appHost.ApplicationUpdated += OnApplicationUpdated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
||||
private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -104,7 +97,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserLockedOut(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -114,7 +107,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||
private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -125,7 +118,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||
private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||
{
|
||||
var item = e.MediaInfo;
|
||||
|
||||
@@ -146,7 +139,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
return;
|
||||
}
|
||||
|
||||
var user = e.Users.First();
|
||||
var user = e.Users[0];
|
||||
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -156,7 +149,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||
private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||
{
|
||||
var item = e.MediaInfo;
|
||||
|
||||
@@ -232,7 +225,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
return null;
|
||||
}
|
||||
|
||||
void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
|
||||
private void OnSessionEnded(object sender, SessionEventArgs e)
|
||||
{
|
||||
string name;
|
||||
var session = e.SessionInfo;
|
||||
@@ -258,7 +251,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
||||
private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
||||
{
|
||||
var user = e.Argument.User;
|
||||
|
||||
@@ -271,7 +264,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
||||
private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -282,7 +275,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
private void OnApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -292,25 +285,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key),
|
||||
Type = "NamedConfigurationUpdated"
|
||||
});
|
||||
}
|
||||
|
||||
void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"),
|
||||
Type = "ServerConfigurationUpdated"
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -320,7 +295,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -329,7 +304,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -339,7 +314,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
|
||||
private void OnUserCreated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -349,18 +324,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)),
|
||||
Type = "SubtitlesDownloaded",
|
||||
ItemId = e.Item.Id.ToString("N"),
|
||||
ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider)
|
||||
});
|
||||
}
|
||||
|
||||
void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
|
||||
private void OnSessionStarted(object sender, SessionEventArgs e)
|
||||
{
|
||||
string name;
|
||||
var session = e.SessionInfo;
|
||||
@@ -386,7 +350,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
|
||||
private void OnPluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -397,7 +361,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -406,7 +370,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
{
|
||||
@@ -416,7 +380,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||
{
|
||||
var installationInfo = e.InstallationInfo;
|
||||
|
||||
@@ -429,7 +393,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
});
|
||||
}
|
||||
|
||||
void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||
{
|
||||
var result = e.Result;
|
||||
var task = e.Task;
|
||||
@@ -468,48 +432,36 @@ namespace Emby.Server.Implementations.Activity
|
||||
}
|
||||
|
||||
private void CreateLogEntry(ActivityLogEntry entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
_activityManager.Create(entry);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Logged at lower levels
|
||||
}
|
||||
}
|
||||
=> _activityManager.Create(entry);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_taskManager.TaskCompleted -= _taskManager_TaskCompleted;
|
||||
_taskManager.TaskCompleted -= OnTaskCompleted;
|
||||
|
||||
_installationManager.PluginInstalled -= _installationManager_PluginInstalled;
|
||||
_installationManager.PluginUninstalled -= _installationManager_PluginUninstalled;
|
||||
_installationManager.PluginUpdated -= _installationManager_PluginUpdated;
|
||||
_installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed;
|
||||
_installationManager.PluginInstalled -= OnPluginInstalled;
|
||||
_installationManager.PluginUninstalled -= OnPluginUninstalled;
|
||||
_installationManager.PluginUpdated -= OnPluginUpdated;
|
||||
_installationManager.PackageInstallationFailed -= OnPackageInstallationFailed;
|
||||
|
||||
_sessionManager.SessionStarted -= _sessionManager_SessionStarted;
|
||||
_sessionManager.AuthenticationFailed -= _sessionManager_AuthenticationFailed;
|
||||
_sessionManager.AuthenticationSucceeded -= _sessionManager_AuthenticationSucceeded;
|
||||
_sessionManager.SessionEnded -= _sessionManager_SessionEnded;
|
||||
_sessionManager.SessionStarted -= OnSessionStarted;
|
||||
_sessionManager.AuthenticationFailed -= OnAuthenticationFailed;
|
||||
_sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded;
|
||||
_sessionManager.SessionEnded -= OnSessionEnded;
|
||||
|
||||
_sessionManager.PlaybackStart -= _sessionManager_PlaybackStart;
|
||||
_sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped;
|
||||
_sessionManager.PlaybackStart -= OnPlaybackStart;
|
||||
_sessionManager.PlaybackStopped -= OnPlaybackStopped;
|
||||
|
||||
_subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure;
|
||||
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
|
||||
|
||||
_userManager.UserCreated -= _userManager_UserCreated;
|
||||
_userManager.UserPasswordChanged -= _userManager_UserPasswordChanged;
|
||||
_userManager.UserDeleted -= _userManager_UserDeleted;
|
||||
_userManager.UserPolicyUpdated -= _userManager_UserPolicyUpdated;
|
||||
_userManager.UserLockedOut -= _userManager_UserLockedOut;
|
||||
_userManager.UserCreated -= OnUserCreated;
|
||||
_userManager.UserPasswordChanged -= OnUserPasswordChanged;
|
||||
_userManager.UserDeleted -= OnUserDeleted;
|
||||
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
||||
_userManager.UserLockedOut -= OnUserLockedOut;
|
||||
|
||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
||||
_config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated;
|
||||
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
|
||||
|
||||
_deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
|
||||
|
||||
_appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
|
||||
_appHost.ApplicationUpdated -= OnApplicationUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -531,6 +483,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
values.Add(CreateValueString(years, "year"));
|
||||
days = days % DaysInYear;
|
||||
}
|
||||
|
||||
// Number of months
|
||||
if (days >= DaysInMonth)
|
||||
{
|
||||
@@ -538,25 +491,39 @@ namespace Emby.Server.Implementations.Activity
|
||||
values.Add(CreateValueString(months, "month"));
|
||||
days = days % DaysInMonth;
|
||||
}
|
||||
|
||||
// Number of days
|
||||
if (days >= 1)
|
||||
{
|
||||
values.Add(CreateValueString(days, "day"));
|
||||
}
|
||||
|
||||
// Number of hours
|
||||
if (span.Hours >= 1)
|
||||
{
|
||||
values.Add(CreateValueString(span.Hours, "hour"));
|
||||
}
|
||||
// Number of minutes
|
||||
if (span.Minutes >= 1)
|
||||
{
|
||||
values.Add(CreateValueString(span.Minutes, "minute"));
|
||||
}
|
||||
|
||||
// Number of seconds (include when 0 if no other components included)
|
||||
if (span.Seconds >= 1 || values.Count == 0)
|
||||
{
|
||||
values.Add(CreateValueString(span.Seconds, "second"));
|
||||
}
|
||||
|
||||
// Combine values into string
|
||||
var builder = new StringBuilder();
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
{
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.Append(i == values.Count - 1 ? " and " : ", ");
|
||||
}
|
||||
|
||||
builder.Append(values[i]);
|
||||
}
|
||||
// Return result
|
||||
|
||||
@@ -17,12 +17,14 @@ namespace Emby.Server.Implementations.AppBase
|
||||
string programDataPath,
|
||||
string logDirectoryPath,
|
||||
string configurationDirectoryPath,
|
||||
string cacheDirectoryPath)
|
||||
string cacheDirectoryPath,
|
||||
string webDirectoryPath)
|
||||
{
|
||||
ProgramDataPath = programDataPath;
|
||||
LogDirectoryPath = logDirectoryPath;
|
||||
ConfigurationDirectoryPath = configurationDirectoryPath;
|
||||
CachePath = cacheDirectoryPath;
|
||||
WebPath = webDirectoryPath;
|
||||
|
||||
DataPath = Path.Combine(ProgramDataPath, "data");
|
||||
}
|
||||
@@ -33,6 +35,12 @@ namespace Emby.Server.Implementations.AppBase
|
||||
/// <value>The program data path.</value>
|
||||
public string ProgramDataPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the web UI resources folder
|
||||
/// </summary>
|
||||
/// <value>The web UI resources path.</value>
|
||||
public string WebPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system folder
|
||||
/// </summary>
|
||||
|
||||
@@ -37,7 +37,6 @@ using Emby.Server.Implementations.LiveTv;
|
||||
using Emby.Server.Implementations.Localization;
|
||||
using Emby.Server.Implementations.Net;
|
||||
using Emby.Server.Implementations.Playlists;
|
||||
using Emby.Server.Implementations.Reflection;
|
||||
using Emby.Server.Implementations.ScheduledTasks;
|
||||
using Emby.Server.Implementations.Security;
|
||||
using Emby.Server.Implementations.Serialization;
|
||||
@@ -45,7 +44,6 @@ using Emby.Server.Implementations.Session;
|
||||
using Emby.Server.Implementations.SocketSharp;
|
||||
using Emby.Server.Implementations.TV;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using Emby.Server.Implementations.Xml;
|
||||
using MediaBrowser.Api;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
@@ -93,13 +91,11 @@ using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using MediaBrowser.Providers.Chapters;
|
||||
using MediaBrowser.Providers.Manager;
|
||||
using MediaBrowser.Providers.Subtitles;
|
||||
@@ -115,6 +111,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using ServiceStack;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
{
|
||||
@@ -143,12 +140,8 @@ namespace Emby.Server.Implementations
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
|
||||
if (OperatingSystem.Id == OperatingSystemId.Windows
|
||||
|| OperatingSystem.Id == OperatingSystemId.Darwin)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -168,7 +161,7 @@ namespace Emby.Server.Implementations
|
||||
public event EventHandler<GenericEventArgs<PackageVersionInfo>> ApplicationUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has changes that require the entire application to restart.
|
||||
/// Gets a value indicating whether this instance has changes that require the entire application to restart.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance has pending application restart; otherwise, <c>false</c>.</value>
|
||||
public bool HasPendingRestart { get; private set; }
|
||||
@@ -182,7 +175,7 @@ namespace Emby.Server.Implementations
|
||||
protected ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the plugins.
|
||||
/// Gets the plugins.
|
||||
/// </summary>
|
||||
/// <value>The plugins.</value>
|
||||
public IPlugin[] Plugins { get; protected set; }
|
||||
@@ -194,13 +187,13 @@ namespace Emby.Server.Implementations
|
||||
public ILoggerFactory LoggerFactory { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the application paths.
|
||||
/// Gets or sets the application paths.
|
||||
/// </summary>
|
||||
/// <value>The application paths.</value>
|
||||
protected ServerApplicationPaths ApplicationPaths { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all concrete types.
|
||||
/// Gets or sets all concrete types.
|
||||
/// </summary>
|
||||
/// <value>All concrete types.</value>
|
||||
public Type[] AllConcreteTypes { get; protected set; }
|
||||
@@ -208,7 +201,7 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// The disposable parts
|
||||
/// </summary>
|
||||
protected readonly List<IDisposable> DisposableParts = new List<IDisposable>();
|
||||
private readonly List<IDisposable> _disposableParts = new List<IDisposable>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configuration manager.
|
||||
@@ -218,16 +211,15 @@ namespace Emby.Server.Implementations
|
||||
|
||||
public IFileSystem FileSystemManager { get; set; }
|
||||
|
||||
protected IEnvironmentInfo EnvironmentInfo { get; set; }
|
||||
|
||||
public PackageVersionClass SystemUpdateLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
#if BETA
|
||||
return PackageVersionClass.Beta;
|
||||
#endif
|
||||
#else
|
||||
return PackageVersionClass.Release;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,15 +231,6 @@ namespace Emby.Server.Implementations
|
||||
/// <value>The server configuration manager.</value>
|
||||
public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configuration manager.
|
||||
/// </summary>
|
||||
/// <returns>IConfigurationManager.</returns>
|
||||
protected IConfigurationManager GetConfigurationManager()
|
||||
{
|
||||
return new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager);
|
||||
}
|
||||
|
||||
protected virtual IResourceFileManager CreateResourceFileManager()
|
||||
{
|
||||
return new ResourceFileManager(HttpResultFactory, LoggerFactory, FileSystemManager);
|
||||
@@ -258,27 +241,33 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
/// <value>The user manager.</value>
|
||||
public IUserManager UserManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the library manager.
|
||||
/// </summary>
|
||||
/// <value>The library manager.</value>
|
||||
internal ILibraryManager LibraryManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the directory watchers.
|
||||
/// </summary>
|
||||
/// <value>The directory watchers.</value>
|
||||
private ILibraryMonitor LibraryMonitor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the provider manager.
|
||||
/// </summary>
|
||||
/// <value>The provider manager.</value>
|
||||
private IProviderManager ProviderManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP server.
|
||||
/// </summary>
|
||||
/// <value>The HTTP server.</value>
|
||||
private IHttpServer HttpServer { get; set; }
|
||||
|
||||
private IDtoService DtoService { get; set; }
|
||||
|
||||
public IImageProcessor ImageProcessor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -286,6 +275,7 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
/// <value>The media encoder.</value>
|
||||
private IMediaEncoder MediaEncoder { get; set; }
|
||||
|
||||
private ISubtitleEncoder SubtitleEncoder { get; set; }
|
||||
|
||||
private ISessionManager SessionManager { get; set; }
|
||||
@@ -295,6 +285,7 @@ namespace Emby.Server.Implementations
|
||||
public LocalizationManager LocalizationManager { get; set; }
|
||||
|
||||
private IEncodingManager EncodingManager { get; set; }
|
||||
|
||||
private IChannelManager ChannelManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -302,20 +293,29 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
/// <value>The user data repository.</value>
|
||||
private IUserDataManager UserDataManager { get; set; }
|
||||
|
||||
private IUserRepository UserRepository { get; set; }
|
||||
|
||||
internal SqliteItemRepository ItemRepository { get; set; }
|
||||
|
||||
private INotificationManager NotificationManager { get; set; }
|
||||
|
||||
private ISubtitleManager SubtitleManager { get; set; }
|
||||
|
||||
private IChapterManager ChapterManager { get; set; }
|
||||
|
||||
private IDeviceManager DeviceManager { get; set; }
|
||||
|
||||
internal IUserViewManager UserViewManager { get; set; }
|
||||
|
||||
private IAuthenticationRepository AuthenticationRepository { get; set; }
|
||||
|
||||
private ITVSeriesManager TVSeriesManager { get; set; }
|
||||
|
||||
private ICollectionManager CollectionManager { get; set; }
|
||||
|
||||
private IMediaSourceManager MediaSourceManager { get; set; }
|
||||
|
||||
private IPlaylistManager PlaylistManager { get; set; }
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
@@ -331,52 +331,55 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
/// <value>The zip client.</value>
|
||||
protected IZipClient ZipClient { get; private set; }
|
||||
|
||||
protected IHttpResultFactory HttpResultFactory { get; private set; }
|
||||
|
||||
protected IAuthService AuthService { get; private set; }
|
||||
|
||||
public IStartupOptions StartupOptions { get; private set; }
|
||||
public IStartupOptions StartupOptions { get; }
|
||||
|
||||
internal IImageEncoder ImageEncoder { get; private set; }
|
||||
|
||||
protected IProcessFactory ProcessFactory { get; private set; }
|
||||
protected ICryptoProvider CryptographyProvider = new CryptographyProvider();
|
||||
|
||||
protected readonly IXmlSerializer XmlSerializer;
|
||||
|
||||
protected ISocketFactory SocketFactory { get; private set; }
|
||||
|
||||
protected ITaskManager TaskManager { get; private set; }
|
||||
|
||||
public IHttpClient HttpClient { get; private set; }
|
||||
|
||||
protected INetworkManager NetworkManager { get; set; }
|
||||
|
||||
public IJsonSerializer JsonSerializer { get; private set; }
|
||||
|
||||
protected IIsoManager IsoManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
||||
/// </summary>
|
||||
public ApplicationHost(ServerApplicationPaths applicationPaths,
|
||||
public ApplicationHost(
|
||||
ServerApplicationPaths applicationPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IStartupOptions options,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environmentInfo,
|
||||
IImageEncoder imageEncoder,
|
||||
INetworkManager networkManager,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
|
||||
// hack alert, until common can target .net core
|
||||
BaseExtensions.CryptographyProvider = CryptographyProvider;
|
||||
|
||||
XmlSerializer = new MyXmlSerializer(fileSystem, loggerFactory);
|
||||
|
||||
NetworkManager = networkManager;
|
||||
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
|
||||
EnvironmentInfo = environmentInfo;
|
||||
|
||||
ApplicationPaths = applicationPaths;
|
||||
LoggerFactory = loggerFactory;
|
||||
FileSystemManager = fileSystem;
|
||||
|
||||
ConfigurationManager = GetConfigurationManager();
|
||||
ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager);
|
||||
|
||||
Logger = LoggerFactory.CreateLogger("App");
|
||||
|
||||
@@ -415,7 +418,7 @@ namespace Emby.Server.Implementations
|
||||
_validAddressResults.Clear();
|
||||
}
|
||||
|
||||
public string ApplicationVersion => typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
||||
public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current application user agent
|
||||
@@ -423,14 +426,23 @@ namespace Emby.Server.Implementations
|
||||
/// <value>The application user agent.</value>
|
||||
public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the email address for use within a comment section of a user agent field.
|
||||
/// Presently used to provide contact information to MusicBrainz service.
|
||||
/// </summary>
|
||||
public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org";
|
||||
|
||||
private string _productName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current application name
|
||||
/// </summary>
|
||||
/// <value>The application name.</value>
|
||||
public string ApplicationProductName => _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName);
|
||||
public string ApplicationProductName
|
||||
=> _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName);
|
||||
|
||||
private DeviceId _deviceId;
|
||||
|
||||
public string SystemId
|
||||
{
|
||||
get
|
||||
@@ -461,15 +473,15 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Creates an instance of type and resolves all constructor dependencies
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// /// <typeparam name="T">The type</typeparam>
|
||||
/// <returns>T</returns>
|
||||
public T CreateInstance<T>()
|
||||
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the instance safe.
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">The type information.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
protected object CreateInstanceSafe(Type type)
|
||||
{
|
||||
@@ -488,14 +500,14 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Resolves this instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T">The type</typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
public T Resolve<T>() => _serviceProvider.GetService<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the export types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T">The type</typeparam>
|
||||
/// <returns>IEnumerable{Type}.</returns>
|
||||
public IEnumerable<Type> GetExportTypes<T>()
|
||||
{
|
||||
@@ -507,22 +519,22 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Gets the exports.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T">The type</typeparam>
|
||||
/// <param name="manageLifetime">if set to <c>true</c> [manage lifetime].</param>
|
||||
/// <returns>IEnumerable{``0}.</returns>
|
||||
public IEnumerable<T> GetExports<T>(bool manageLifetime = true)
|
||||
{
|
||||
var parts = GetExportTypes<T>()
|
||||
.Select(x => CreateInstanceSafe(x))
|
||||
.Select(CreateInstanceSafe)
|
||||
.Where(i => i != null)
|
||||
.Cast<T>()
|
||||
.ToList(); // Convert to list so this isn't executed for each iteration
|
||||
|
||||
if (manageLifetime)
|
||||
{
|
||||
lock (DisposableParts)
|
||||
lock (_disposableParts)
|
||||
{
|
||||
DisposableParts.AddRange(parts.OfType<IDisposable>());
|
||||
_disposableParts.AddRange(parts.OfType<IDisposable>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +544,7 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Runs the startup tasks.
|
||||
/// </summary>
|
||||
public async Task RunStartupTasks()
|
||||
public async Task RunStartupTasksAsync()
|
||||
{
|
||||
Logger.LogInformation("Running startup tasks");
|
||||
|
||||
@@ -542,29 +554,20 @@ namespace Emby.Server.Implementations
|
||||
|
||||
MediaEncoder.SetFFmpegPath();
|
||||
|
||||
//if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath))
|
||||
//{
|
||||
// if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted)
|
||||
// {
|
||||
// ServerConfigurationManager.Configuration.IsStartupWizardCompleted = false;
|
||||
// ServerConfigurationManager.SaveConfiguration();
|
||||
// }
|
||||
//}
|
||||
|
||||
Logger.LogInformation("ServerId: {0}", SystemId);
|
||||
|
||||
var entryPoints = GetExports<IServerEntryPoint>();
|
||||
var entryPoints = GetExports<IServerEntryPoint>().ToList();
|
||||
|
||||
var stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
await Task.WhenAll(StartEntryPoints(entryPoints, true));
|
||||
await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false);
|
||||
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
|
||||
|
||||
Logger.LogInformation("Core startup complete");
|
||||
HttpServer.GlobalResponse = null;
|
||||
|
||||
stopWatch.Restart();
|
||||
await Task.WhenAll(StartEntryPoints(entryPoints, false));
|
||||
await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
|
||||
Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
|
||||
stopWatch.Stop();
|
||||
}
|
||||
@@ -584,7 +587,7 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Init(IServiceCollection serviceCollection)
|
||||
public async Task InitAsync(IServiceCollection serviceCollection)
|
||||
{
|
||||
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
||||
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
||||
@@ -614,14 +617,14 @@ namespace Emby.Server.Implementations
|
||||
|
||||
SetHttpLimit();
|
||||
|
||||
await RegisterResources(serviceCollection);
|
||||
await RegisterResources(serviceCollection).ConfigureAwait(false);
|
||||
|
||||
FindParts();
|
||||
|
||||
string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
|
||||
if (string.IsNullOrEmpty(contentRoot))
|
||||
{
|
||||
contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src");
|
||||
contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
|
||||
}
|
||||
|
||||
var host = new WebHostBuilder()
|
||||
@@ -629,7 +632,7 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
options.ListenAnyIP(HttpPort);
|
||||
|
||||
if (EnableHttps)
|
||||
if (EnableHttps && Certificate != null)
|
||||
{
|
||||
options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); });
|
||||
}
|
||||
@@ -651,7 +654,7 @@ namespace Emby.Server.Implementations
|
||||
})
|
||||
.Build();
|
||||
|
||||
await host.StartAsync();
|
||||
await host.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
|
||||
@@ -700,14 +703,14 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||
|
||||
serviceCollection.AddSingleton<IConfiguration>(_configuration);
|
||||
|
||||
serviceCollection.AddSingleton(JsonSerializer);
|
||||
|
||||
serviceCollection.AddSingleton(LoggerFactory);
|
||||
serviceCollection.AddLogging();
|
||||
serviceCollection.AddSingleton(Logger);
|
||||
|
||||
serviceCollection.AddSingleton(EnvironmentInfo);
|
||||
|
||||
serviceCollection.AddSingleton(FileSystemManager);
|
||||
serviceCollection.AddSingleton<TvDbClientManager>();
|
||||
|
||||
@@ -730,13 +733,12 @@ namespace Emby.Server.Implementations
|
||||
ApplicationHost.StreamHelper = new StreamHelper();
|
||||
serviceCollection.AddSingleton(StreamHelper);
|
||||
|
||||
serviceCollection.AddSingleton(CryptographyProvider);
|
||||
serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
|
||||
|
||||
SocketFactory = new SocketFactory();
|
||||
serviceCollection.AddSingleton(SocketFactory);
|
||||
|
||||
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, ZipClient, PackageRuntime);
|
||||
serviceCollection.AddSingleton(InstallationManager);
|
||||
serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
|
||||
|
||||
ZipClient = new ZipClient();
|
||||
serviceCollection.AddSingleton(ZipClient);
|
||||
@@ -749,17 +751,12 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton(ServerConfigurationManager);
|
||||
|
||||
var assemblyInfo = new AssemblyInfo();
|
||||
serviceCollection.AddSingleton<IAssemblyInfo>(assemblyInfo);
|
||||
|
||||
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory);
|
||||
await LocalizationManager.LoadAll();
|
||||
LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory);
|
||||
await LocalizationManager.LoadAll().ConfigureAwait(false);
|
||||
serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
|
||||
|
||||
serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
|
||||
|
||||
serviceCollection.AddSingleton<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
|
||||
|
||||
UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
|
||||
serviceCollection.AddSingleton(UserDataManager);
|
||||
|
||||
@@ -770,7 +767,7 @@ namespace Emby.Server.Implementations
|
||||
var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager);
|
||||
serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo);
|
||||
|
||||
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo);
|
||||
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, LocalizationManager);
|
||||
serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
|
||||
|
||||
AuthenticationRepository = GetAuthenticationRepository();
|
||||
@@ -786,7 +783,7 @@ namespace Emby.Server.Implementations
|
||||
var musicManager = new MusicManager(LibraryManager);
|
||||
serviceCollection.AddSingleton<IMusicManager>(new MusicManager(LibraryManager));
|
||||
|
||||
LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
|
||||
LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager);
|
||||
serviceCollection.AddSingleton(LibraryMonitor);
|
||||
|
||||
serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
|
||||
@@ -794,16 +791,19 @@ namespace Emby.Server.Implementations
|
||||
CertificateInfo = GetCertificateInfo(true);
|
||||
Certificate = GetCertificate(CertificateInfo);
|
||||
|
||||
HttpServer = new HttpListenerHost(this,
|
||||
HttpServer = new HttpListenerHost(
|
||||
this,
|
||||
LoggerFactory,
|
||||
ServerConfigurationManager,
|
||||
_configuration,
|
||||
NetworkManager,
|
||||
JsonSerializer,
|
||||
XmlSerializer,
|
||||
CreateHttpListener());
|
||||
CreateHttpListener())
|
||||
{
|
||||
GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading")
|
||||
};
|
||||
|
||||
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
|
||||
serviceCollection.AddSingleton(HttpServer);
|
||||
|
||||
ImageProcessor = GetImageProcessor();
|
||||
@@ -862,13 +862,13 @@ namespace Emby.Server.Implementations
|
||||
LoggerFactory,
|
||||
JsonSerializer,
|
||||
StartupOptions.FFmpegPath,
|
||||
StartupOptions.FFprobePath,
|
||||
ServerConfigurationManager,
|
||||
FileSystemManager,
|
||||
() => SubtitleEncoder,
|
||||
() => MediaSourceManager,
|
||||
ProcessFactory,
|
||||
5000);
|
||||
5000,
|
||||
LocalizationManager);
|
||||
serviceCollection.AddSingleton(MediaEncoder);
|
||||
|
||||
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
|
||||
@@ -905,9 +905,7 @@ namespace Emby.Server.Implementations
|
||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
}
|
||||
|
||||
public virtual string PackageRuntime => "netcore";
|
||||
|
||||
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo)
|
||||
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
|
||||
{
|
||||
// Distinct these to prevent users from reporting problems that aren't actually problems
|
||||
var commandLineArgs = Environment
|
||||
@@ -915,12 +913,14 @@ namespace Emby.Server.Implementations
|
||||
.Distinct();
|
||||
|
||||
logger.LogInformation("Arguments: {Args}", commandLineArgs);
|
||||
logger.LogInformation("Operating system: {OS} {OSVersion}", environmentInfo.OperatingSystemName, environmentInfo.OperatingSystemVersion);
|
||||
logger.LogInformation("Architecture: {Architecture}", environmentInfo.SystemArchitecture);
|
||||
// FIXME: @bond this logs the kernel version, not the OS version
|
||||
logger.LogInformation("Operating system: {OS} {OSVersion}", OperatingSystem.Name, Environment.OSVersion.Version);
|
||||
logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture);
|
||||
logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess);
|
||||
logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive);
|
||||
logger.LogInformation("Processor count: {ProcessorCount}", Environment.ProcessorCount);
|
||||
logger.LogInformation("Program data path: {ProgramDataPath}", appPaths.ProgramDataPath);
|
||||
logger.LogInformation("Web resources path: {WebPath}", appPaths.WebPath);
|
||||
logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath);
|
||||
}
|
||||
|
||||
@@ -957,7 +957,7 @@ namespace Emby.Server.Implementations
|
||||
var password = string.IsNullOrWhiteSpace(info.Password) ? null : info.Password;
|
||||
|
||||
var localCert = new X509Certificate2(certificateLocation, password);
|
||||
//localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA;
|
||||
// localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA;
|
||||
if (!localCert.HasPrivateKey)
|
||||
{
|
||||
Logger.LogError("No private key included in SSL cert {CertificateLocation}.", certificateLocation);
|
||||
@@ -1044,6 +1044,8 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
protected void FindParts()
|
||||
{
|
||||
InstallationManager = _serviceProvider.GetService<IInstallationManager>();
|
||||
|
||||
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
|
||||
{
|
||||
ServerConfigurationManager.Configuration.IsPortAuthorized = true;
|
||||
@@ -1058,13 +1060,15 @@ namespace Emby.Server.Implementations
|
||||
|
||||
HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>(), GetUrlPrefixes());
|
||||
|
||||
LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(),
|
||||
LibraryManager.AddParts(
|
||||
GetExports<IResolverIgnoreRule>(),
|
||||
GetExports<IItemResolver>(),
|
||||
GetExports<IIntroProvider>(),
|
||||
GetExports<IBaseItemComparer>(),
|
||||
GetExports<ILibraryPostScanTask>());
|
||||
|
||||
ProviderManager.AddParts(GetExports<IImageProvider>(),
|
||||
ProviderManager.AddParts(
|
||||
GetExports<IImageProvider>(),
|
||||
GetExports<IMetadataService>(),
|
||||
GetExports<IMetadataProvider>(),
|
||||
GetExports<IMetadataSaver>(),
|
||||
@@ -1145,6 +1149,7 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
|
||||
private CertificateInfo CertificateInfo { get; set; }
|
||||
|
||||
protected X509Certificate2 Certificate { get; private set; }
|
||||
|
||||
private IEnumerable<string> GetUrlPrefixes()
|
||||
@@ -1155,7 +1160,7 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
var prefixes = new List<string>
|
||||
{
|
||||
"http://"+i+":" + HttpPort + "/"
|
||||
"http://" + i + ":" + HttpPort + "/"
|
||||
};
|
||||
|
||||
if (CertificateInfo != null)
|
||||
@@ -1184,30 +1189,12 @@ namespace Emby.Server.Implementations
|
||||
// Generate self-signed cert
|
||||
var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns);
|
||||
var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N") + ".pfx");
|
||||
var password = "embycert";
|
||||
|
||||
//if (generateCertificate)
|
||||
//{
|
||||
// if (!File.Exists(certPath))
|
||||
// {
|
||||
// FileSystemManager.CreateDirectory(FileSystemManager.GetDirectoryName(certPath));
|
||||
|
||||
// try
|
||||
// {
|
||||
// CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, password, Logger);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Logger.LogError(ex, "Error creating ssl cert");
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
const string Password = "embycert";
|
||||
|
||||
return new CertificateInfo
|
||||
{
|
||||
Path = certPath,
|
||||
Password = password
|
||||
Password = Password
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1242,9 +1229,9 @@ namespace Emby.Server.Implementations
|
||||
requiresRestart = true;
|
||||
}
|
||||
|
||||
var currentCertPath = CertificateInfo == null ? null : CertificateInfo.Path;
|
||||
var currentCertPath = CertificateInfo?.Path;
|
||||
var newCertInfo = GetCertificateInfo(false);
|
||||
var newCertPath = newCertInfo == null ? null : newCertInfo.Path;
|
||||
var newCertPath = newCertInfo?.Path;
|
||||
|
||||
if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -1377,6 +1364,7 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Gets the system status.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <returns>SystemInfo.</returns>
|
||||
public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -1393,6 +1381,7 @@ namespace Emby.Server.Implementations
|
||||
CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
|
||||
Id = SystemId,
|
||||
ProgramDataPath = ApplicationPaths.ProgramDataPath,
|
||||
WebPath = ApplicationPaths.WebPath,
|
||||
LogPath = ApplicationPaths.LogDirectoryPath,
|
||||
ItemsByNamePath = ApplicationPaths.InternalMetadataPath,
|
||||
InternalMetadataPath = ApplicationPaths.InternalMetadataPath,
|
||||
@@ -1400,8 +1389,8 @@ namespace Emby.Server.Implementations
|
||||
HttpServerPortNumber = HttpPort,
|
||||
SupportsHttps = SupportsHttps,
|
||||
HttpsPortNumber = HttpsPort,
|
||||
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
|
||||
OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName,
|
||||
OperatingSystem = OperatingSystem.Id.ToString(),
|
||||
OperatingSystemDisplayName = OperatingSystem.Name,
|
||||
CanSelfRestart = CanSelfRestart,
|
||||
CanLaunchWebBrowser = CanLaunchWebBrowser,
|
||||
WanAddress = wanAddress,
|
||||
@@ -1411,7 +1400,7 @@ namespace Emby.Server.Implementations
|
||||
LocalAddress = localAddress,
|
||||
SupportsLibraryMonitor = true,
|
||||
EncoderLocation = MediaEncoder.EncoderLocation,
|
||||
SystemArchitecture = EnvironmentInfo.SystemArchitecture,
|
||||
SystemArchitecture = RuntimeInformation.OSArchitecture,
|
||||
SystemUpdateLevel = SystemUpdateLevel,
|
||||
PackageName = StartupOptions.PackageName
|
||||
};
|
||||
@@ -1435,7 +1424,7 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
Version = ApplicationVersion,
|
||||
Id = SystemId,
|
||||
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
|
||||
OperatingSystem = OperatingSystem.Id.ToString(),
|
||||
WanAddress = wanAddress,
|
||||
ServerName = FriendlyName,
|
||||
LocalAddress = localAddress
|
||||
@@ -1470,18 +1459,18 @@ namespace Emby.Server.Implementations
|
||||
|
||||
public async Task<string> GetWanApiUrl(CancellationToken cancellationToken)
|
||||
{
|
||||
const string url = "http://ipv4.icanhazip.com";
|
||||
const string Url = "http://ipv4.icanhazip.com";
|
||||
try
|
||||
{
|
||||
using (var response = await HttpClient.Get(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
Url = Url,
|
||||
LogErrorResponseBody = false,
|
||||
LogErrors = false,
|
||||
LogRequest = false,
|
||||
BufferContent = false,
|
||||
CancellationToken = cancellationToken
|
||||
}))
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
string res = await response.ReadToEndAsync().ConfigureAwait(false);
|
||||
return GetLocalApiUrl(res.Trim());
|
||||
@@ -1570,10 +1559,12 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private async Task<bool> IsIpAddressValidAsync(IpAddressInfo address, CancellationToken cancellationToken)
|
||||
{
|
||||
if (address.Equals(IpAddressInfo.Loopback) ||
|
||||
@@ -1590,25 +1581,26 @@ namespace Emby.Server.Implementations
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
var logPing = false;
|
||||
|
||||
#if DEBUG
|
||||
logPing = true;
|
||||
const bool LogPing = true;
|
||||
#else
|
||||
const bool LogPing = false;
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
using (var response = await HttpClient.SendAsync(new HttpRequestOptions
|
||||
{
|
||||
Url = apiUrl,
|
||||
LogErrorResponseBody = false,
|
||||
LogErrors = logPing,
|
||||
LogRequest = logPing,
|
||||
BufferContent = false,
|
||||
using (var response = await HttpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
Url = apiUrl,
|
||||
LogErrorResponseBody = false,
|
||||
LogErrors = logPing,
|
||||
LogRequest = logPing,
|
||||
BufferContent = false,
|
||||
|
||||
CancellationToken = cancellationToken
|
||||
CancellationToken = cancellationToken
|
||||
|
||||
}, HttpMethod.Post).ConfigureAwait(false))
|
||||
}, HttpMethod.Post).ConfigureAwait(false))
|
||||
{
|
||||
using (var reader = new StreamReader(response.Content))
|
||||
{
|
||||
@@ -1673,6 +1665,7 @@ namespace Emby.Server.Implementations
|
||||
public event EventHandler HasUpdateAvailableChanged;
|
||||
|
||||
private bool _hasUpdateAvailable;
|
||||
|
||||
public bool HasUpdateAvailable
|
||||
{
|
||||
get => _hasUpdateAvailable;
|
||||
@@ -1733,7 +1726,7 @@ namespace Emby.Server.Implementations
|
||||
var process = ProcessFactory.Create(new ProcessOptions
|
||||
{
|
||||
FileName = url,
|
||||
//EnableRaisingEvents = true,
|
||||
EnableRaisingEvents = true,
|
||||
UseShellExecute = true,
|
||||
ErrorDialog = false
|
||||
});
|
||||
@@ -1768,26 +1761,25 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
Logger.LogInformation("Application has been updated to version {0}", package.versionStr);
|
||||
|
||||
ApplicationUpdated?.Invoke(this, new GenericEventArgs<PackageVersionInfo>
|
||||
{
|
||||
Argument = package
|
||||
});
|
||||
ApplicationUpdated?.Invoke(
|
||||
this,
|
||||
new GenericEventArgs<PackageVersionInfo>()
|
||||
{
|
||||
Argument = package
|
||||
});
|
||||
|
||||
NotifyPendingRestart();
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
Dispose(true);
|
||||
}
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1796,14 +1788,19 @@ namespace Emby.Server.Implementations
|
||||
/// <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 (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispose)
|
||||
{
|
||||
var type = GetType();
|
||||
|
||||
Logger.LogInformation("Disposing {Type}", type.Name);
|
||||
|
||||
var parts = DisposableParts.Distinct().Where(i => i.GetType() != type).ToList();
|
||||
DisposableParts.Clear();
|
||||
var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList();
|
||||
_disposableParts.Clear();
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
@@ -1819,6 +1816,8 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
|
||||
{
|
||||
{"HttpListenerHost:DefaultRedirectPath", "web/index.html"}
|
||||
{"HttpListenerHost:DefaultRedirectPath", "web/index.html"},
|
||||
{"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
|
||||
namespace Emby.Server.Implementations.Cryptography
|
||||
@@ -136,7 +135,7 @@ namespace Emby.Server.Implementations.Cryptography
|
||||
{
|
||||
return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
|
||||
}
|
||||
|
||||
|
||||
public byte[] ComputeHash(PasswordHash hash)
|
||||
{
|
||||
int iterations = _defaultIterations;
|
||||
|
||||
@@ -90,9 +90,10 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(displayPreferences.Id))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences.Id));
|
||||
throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -22,9 +22,9 @@ using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
@@ -56,6 +56,8 @@ namespace Emby.Server.Implementations.Data
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private IServerApplicationHost _appHost;
|
||||
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
public IImageProcessor ImageProcessor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -66,7 +68,7 @@ namespace Emby.Server.Implementations.Data
|
||||
IServerApplicationHost appHost,
|
||||
IJsonSerializer jsonSerializer,
|
||||
ILoggerFactory loggerFactory,
|
||||
IAssemblyInfo assemblyInfo)
|
||||
ILocalizationManager localization)
|
||||
: base(loggerFactory.CreateLogger(nameof(SqliteItemRepository)))
|
||||
{
|
||||
if (config == null)
|
||||
@@ -82,7 +84,8 @@ namespace Emby.Server.Implementations.Data
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_typeMapper = new TypeMapper(assemblyInfo);
|
||||
_typeMapper = new TypeMapper();
|
||||
_localization = localization;
|
||||
|
||||
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
|
||||
}
|
||||
@@ -6189,6 +6192,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
item.ColorTransfer = reader[34].ToString();
|
||||
}
|
||||
|
||||
if (item.Type == MediaStreamType.Subtitle){
|
||||
item.localizedUndefined = _localization.GetLocalizedString("Undefined");
|
||||
item.localizedDefault = _localization.GetLocalizedString("Default");
|
||||
item.localizedForced = _localization.GetLocalizedString("Forced");
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
@@ -10,16 +9,13 @@ namespace Emby.Server.Implementations.Data
|
||||
/// </summary>
|
||||
public class TypeMapper
|
||||
{
|
||||
private readonly IAssemblyInfo _assemblyInfo;
|
||||
|
||||
/// <summary>
|
||||
/// This holds all the types in the running assemblies so that we can de-serialize properly when we don't have strong types
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Type> _typeMap = new ConcurrentDictionary<string, Type>();
|
||||
|
||||
public TypeMapper(IAssemblyInfo assemblyInfo)
|
||||
public TypeMapper()
|
||||
{
|
||||
_assemblyInfo = assemblyInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,8 +41,7 @@ namespace Emby.Server.Implementations.Data
|
||||
/// <returns>Type.</returns>
|
||||
private Type LookupType(string typeName)
|
||||
{
|
||||
return _assemblyInfo
|
||||
.GetCurrentAssemblies()
|
||||
return AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Select(a => a.GetType(typeName))
|
||||
.FirstOrDefault(t => t != null);
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
{
|
||||
public class CommonProcess : IProcess
|
||||
{
|
||||
public event EventHandler Exited;
|
||||
|
||||
private readonly ProcessOptions _options;
|
||||
private readonly Process _process;
|
||||
|
||||
private bool _disposed = false;
|
||||
private bool _hasExited;
|
||||
|
||||
public CommonProcess(ProcessOptions options)
|
||||
{
|
||||
_options = options;
|
||||
StartInfo = options;
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
@@ -27,10 +27,10 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
CreateNoWindow = options.CreateNoWindow,
|
||||
RedirectStandardError = options.RedirectStandardError,
|
||||
RedirectStandardInput = options.RedirectStandardInput,
|
||||
RedirectStandardOutput = options.RedirectStandardOutput
|
||||
RedirectStandardOutput = options.RedirectStandardOutput,
|
||||
ErrorDialog = options.ErrorDialog
|
||||
};
|
||||
|
||||
startInfo.ErrorDialog = options.ErrorDialog;
|
||||
|
||||
if (options.IsHidden)
|
||||
{
|
||||
@@ -45,11 +45,22 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
if (options.EnableRaisingEvents)
|
||||
{
|
||||
_process.EnableRaisingEvents = true;
|
||||
_process.Exited += _process_Exited;
|
||||
_process.Exited += OnProcessExited;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _hasExited;
|
||||
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
|
||||
@@ -72,25 +83,6 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
private void _process_Exited(object sender, EventArgs e)
|
||||
{
|
||||
_hasExited = true;
|
||||
if (Exited != null)
|
||||
{
|
||||
Exited(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public ProcessOptions StartInfo => _options;
|
||||
|
||||
public StreamWriter StandardInput => _process.StandardInput;
|
||||
|
||||
public StreamReader StandardError => _process.StandardError;
|
||||
|
||||
public StreamReader StandardOutput => _process.StandardOutput;
|
||||
|
||||
public int ExitCode => _process.ExitCode;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_process.Start();
|
||||
@@ -108,7 +100,7 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
|
||||
public Task<bool> WaitForExitAsync(int timeMs)
|
||||
{
|
||||
//Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
||||
// Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
||||
|
||||
if (HasExited)
|
||||
{
|
||||
@@ -130,7 +122,29 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_process?.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
|
||||
<ProjectReference Include="..\OpenSubtitlesHandler\OpenSubtitlesHandler.csproj" />
|
||||
<ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
|
||||
<ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj" />
|
||||
<ProjectReference Include="..\Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj" />
|
||||
@@ -47,6 +46,21 @@
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code analysers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Localization\iso6392.txt" />
|
||||
<EmbeddedResource Include="Localization\countries.json" />
|
||||
|
||||
@@ -388,7 +388,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
|
||||
FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
|
||||
|
||||
CollectionFolders = GetTopParentIds(newAndRemoved, user, allUserRootChildren).ToArray()
|
||||
CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
return item.SourceType == SourceType.Library;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetTopParentIds(List<BaseItem> items, User user, List<Folder> allUserRootChildren)
|
||||
private IEnumerable<string> GetTopParentIds(List<BaseItem> items, List<Folder> allUserRootChildren)
|
||||
{
|
||||
var list = new List<string>();
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Emby.Server.Implementations.EnvironmentInfo
|
||||
{
|
||||
public class EnvironmentInfo : IEnvironmentInfo
|
||||
{
|
||||
public EnvironmentInfo(MediaBrowser.Model.System.OperatingSystem operatingSystem)
|
||||
{
|
||||
OperatingSystem = operatingSystem;
|
||||
}
|
||||
|
||||
public MediaBrowser.Model.System.OperatingSystem OperatingSystem { get; private set; }
|
||||
|
||||
public string OperatingSystemName
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (OperatingSystem)
|
||||
{
|
||||
case MediaBrowser.Model.System.OperatingSystem.Android: return "Android";
|
||||
case MediaBrowser.Model.System.OperatingSystem.BSD: return "BSD";
|
||||
case MediaBrowser.Model.System.OperatingSystem.Linux: return "Linux";
|
||||
case MediaBrowser.Model.System.OperatingSystem.OSX: return "macOS";
|
||||
case MediaBrowser.Model.System.OperatingSystem.Windows: return "Windows";
|
||||
default: throw new Exception($"Unknown OS {OperatingSystem}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string OperatingSystemVersion => Environment.OSVersion.Version.ToString() + " " + Environment.OSVersion.ServicePack.ToString();
|
||||
|
||||
public Architecture SystemArchitecture => RuntimeInformation.OSArchitecture;
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string expires))
|
||||
{
|
||||
responseHeaders[HeaderNames.Expires] = "-1";
|
||||
responseHeaders[HeaderNames.Expires] = "0";
|
||||
}
|
||||
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
@@ -146,7 +146,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _))
|
||||
{
|
||||
responseHeaders[HeaderNames.Expires] = "-1";
|
||||
responseHeaders[HeaderNames.Expires] = "0";
|
||||
}
|
||||
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
@@ -190,7 +190,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _))
|
||||
{
|
||||
responseHeaders[HeaderNames.Expires] = "-1";
|
||||
responseHeaders[HeaderNames.Expires] = "0";
|
||||
}
|
||||
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
@@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
responseHeaders[HeaderNames.Expires] = "-1";
|
||||
responseHeaders[HeaderNames.Expires] = "0";
|
||||
|
||||
return ToOptimizedResultInternal(requestContext, result, responseHeaders);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
// This code is executed before the service
|
||||
var auth = AuthorizationContext.GetAuthorizationInfo(request);
|
||||
|
||||
if (!IsExemptFromAuthenticationToken(auth, authAttribtues, request))
|
||||
if (!IsExemptFromAuthenticationToken(authAttribtues, request))
|
||||
{
|
||||
ValidateSecurityToken(request, auth.Token);
|
||||
}
|
||||
@@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request)
|
||||
private bool IsExemptFromAuthenticationToken(IAuthenticationAttributes authAttribtues, IRequest request)
|
||||
{
|
||||
if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer
|
||||
@@ -15,8 +13,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// </summary>
|
||||
public class StreamWriter : IAsyncStreamWriter, IHasHeaders
|
||||
{
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source stream.
|
||||
/// </summary>
|
||||
|
||||
@@ -10,8 +10,8 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
@@ -127,7 +127,6 @@ namespace Emby.Server.Implementations.IO
|
||||
private IServerConfigurationManager ConfigurationManager { get; set; }
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IEnvironmentInfo _environmentInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
|
||||
@@ -136,14 +135,12 @@ namespace Emby.Server.Implementations.IO
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environmentInfo)
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
LibraryManager = libraryManager;
|
||||
Logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
ConfigurationManager = configurationManager;
|
||||
_fileSystem = fileSystem;
|
||||
_environmentInfo = environmentInfo;
|
||||
}
|
||||
|
||||
private bool IsLibraryMonitorEnabled(BaseItem item)
|
||||
@@ -267,7 +264,7 @@ namespace Emby.Server.Implementations.IO
|
||||
return;
|
||||
}
|
||||
|
||||
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
|
||||
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
||||
{
|
||||
if (path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase) || path.StartsWith("smb://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -276,12 +273,6 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
}
|
||||
|
||||
if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Android)
|
||||
{
|
||||
// causing crashing
|
||||
return;
|
||||
}
|
||||
|
||||
// Already being watched
|
||||
if (_fileSystemWatchers.ContainsKey(path))
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
@@ -24,22 +25,19 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
private readonly string _tempPath;
|
||||
|
||||
private readonly IEnvironmentInfo _environmentInfo;
|
||||
private readonly bool _isEnvironmentCaseInsensitive;
|
||||
|
||||
public ManagedFileSystem(
|
||||
ILoggerFactory loggerFactory,
|
||||
IEnvironmentInfo environmentInfo,
|
||||
IApplicationPaths applicationPaths)
|
||||
{
|
||||
Logger = loggerFactory.CreateLogger("FileSystem");
|
||||
_supportsAsyncFileStreams = true;
|
||||
_tempPath = applicationPaths.TempDirectory;
|
||||
_environmentInfo = environmentInfo;
|
||||
|
||||
SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
|
||||
SetInvalidFileNameChars(OperatingSystem.Id == OperatingSystemId.Windows);
|
||||
|
||||
_isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||
_isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows;
|
||||
}
|
||||
|
||||
public virtual void AddShortcutHandler(IShortcutHandler handler)
|
||||
@@ -468,7 +466,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
public virtual void SetHidden(string path, bool isHidden)
|
||||
{
|
||||
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
|
||||
if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -492,7 +490,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
public virtual void SetReadOnly(string path, bool isReadOnly)
|
||||
{
|
||||
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
|
||||
if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -516,7 +514,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
|
||||
{
|
||||
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
|
||||
if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -801,7 +799,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
public virtual void SetExecutable(string path)
|
||||
{
|
||||
if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
|
||||
if (OperatingSystem.Id == MediaBrowser.Model.System.OperatingSystemId.Darwin)
|
||||
{
|
||||
RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -8,168 +9,213 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
public class StreamHelper : IStreamHelper
|
||||
{
|
||||
private const int StreamCopyToBufferSize = 81920;
|
||||
|
||||
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
int read;
|
||||
while ((read = source.Read(buffer, 0, buffer.Length)) != 0)
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false);
|
||||
|
||||
if (onStarted != null)
|
||||
int read;
|
||||
while ((read = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
onStarted();
|
||||
onStarted = null;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false);
|
||||
|
||||
if (onStarted != null)
|
||||
{
|
||||
onStarted();
|
||||
onStarted = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
if (emptyReadLimit <= 0)
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
try
|
||||
{
|
||||
int read;
|
||||
while ((read = source.Read(buffer, 0, buffer.Length)) != 0)
|
||||
if (emptyReadLimit <= 0)
|
||||
{
|
||||
int read;
|
||||
while ((read = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var eofCount = 0;
|
||||
|
||||
while (eofCount < emptyReadLimit)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false);
|
||||
}
|
||||
var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
eofCount++;
|
||||
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
eofCount = 0;
|
||||
|
||||
await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var eofCount = 0;
|
||||
|
||||
while (eofCount < emptyReadLimit)
|
||||
finally
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var bytesRead = source.Read(buffer, 0, buffer.Length);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
eofCount++;
|
||||
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
eofCount = 0;
|
||||
|
||||
await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
|
||||
}
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
const int StreamCopyToBufferSize = 81920;
|
||||
public async Task<int> CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken)
|
||||
{
|
||||
var array = new byte[StreamCopyToBufferSize];
|
||||
int bytesRead;
|
||||
int totalBytesRead = 0;
|
||||
|
||||
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
|
||||
try
|
||||
{
|
||||
var bytesToWrite = bytesRead;
|
||||
int totalBytesRead = 0;
|
||||
|
||||
if (bytesToWrite > 0)
|
||||
int bytesRead;
|
||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||
var bytesToWrite = bytesRead;
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (bytesToWrite > 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalBytesRead;
|
||||
return totalBytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CopyToAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken)
|
||||
{
|
||||
var array = new byte[StreamCopyToBufferSize];
|
||||
int bytesRead;
|
||||
int totalBytesRead = 0;
|
||||
|
||||
while ((bytesRead = source.Read(array, 0, array.Length)) != 0)
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
|
||||
try
|
||||
{
|
||||
var bytesToWrite = bytesRead;
|
||||
int bytesRead;
|
||||
int totalBytesRead = 0;
|
||||
|
||||
if (bytesToWrite > 0)
|
||||
while ((bytesRead = source.Read(buffer, 0, buffer.Length)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||
var bytesToWrite = bytesRead;
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (bytesToWrite > 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalBytesRead;
|
||||
return totalBytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CopyToAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
||||
{
|
||||
var array = new byte[StreamCopyToBufferSize];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = source.Read(array, 0, array.Length)) != 0)
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
|
||||
try
|
||||
{
|
||||
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||
int bytesRead;
|
||||
|
||||
if (bytesToWrite > 0)
|
||||
while ((bytesRead = source.Read(buffer, 0, buffer.Length)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||
|
||||
copyLength -= bytesToWrite;
|
||||
if (bytesToWrite > 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (copyLength <= 0)
|
||||
{
|
||||
break;
|
||||
copyLength -= bytesToWrite;
|
||||
|
||||
if (copyLength <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
||||
{
|
||||
var array = new byte[StreamCopyToBufferSize];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
|
||||
try
|
||||
{
|
||||
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||
int bytesRead;
|
||||
|
||||
if (bytesToWrite > 0)
|
||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||
|
||||
copyLength -= bytesToWrite;
|
||||
if (bytesToWrite > 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (copyLength <= 0)
|
||||
{
|
||||
break;
|
||||
copyLength -= bytesToWrite;
|
||||
|
||||
if (copyLength <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
try
|
||||
{
|
||||
var bytesRead = await CopyToAsyncInternal(source, target, buffer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//var position = fs.Position;
|
||||
//_logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
||||
|
||||
if (bytesRead == 0)
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
var bytesRead = await CopyToAsyncInternal(source, target, buffer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<int> CopyToAsyncInternal(Stream source, Stream destination, byte[] buffer, CancellationToken cancellationToken)
|
||||
|
||||
@@ -7,11 +7,6 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
string FFmpegPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --ffprobe
|
||||
/// </summary>
|
||||
string FFprobePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --service
|
||||
/// </summary>
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library
|
||||
PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
|
||||
byte[] calculatedHash;
|
||||
string calculatedHashString;
|
||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id))
|
||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(readyHash.Salt))
|
||||
{
|
||||
|
||||
@@ -58,22 +58,23 @@ namespace Emby.Server.Implementations.Library
|
||||
private ILibraryPostScanTask[] PostscanTasks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the intro providers.
|
||||
/// Gets or sets the intro providers.
|
||||
/// </summary>
|
||||
/// <value>The intro providers.</value>
|
||||
private IIntroProvider[] IntroProviders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of entity resolution ignore rules
|
||||
/// Gets or sets the list of entity resolution ignore rules
|
||||
/// </summary>
|
||||
/// <value>The entity resolution ignore rules.</value>
|
||||
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of currently registered entity resolvers
|
||||
/// Gets or sets the list of currently registered entity resolvers
|
||||
/// </summary>
|
||||
/// <value>The entity resolvers enumerable.</value>
|
||||
private IItemResolver[] EntityResolvers { get; set; }
|
||||
|
||||
private IMultiItemResolver[] MultiItemResolvers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -83,7 +84,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private IBaseItemComparer[] Comparers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active item repository
|
||||
/// Gets or sets the active item repository
|
||||
/// </summary>
|
||||
/// <value>The item repository.</value>
|
||||
public IItemRepository ItemRepository { get; set; }
|
||||
@@ -133,12 +134,14 @@ namespace Emby.Server.Implementations.Library
|
||||
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>
|
||||
@@ -150,7 +153,8 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="appHost">The application host</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
/// <param name="taskManager">The task manager.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
@@ -167,6 +171,7 @@ namespace Emby.Server.Implementations.Library
|
||||
Func<IProviderManager> providerManagerFactory,
|
||||
Func<IUserViewManager> userviewManager)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = loggerFactory.CreateLogger(nameof(LibraryManager));
|
||||
_taskManager = taskManager;
|
||||
_userManager = userManager;
|
||||
@@ -176,7 +181,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_fileSystem = fileSystem;
|
||||
_providerManagerFactory = providerManagerFactory;
|
||||
_userviewManager = userviewManager;
|
||||
_appHost = appHost;
|
||||
|
||||
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
|
||||
|
||||
ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
||||
@@ -191,8 +196,9 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="resolvers">The resolvers.</param>
|
||||
/// <param name="introProviders">The intro providers.</param>
|
||||
/// <param name="itemComparers">The item comparers.</param>
|
||||
/// <param name="postscanTasks">The postscan tasks.</param>
|
||||
public void AddParts(IEnumerable<IResolverIgnoreRule> rules,
|
||||
/// <param name="postscanTasks">The post scan tasks.</param>
|
||||
public void AddParts(
|
||||
IEnumerable<IResolverIgnoreRule> rules,
|
||||
IEnumerable<IItemResolver> resolvers,
|
||||
IEnumerable<IIntroProvider> introProviders,
|
||||
IEnumerable<IBaseItemComparer> itemComparers,
|
||||
@@ -203,24 +209,19 @@ namespace Emby.Server.Implementations.Library
|
||||
MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray();
|
||||
IntroProviders = introProviders.ToArray();
|
||||
Comparers = itemComparers.ToArray();
|
||||
|
||||
PostscanTasks = postscanTasks.OrderBy(i =>
|
||||
{
|
||||
var hasOrder = i as IHasOrder;
|
||||
|
||||
return hasOrder == null ? 0 : hasOrder.Order;
|
||||
|
||||
}).ToArray();
|
||||
PostscanTasks = postscanTasks.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _root folder
|
||||
/// </summary>
|
||||
private volatile AggregateFolder _rootFolder;
|
||||
|
||||
/// <summary>
|
||||
/// The _root folder sync lock
|
||||
/// </summary>
|
||||
private readonly object _rootFolderSyncLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root folder.
|
||||
/// </summary>
|
||||
@@ -239,11 +240,13 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _rootFolder;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _wizardCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Records the configuration values.
|
||||
/// </summary>
|
||||
@@ -258,7 +261,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
|
||||
void ConfigurationUpdated(object sender, EventArgs e)
|
||||
private void ConfigurationUpdated(object sender, EventArgs e)
|
||||
{
|
||||
var config = ConfigurationManager.Configuration;
|
||||
|
||||
@@ -335,12 +338,14 @@ namespace Emby.Server.Implementations.Library
|
||||
// channel no longer installed
|
||||
}
|
||||
}
|
||||
|
||||
options.DeleteFileLocation = false;
|
||||
}
|
||||
|
||||
if (item is LiveTvProgram)
|
||||
{
|
||||
_logger.LogDebug("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
_logger.LogDebug(
|
||||
"Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
item.GetType().Name,
|
||||
item.Name ?? "Unknown name",
|
||||
item.Path ?? string.Empty,
|
||||
@@ -348,7 +353,8 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
_logger.LogInformation(
|
||||
"Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||
item.GetType().Name,
|
||||
item.Name ?? "Unknown name",
|
||||
item.Path ?? string.Empty,
|
||||
@@ -488,12 +494,13 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath))
|
||||
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)
|
||||
@@ -511,13 +518,11 @@ namespace Emby.Server.Implementations.Library
|
||||
return key.GetMD5();
|
||||
}
|
||||
|
||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo,
|
||||
Folder parent = null)
|
||||
{
|
||||
return ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent);
|
||||
}
|
||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
|
||||
=> ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent);
|
||||
|
||||
private BaseItem ResolvePath(FileSystemMetadata fileInfo,
|
||||
private BaseItem ResolvePath(
|
||||
FileSystemMetadata fileInfo,
|
||||
IDirectoryService directoryService,
|
||||
IItemResolver[] resolvers,
|
||||
Folder parent = null,
|
||||
@@ -572,7 +577,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
_logger.LogError(ex, "Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", isPhysicalRoot, isVf);
|
||||
|
||||
files = new FileSystemMetadata[] { };
|
||||
files = Array.Empty<FileSystemMetadata>();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -600,13 +605,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
public bool IgnoreFile(FileSystemMetadata file, BaseItem parent)
|
||||
{
|
||||
if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
=> EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent));
|
||||
|
||||
public List<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
|
||||
{
|
||||
@@ -646,7 +645,8 @@ namespace Emby.Server.Implementations.Library
|
||||
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
|
||||
public IEnumerable<BaseItem> ResolvePaths(
|
||||
IEnumerable<FileSystemMetadata> files,
|
||||
IDirectoryService directoryService,
|
||||
Folder parent,
|
||||
LibraryOptions libraryOptions,
|
||||
@@ -672,6 +672,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService);
|
||||
}
|
||||
|
||||
items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));
|
||||
return items;
|
||||
}
|
||||
@@ -681,7 +682,8 @@ namespace Emby.Server.Implementations.Library
|
||||
return ResolveFileList(fileList, directoryService, parent, collectionType, resolvers, libraryOptions);
|
||||
}
|
||||
|
||||
private IEnumerable<BaseItem> ResolveFileList(IEnumerable<FileSystemMetadata> fileList,
|
||||
private IEnumerable<BaseItem> ResolveFileList(
|
||||
IEnumerable<FileSystemMetadata> fileList,
|
||||
IDirectoryService directoryService,
|
||||
Folder parent,
|
||||
string collectionType,
|
||||
@@ -766,6 +768,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private volatile UserRootFolder _userRootFolder;
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
public Folder GetUserRootFolder()
|
||||
{
|
||||
if (_userRootFolder == null)
|
||||
@@ -810,8 +813,6 @@ namespace Emby.Server.Implementations.Library
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
//_logger.LogInformation("FindByPath {0}", path);
|
||||
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
Path = path,
|
||||
@@ -885,7 +886,6 @@ namespace Emby.Server.Implementations.Library
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>Task{Year}.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public Year GetYear(int value)
|
||||
{
|
||||
if (value <= 0)
|
||||
@@ -1027,20 +1027,25 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken)
|
||||
{
|
||||
var rootChildren = RootFolder.Children.ToList();
|
||||
rootChildren = GetUserRootFolder().Children.ToList();
|
||||
|
||||
await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Start by just validating the children of the root, but go no further
|
||||
await RootFolder.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false);
|
||||
await RootFolder.ValidateChildren(
|
||||
new SimpleProgress<double>(),
|
||||
cancellationToken,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
|
||||
recursive: false).ConfigureAwait(false);
|
||||
|
||||
await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await GetUserRootFolder().ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false).ConfigureAwait(false);
|
||||
await GetUserRootFolder().ValidateChildren(
|
||||
new SimpleProgress<double>(),
|
||||
cancellationToken,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)),
|
||||
recursive: false).ConfigureAwait(false);
|
||||
|
||||
// Quickly scan CollectionFolders for changes
|
||||
foreach (var folder in GetUserRootFolder().Children.OfType<Folder>().ToList())
|
||||
foreach (var folder in GetUserRootFolder().Children.OfType<Folder>())
|
||||
{
|
||||
await folder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -1204,7 +1209,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private string GetCollectionType(string path)
|
||||
{
|
||||
return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false)
|
||||
.Select(i => Path.GetFileNameWithoutExtension(i))
|
||||
.Select(Path.GetFileNameWithoutExtension)
|
||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
||||
}
|
||||
|
||||
@@ -1218,7 +1223,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException(nameof(id), "Guid can't be empty");
|
||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
|
||||
if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
|
||||
@@ -1386,17 +1391,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList();
|
||||
|
||||
if (parents.All(i =>
|
||||
{
|
||||
if (i is ICollectionFolder || i is UserView)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name);
|
||||
return false;
|
||||
|
||||
}))
|
||||
if (parents.All(i => i is ICollectionFolder || i is UserView))
|
||||
{
|
||||
// Optimize by querying against top level views
|
||||
query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
|
||||
@@ -1452,17 +1447,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
|
||||
{
|
||||
if (parents.All(i =>
|
||||
{
|
||||
if (i is ICollectionFolder || i is UserView)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name);
|
||||
return false;
|
||||
|
||||
}))
|
||||
if (parents.All(i => i is ICollectionFolder || i is UserView))
|
||||
{
|
||||
// Optimize by querying against top level views
|
||||
query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
|
||||
@@ -1511,11 +1496,9 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private IEnumerable<Guid> GetTopParentIdsForQuery(BaseItem item, User user)
|
||||
{
|
||||
var view = item as UserView;
|
||||
|
||||
if (view != null)
|
||||
if (item is UserView view)
|
||||
{
|
||||
if (string.Equals(view.ViewType, CollectionType.LiveTv))
|
||||
if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.Ordinal))
|
||||
{
|
||||
return new[] { view.Id };
|
||||
}
|
||||
@@ -1528,8 +1511,10 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return GetTopParentIdsForQuery(displayParent, user);
|
||||
}
|
||||
|
||||
return Array.Empty<Guid>();
|
||||
}
|
||||
|
||||
if (!view.ParentId.Equals(Guid.Empty))
|
||||
{
|
||||
var displayParent = GetItemById(view.ParentId);
|
||||
@@ -1537,6 +1522,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return GetTopParentIdsForQuery(displayParent, user);
|
||||
}
|
||||
|
||||
return Array.Empty<Guid>();
|
||||
}
|
||||
|
||||
@@ -1550,11 +1536,11 @@ namespace Emby.Server.Implementations.Library
|
||||
.Where(i => user.IsFolderGrouped(i.Id))
|
||||
.SelectMany(i => GetTopParentIdsForQuery(i, user));
|
||||
}
|
||||
|
||||
return Array.Empty<Guid>();
|
||||
}
|
||||
|
||||
var collectionFolder = item as CollectionFolder;
|
||||
if (collectionFolder != null)
|
||||
if (item is CollectionFolder collectionFolder)
|
||||
{
|
||||
return collectionFolder.PhysicalFolderIds;
|
||||
}
|
||||
@@ -1564,6 +1550,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return new[] { topParent.Id };
|
||||
}
|
||||
|
||||
return Array.Empty<Guid>();
|
||||
}
|
||||
|
||||
@@ -1760,19 +1747,16 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (comparer != null)
|
||||
// If it requires a user, create a new one, and assign the user
|
||||
if (comparer is IUserBaseItemComparer)
|
||||
{
|
||||
// If it requires a user, create a new one, and assign the user
|
||||
if (comparer is IUserBaseItemComparer)
|
||||
{
|
||||
var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType());
|
||||
var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType());
|
||||
|
||||
userComparer.User = user;
|
||||
userComparer.UserManager = _userManager;
|
||||
userComparer.UserDataRepository = _userDataRepository;
|
||||
userComparer.User = user;
|
||||
userComparer.UserManager = _userManager;
|
||||
userComparer.UserDataRepository = _userDataRepository;
|
||||
|
||||
return userComparer;
|
||||
}
|
||||
return userComparer;
|
||||
}
|
||||
|
||||
return comparer;
|
||||
@@ -1783,7 +1767,6 @@ namespace Emby.Server.Implementations.Library
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="parent">The parent item.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public void CreateItem(BaseItem item, BaseItem parent)
|
||||
{
|
||||
CreateItems(new[] { item }, parent, CancellationToken.None);
|
||||
@@ -1793,20 +1776,23 @@ namespace Emby.Server.Implementations.Library
|
||||
/// Creates the items.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="parent">The parent item</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
|
||||
{
|
||||
ItemRepository.SaveItems(items, cancellationToken);
|
||||
// Don't iterate multiple times
|
||||
var itemsList = items.ToList();
|
||||
|
||||
foreach (var item in items)
|
||||
ItemRepository.SaveItems(itemsList, cancellationToken);
|
||||
|
||||
foreach (var item in itemsList)
|
||||
{
|
||||
RegisterItem(item);
|
||||
}
|
||||
|
||||
if (ItemAdded != null)
|
||||
{
|
||||
foreach (var item in items)
|
||||
foreach (var item in itemsList)
|
||||
{
|
||||
// With the live tv guide this just creates too much noise
|
||||
if (item.SourceType != SourceType.Library)
|
||||
@@ -1816,11 +1802,13 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
try
|
||||
{
|
||||
ItemAdded(this, new ItemChangeEventArgs
|
||||
{
|
||||
Item = item,
|
||||
Parent = parent ?? item.GetParent()
|
||||
});
|
||||
ItemAdded(
|
||||
this,
|
||||
new ItemChangeEventArgs
|
||||
{
|
||||
Item = item,
|
||||
Parent = parent ?? item.GetParent()
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1842,7 +1830,10 @@ namespace Emby.Server.Implementations.Library
|
||||
/// </summary>
|
||||
public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var item in items)
|
||||
// Don't iterate multiple times
|
||||
var itemsList = items.ToList();
|
||||
|
||||
foreach (var item in itemsList)
|
||||
{
|
||||
if (item.IsFileProtocol)
|
||||
{
|
||||
@@ -1854,14 +1845,11 @@ namespace Emby.Server.Implementations.Library
|
||||
RegisterItem(item);
|
||||
}
|
||||
|
||||
//var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name;
|
||||
//_logger.LogDebug("Saving {0} to database.", logName);
|
||||
|
||||
ItemRepository.SaveItems(items, cancellationToken);
|
||||
ItemRepository.SaveItems(itemsList, cancellationToken);
|
||||
|
||||
if (ItemUpdated != null)
|
||||
{
|
||||
foreach (var item in items)
|
||||
foreach (var item in itemsList)
|
||||
{
|
||||
// With the live tv guide this just creates too much noise
|
||||
if (item.SourceType != SourceType.Library)
|
||||
@@ -1871,12 +1859,14 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
try
|
||||
{
|
||||
ItemUpdated(this, new ItemChangeEventArgs
|
||||
{
|
||||
Item = item,
|
||||
Parent = parent,
|
||||
UpdateReason = updateReason
|
||||
});
|
||||
ItemUpdated(
|
||||
this,
|
||||
new ItemChangeEventArgs
|
||||
{
|
||||
Item = item,
|
||||
Parent = parent,
|
||||
UpdateReason = updateReason
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1890,9 +1880,9 @@ namespace Emby.Server.Implementations.Library
|
||||
/// Updates the item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="parent">The parent item.</param>
|
||||
/// <param name="updateReason">The update reason.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
{
|
||||
UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
|
||||
@@ -1902,17 +1892,20 @@ namespace Emby.Server.Implementations.Library
|
||||
/// Reports the item removed.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="parent">The parent item.</param>
|
||||
public void ReportItemRemoved(BaseItem item, BaseItem parent)
|
||||
{
|
||||
if (ItemRemoved != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ItemRemoved(this, new ItemChangeEventArgs
|
||||
{
|
||||
Item = item,
|
||||
Parent = parent
|
||||
});
|
||||
ItemRemoved(
|
||||
this,
|
||||
new ItemChangeEventArgs
|
||||
{
|
||||
Item = item,
|
||||
Parent = parent
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -2038,8 +2031,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
public string GetConfiguredContentType(BaseItem item, bool inheritConfiguredPath)
|
||||
{
|
||||
var collectionFolder = item as ICollectionFolder;
|
||||
if (collectionFolder != null)
|
||||
if (item is ICollectionFolder collectionFolder)
|
||||
{
|
||||
return collectionFolder.CollectionType;
|
||||
}
|
||||
@@ -2049,13 +2041,11 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private string GetContentTypeOverride(string path, bool inherit)
|
||||
{
|
||||
var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrEmpty(i.Name) && _fileSystem.ContainsSubPath(i.Name, path)));
|
||||
if (nameValuePair != null)
|
||||
{
|
||||
return nameValuePair.Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
var nameValuePair = ConfigurationManager.Configuration.ContentTypes
|
||||
.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
|
||||
|| (inherit && !string.IsNullOrEmpty(i.Name)
|
||||
&& _fileSystem.ContainsSubPath(i.Name, path)));
|
||||
return nameValuePair?.Value;
|
||||
}
|
||||
|
||||
private string GetTopFolderContentType(BaseItem item)
|
||||
@@ -2072,6 +2062,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
item = parent;
|
||||
}
|
||||
|
||||
@@ -2083,9 +2074,9 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
|
||||
//private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromMinutes(1);
|
||||
|
||||
public UserView GetNamedView(User user,
|
||||
public UserView GetNamedView(
|
||||
User user,
|
||||
string name,
|
||||
string viewType,
|
||||
string sortName)
|
||||
@@ -2093,13 +2084,15 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetNamedView(user, name, Guid.Empty, viewType, sortName);
|
||||
}
|
||||
|
||||
public UserView GetNamedView(string name,
|
||||
public UserView GetNamedView(
|
||||
string name,
|
||||
string viewType,
|
||||
string sortName)
|
||||
{
|
||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath,
|
||||
"views",
|
||||
_fileSystem.GetValidFilename(viewType));
|
||||
var path = Path.Combine(
|
||||
ConfigurationManager.ApplicationPaths.InternalMetadataPath,
|
||||
"views",
|
||||
_fileSystem.GetValidFilename(viewType));
|
||||
|
||||
var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
|
||||
|
||||
@@ -2135,7 +2128,8 @@ namespace Emby.Server.Implementations.Library
|
||||
return item;
|
||||
}
|
||||
|
||||
public UserView GetNamedView(User user,
|
||||
public UserView GetNamedView(
|
||||
User user,
|
||||
string name,
|
||||
Guid parentId,
|
||||
string viewType,
|
||||
@@ -2164,10 +2158,10 @@ namespace Emby.Server.Implementations.Library
|
||||
Name = name,
|
||||
ViewType = viewType,
|
||||
ForcedSortName = sortName,
|
||||
UserId = user.Id
|
||||
UserId = user.Id,
|
||||
DisplayParentId = parentId
|
||||
};
|
||||
|
||||
item.DisplayParentId = parentId;
|
||||
|
||||
CreateItem(item, null);
|
||||
|
||||
@@ -2184,20 +2178,24 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (refresh)
|
||||
{
|
||||
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
// Need to force save to increment DateLastSaved
|
||||
ForceSave = true
|
||||
_providerManagerFactory().QueueRefresh(
|
||||
item.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
// Need to force save to increment DateLastSaved
|
||||
ForceSave = true
|
||||
|
||||
}, RefreshPriority.Normal);
|
||||
},
|
||||
RefreshPriority.Normal);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public UserView GetShadowView(BaseItem parent,
|
||||
string viewType,
|
||||
string sortName)
|
||||
public UserView GetShadowView(
|
||||
BaseItem parent,
|
||||
string viewType,
|
||||
string sortName)
|
||||
{
|
||||
if (parent == null)
|
||||
{
|
||||
@@ -2248,18 +2246,21 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (refresh)
|
||||
{
|
||||
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
// Need to force save to increment DateLastSaved
|
||||
ForceSave = true
|
||||
|
||||
}, RefreshPriority.Normal);
|
||||
_providerManagerFactory().QueueRefresh(
|
||||
item.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
// Need to force save to increment DateLastSaved
|
||||
ForceSave = true
|
||||
},
|
||||
RefreshPriority.Normal);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public UserView GetNamedView(string name,
|
||||
public UserView GetNamedView(
|
||||
string name,
|
||||
Guid parentId,
|
||||
string viewType,
|
||||
string sortName,
|
||||
@@ -2322,17 +2323,21 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (refresh)
|
||||
{
|
||||
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
// Need to force save to increment DateLastSaved
|
||||
ForceSave = true
|
||||
}, RefreshPriority.Normal);
|
||||
_providerManagerFactory().QueueRefresh(
|
||||
item.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
|
||||
{
|
||||
// Need to force save to increment DateLastSaved
|
||||
ForceSave = true
|
||||
},
|
||||
RefreshPriority.Normal);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public void AddExternalSubtitleStreams(List<MediaStream> streams,
|
||||
public void AddExternalSubtitleStreams(
|
||||
List<MediaStream> streams,
|
||||
string videoPath,
|
||||
string[] files)
|
||||
{
|
||||
@@ -2436,6 +2441,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
|
||||
episode.IndexNumber = episodeInfo.EpisodeNumber;
|
||||
}
|
||||
|
||||
@@ -2445,6 +2451,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
|
||||
episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber;
|
||||
}
|
||||
|
||||
@@ -2454,6 +2461,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
|
||||
episode.ParentIndexNumber = episodeInfo.SeasonNumber;
|
||||
}
|
||||
}
|
||||
@@ -2483,6 +2491,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
private NamingOptions _namingOptions;
|
||||
private string[] _videoFileExtensions;
|
||||
|
||||
private NamingOptions GetNamingOptionsInternal()
|
||||
{
|
||||
if (_namingOptions == null)
|
||||
@@ -2679,7 +2688,7 @@ namespace Emby.Server.Implementations.Library
|
||||
var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
|
||||
var changed = false;
|
||||
|
||||
if (!string.Equals(newPath, path))
|
||||
if (!string.Equals(newPath, path, StringComparison.Ordinal))
|
||||
{
|
||||
if (to.IndexOf('/') != -1)
|
||||
{
|
||||
@@ -2803,6 +2812,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -2907,6 +2917,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
private const string ShortcutFileExtension = ".mblink";
|
||||
|
||||
public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
|
||||
{
|
||||
AddMediaPathInternal(virtualFolderName, pathInfo, true);
|
||||
@@ -2923,7 +2934,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
throw new ArgumentException(nameof(path));
|
||||
}
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
|
||||
@@ -216,10 +216,10 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
public static bool IsValidUsername(string username)
|
||||
{
|
||||
//This is some regex that matches only on unicode "word" characters, as well as -, _ and @
|
||||
//In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
|
||||
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)
|
||||
return Regex.IsMatch(username, "^[\\w-'._@]*$");
|
||||
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
|
||||
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
|
||||
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
|
||||
return Regex.IsMatch(username, @"^[\w\-'._@]*$");
|
||||
}
|
||||
|
||||
private static bool IsValidUsernameCharacter(char i)
|
||||
@@ -448,11 +448,19 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
user.Policy.InvalidLoginAttemptCount = newValue;
|
||||
|
||||
var maxCount = user.Policy.IsAdministrator ? 3 : 5;
|
||||
// Check for users without a value here and then fill in the default value
|
||||
// also protect from an always lockout if misconfigured
|
||||
if (user.Policy.LoginAttemptsBeforeLockout == null || user.Policy.LoginAttemptsBeforeLockout == 0)
|
||||
{
|
||||
user.Policy.LoginAttemptsBeforeLockout = user.Policy.IsAdministrator ? 5 : 3;
|
||||
}
|
||||
|
||||
var maxCount = user.Policy.LoginAttemptsBeforeLockout;
|
||||
|
||||
var fireLockout = false;
|
||||
|
||||
if (newValue >= maxCount)
|
||||
// -1 can be used to specify no lockout value
|
||||
if (maxCount != -1 && newValue >= maxCount)
|
||||
{
|
||||
_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue);
|
||||
user.Policy.IsDisabled = true;
|
||||
|
||||
@@ -33,7 +33,6 @@ using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -58,7 +57,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
private readonly IAssemblyInfo _assemblyInfo;
|
||||
private IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public static EmbyTV Current;
|
||||
@@ -74,7 +72,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
public EmbyTV(IServerApplicationHost appHost,
|
||||
IStreamHelper streamHelper,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IAssemblyInfo assemblyInfo,
|
||||
ILogger logger,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IHttpClient httpClient,
|
||||
@@ -101,7 +98,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_processFactory = processFactory;
|
||||
_liveTvManager = (LiveTvManager)liveTvManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_assemblyInfo = assemblyInfo;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_streamHelper = streamHelper;
|
||||
|
||||
@@ -265,7 +261,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
public string HomePageUrl => "https://github.com/jellyfin/jellyfin";
|
||||
|
||||
public async Task RefreshSeriesTimers(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
public async Task RefreshSeriesTimers(CancellationToken cancellationToken)
|
||||
{
|
||||
var seriesTimers = await GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -275,7 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshTimers(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
public async Task RefreshTimers(CancellationToken cancellationToken)
|
||||
{
|
||||
var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -1087,8 +1087,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
if (coreService != null)
|
||||
{
|
||||
await coreService.RefreshSeriesTimers(cancellationToken, new SimpleProgress<double>()).ConfigureAwait(false);
|
||||
await coreService.RefreshTimers(cancellationToken, new SimpleProgress<double>()).ConfigureAwait(false);
|
||||
await coreService.RefreshSeriesTimers(cancellationToken).ConfigureAwait(false);
|
||||
await coreService.RefreshTimers(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Load these now which will prefetch metadata
|
||||
|
||||
@@ -151,7 +151,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
});
|
||||
}
|
||||
|
||||
private static int RtpHeaderBytes = 12;
|
||||
private const int RtpHeaderBytes = 12;
|
||||
|
||||
private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||
{
|
||||
var bufferSize = 81920;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
public string OriginalStreamId { get; set; }
|
||||
public bool EnableStreamSharing { get; set; }
|
||||
public string UniqueId { get; private set; }
|
||||
public string UniqueId { get; }
|
||||
|
||||
protected readonly IFileSystem FileSystem;
|
||||
protected readonly IServerApplicationPaths AppPaths;
|
||||
@@ -31,12 +31,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
protected readonly ILogger Logger;
|
||||
protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
public string TunerHostId { get; private set; }
|
||||
public string TunerHostId { get; }
|
||||
|
||||
public DateTime DateOpened { get; protected set; }
|
||||
|
||||
public Func<Task> OnClose { get; set; }
|
||||
|
||||
public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
|
||||
{
|
||||
OriginalMediaSource = mediaSource;
|
||||
@@ -76,26 +74,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
LiveStreamCancellationTokenSource.Cancel();
|
||||
|
||||
if (OnClose != null)
|
||||
{
|
||||
return CloseWithExternalFn();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task CloseWithExternalFn()
|
||||
{
|
||||
try
|
||||
{
|
||||
await OnClose().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error closing live stream");
|
||||
}
|
||||
}
|
||||
|
||||
protected Stream GetInputStream(string path, bool allowAsyncFileRead)
|
||||
{
|
||||
var fileOpenOptions = FileOpenOptions.SequentialScan;
|
||||
@@ -113,27 +94,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
return DeleteTempFiles(GetStreamFilePaths());
|
||||
}
|
||||
|
||||
protected async Task DeleteTempFiles(List<string> paths, int retryCount = 0)
|
||||
protected async Task DeleteTempFiles(IEnumerable<string> paths, int retryCount = 0)
|
||||
{
|
||||
if (retryCount == 0)
|
||||
{
|
||||
Logger.LogInformation("Deleting temp files {0}", string.Join(", ", paths.ToArray()));
|
||||
Logger.LogInformation("Deleting temp files {0}", paths);
|
||||
}
|
||||
|
||||
var failedFiles = new List<string>();
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FileSystem.DeleteFile(path);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error deleting file {path}", path);
|
||||
@@ -157,8 +137,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token;
|
||||
|
||||
var allowAsync = false;
|
||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
// use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT;
|
||||
|
||||
bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
|
||||
|
||||
@@ -181,28 +161,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
Logger.LogInformation("Live Stream ended.");
|
||||
}
|
||||
|
||||
private Tuple<string, bool> GetNextFile(string currentFile)
|
||||
private (string file, bool isLastFile) GetNextFile(string currentFile)
|
||||
{
|
||||
var files = GetStreamFilePaths();
|
||||
|
||||
//logger.LogInformation("Live stream files: {0}", string.Join(", ", files.ToArray()));
|
||||
|
||||
if (string.IsNullOrEmpty(currentFile))
|
||||
{
|
||||
return new Tuple<string, bool>(files.Last(), true);
|
||||
return (files.Last(), true);
|
||||
}
|
||||
|
||||
var nextIndex = files.FindIndex(i => string.Equals(i, currentFile, StringComparison.OrdinalIgnoreCase)) + 1;
|
||||
|
||||
var isLastFile = nextIndex == files.Count - 1;
|
||||
|
||||
return new Tuple<string, bool>(files.ElementAtOrDefault(nextIndex), isLastFile);
|
||||
return (files.ElementAtOrDefault(nextIndex), isLastFile);
|
||||
}
|
||||
|
||||
private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
//logger.LogInformation("Opening live stream file {0}. Empty read limit: {1}", path, emptyReadLimit);
|
||||
|
||||
using (var inputStream = (FileStream)GetInputStream(path, allowAsync))
|
||||
{
|
||||
if (seekFile)
|
||||
@@ -218,7 +194,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
private void TrySeek(FileStream stream, long offset)
|
||||
{
|
||||
//logger.LogInformation("TrySeek live stream");
|
||||
if (!stream.CanSeek)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
stream.Seek(offset, SeekOrigin.End);
|
||||
|
||||
@@ -10,14 +10,12 @@ using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
@@ -52,9 +50,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
var channelIdPrefix = GetFullChannelIdPrefix(info);
|
||||
|
||||
var result = await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result.Cast<ChannelInfo>().ToList();
|
||||
return await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
||||
@@ -73,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
return Task.FromResult(list);
|
||||
}
|
||||
|
||||
private string[] _disallowedSharedStreamExtensions = new string[]
|
||||
private static readonly string[] _disallowedSharedStreamExtensions = new string[]
|
||||
{
|
||||
".mkv",
|
||||
".mp4",
|
||||
@@ -88,9 +84,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
if (tunerCount > 0)
|
||||
{
|
||||
var tunerHostId = info.Id;
|
||||
var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (liveStreams.Count >= tunerCount)
|
||||
if (liveStreams.Count() >= tunerCount)
|
||||
{
|
||||
throw new LiveTvConflictException("M3U simultaneous stream limit has been reached.");
|
||||
}
|
||||
@@ -98,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
var sources = await GetChannelStreamMediaSources(info, channelInfo, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var mediaSource = sources.First();
|
||||
var mediaSource = sources[0];
|
||||
|
||||
if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,6 @@ using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
@@ -62,12 +61,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
return Task.FromResult((Stream)File.OpenRead(url));
|
||||
}
|
||||
|
||||
const string ExtInfPrefix = "#EXTINF:";
|
||||
private const string ExtInfPrefix = "#EXTINF:";
|
||||
|
||||
private List<ChannelInfo> GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId)
|
||||
{
|
||||
var channels = new List<ChannelInfo>();
|
||||
string line;
|
||||
string extInf = "";
|
||||
string extInf = string.Empty;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
@@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
channel.Path = line;
|
||||
channels.Add(channel);
|
||||
extInf = "";
|
||||
extInf = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +110,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
private ChannelInfo GetChannelnfo(string extInf, string tunerHostId, string mediaUrl)
|
||||
{
|
||||
var channel = new ChannelInfo();
|
||||
channel.TunerHostId = tunerHostId;
|
||||
var channel = new ChannelInfo()
|
||||
{
|
||||
TunerHostId = tunerHostId
|
||||
};
|
||||
|
||||
extInf = extInf.Trim();
|
||||
|
||||
@@ -137,13 +139,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
channelIdValues.Add(channelId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tvgId))
|
||||
{
|
||||
channelIdValues.Add(tvgId);
|
||||
}
|
||||
|
||||
if (channelIdValues.Count > 0)
|
||||
{
|
||||
channel.Id = string.Join("_", channelIdValues.ToArray());
|
||||
channel.Id = string.Join("_", channelIdValues);
|
||||
}
|
||||
|
||||
return channel;
|
||||
@@ -152,7 +156,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
private string GetChannelNumber(string extInf, Dictionary<string, string> attributes, string mediaUrl)
|
||||
{
|
||||
var nameParts = extInf.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null;
|
||||
var nameInExtInf = nameParts.Length > 1 ? nameParts[nameParts.Length - 1].Trim() : null;
|
||||
|
||||
string numberString = null;
|
||||
string attributeValue;
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
{
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"Application": "Application",
|
||||
"Artists": "Artists",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"Books": "Books",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"Channels": "Channels",
|
||||
"ChapterNameValue": "Chapter {0}",
|
||||
"Collections": "Collections",
|
||||
"DeviceOfflineWithName": "{0} has disconnected",
|
||||
"DeviceOnlineWithName": "{0} is connected",
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Favorites",
|
||||
"Folders": "Folders",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderContinueWatching": "Continue Watching",
|
||||
"HeaderFavoriteAlbums": "Favorite Albums",
|
||||
"HeaderFavoriteArtists": "Favorite Artists",
|
||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
||||
"HeaderFavoriteShows": "Favorite Shows",
|
||||
"HeaderFavoriteSongs": "Favorite Songs",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Next Up",
|
||||
"HeaderRecordingGroups": "Recording Groups",
|
||||
"HomeVideos": "Home videos",
|
||||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"Latest": "Latest",
|
||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||
"MixedContent": "Mixed content",
|
||||
"Movies": "Movies",
|
||||
"Music": "Music",
|
||||
"MusicVideos": "Music videos",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameSeasonNumber": "Season {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Application update available",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
||||
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
|
||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionVideoPlayback": "Video playback started",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
|
||||
"Photos": "Photos",
|
||||
"Playlists": "Playlists",
|
||||
"Albums": "Álbumes",
|
||||
"AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}",
|
||||
"Application": "Aplicación",
|
||||
"Artists": "Artistas",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado correctamente",
|
||||
"Books": "Libros",
|
||||
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
|
||||
"Channels": "Canales",
|
||||
"ChapterNameValue": "Capítulo {0}",
|
||||
"Collections": "Colecciones",
|
||||
"DeviceOfflineWithName": "{0} se ha desconectado",
|
||||
"DeviceOnlineWithName": "{0} está conectado",
|
||||
"FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión desde {0}",
|
||||
"Favorites": "Favoritos",
|
||||
"Folders": "Carpetas",
|
||||
"Genres": "Géneros",
|
||||
"HeaderAlbumArtists": "Artistas de álbumes",
|
||||
"HeaderCameraUploads": "Subidas de cámara",
|
||||
"HeaderContinueWatching": "Continuar viendo",
|
||||
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
||||
"HeaderFavoriteArtists": "Artistas favoritos",
|
||||
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
||||
"HeaderFavoriteShows": "Programas favoritos",
|
||||
"HeaderFavoriteSongs": "Canciones favoritas",
|
||||
"HeaderLiveTV": "TV en vivo",
|
||||
"HeaderNextUp": "Continuar Viendo",
|
||||
"HeaderRecordingGroups": "Grupos de grabación",
|
||||
"HomeVideos": "Videos caseros",
|
||||
"Inherit": "Heredar",
|
||||
"ItemAddedWithName": "{0} se ha añadido a la biblioteca",
|
||||
"ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca",
|
||||
"LabelIpAddressValue": "Dirección IP: {0}",
|
||||
"LabelRunningTimeValue": "Tiempo de funcionamiento: {0}",
|
||||
"Latest": "Últimos",
|
||||
"MessageApplicationUpdated": "El servidor Jellyfin fue actualizado",
|
||||
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Fue actualizada la sección {0} de la configuración del servidor",
|
||||
"MessageServerConfigurationUpdated": "Fue actualizada la configuración del servidor",
|
||||
"MixedContent": "Contenido mixto",
|
||||
"Movies": "Películas",
|
||||
"Music": "Música",
|
||||
"MusicVideos": "Videos musicales",
|
||||
"NameInstallFailed": "{0} error de instalación",
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameSeasonUnknown": "Temporada desconocida",
|
||||
"NewVersionIsAvailable": "Disponible una nueva versión de Jellyfin para descargar.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada",
|
||||
"NotificationOptionAudioPlayback": "Se inició la reproducción de audio",
|
||||
"NotificationOptionAudioPlaybackStopped": "Se detuvo la reproducción de audio",
|
||||
"NotificationOptionCameraImageUploaded": "Imagen de la cámara cargada",
|
||||
"NotificationOptionInstallationFailed": "Error de instalación",
|
||||
"NotificationOptionNewLibraryContent": "Nuevo contenido añadido",
|
||||
"NotificationOptionPluginError": "Error en plugin",
|
||||
"NotificationOptionPluginInstalled": "Plugin instalado",
|
||||
"NotificationOptionPluginUninstalled": "Plugin desinstalado",
|
||||
"NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada",
|
||||
"NotificationOptionServerRestartRequired": "Se requiere reinicio del servidor",
|
||||
"NotificationOptionTaskFailed": "Error de tarea programada",
|
||||
"NotificationOptionUserLockedOut": "Usuario bloqueado",
|
||||
"NotificationOptionVideoPlayback": "Se inició la reproducción de video",
|
||||
"NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida",
|
||||
"Photos": "Fotos",
|
||||
"Playlists": "Listas de reproducción",
|
||||
"Plugin": "Plugin",
|
||||
"PluginInstalledWithName": "{0} was installed",
|
||||
"PluginUninstalledWithName": "{0} was uninstalled",
|
||||
"PluginUpdatedWithName": "{0} was updated",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} failed",
|
||||
"ScheduledTaskStartedWithName": "{0} started",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"PluginInstalledWithName": "{0} fue instalado",
|
||||
"PluginUninstalledWithName": "{0} fue desinstalado",
|
||||
"PluginUpdatedWithName": "{0} fue actualizado",
|
||||
"ProviderValue": "Proveedor: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} falló",
|
||||
"ScheduledTaskStartedWithName": "{0} iniciada",
|
||||
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
|
||||
"Shows": "Series",
|
||||
"Songs": "Songs",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
|
||||
"Songs": "Canciones",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
||||
"Sync": "Sync",
|
||||
"System": "System",
|
||||
"TvShows": "TV Shows",
|
||||
"User": "User",
|
||||
"UserCreatedWithName": "User {0} has been created",
|
||||
"UserDeletedWithName": "User {0} has been deleted",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserOfflineFromDevice": "{0} has disconnected from {1}",
|
||||
"UserOnlineFromDevice": "{0} is online from {1}",
|
||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}",
|
||||
"SubtitlesDownloadedForItem": "Descargar subtítulos para {0}",
|
||||
"Sync": "Sincronizar",
|
||||
"System": "Sistema",
|
||||
"TvShows": "Series de TV",
|
||||
"User": "Usuario",
|
||||
"UserCreatedWithName": "El usuario {0} ha sido creado",
|
||||
"UserDeletedWithName": "El usuario {0} ha sido borrado",
|
||||
"UserDownloadingItemWithValues": "{0} está descargando {1}",
|
||||
"UserLockedOutWithName": "El usuario {0} ha sido bloqueado",
|
||||
"UserOfflineFromDevice": "{0} se ha desconectado de {1}",
|
||||
"UserOnlineFromDevice": "{0} está en línea desde {1}",
|
||||
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
|
||||
"UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia",
|
||||
"ValueSpecialEpisodeName": "Especial - {0}",
|
||||
"VersionNumber": "Versión {0}"
|
||||
}
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
{
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"AppDeviceValues": "Application : {0}, Appareil : {1}",
|
||||
"Application": "Application",
|
||||
"Artists": "Artists",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"Books": "Books",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"Channels": "Channels",
|
||||
"ChapterNameValue": "Chapter {0}",
|
||||
"Artists": "Artistes",
|
||||
"AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès",
|
||||
"Books": "Livres",
|
||||
"CameraImageUploadedFrom": "Une nouvelle image de caméra a été téléchargée depuis {0}",
|
||||
"Channels": "Chaînes",
|
||||
"ChapterNameValue": "Chapitre {0}",
|
||||
"Collections": "Collections",
|
||||
"DeviceOfflineWithName": "{0} has disconnected",
|
||||
"DeviceOnlineWithName": "{0} is connected",
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Favorites",
|
||||
"Folders": "Folders",
|
||||
"DeviceOfflineWithName": "{0} s'est déconnecté",
|
||||
"DeviceOnlineWithName": "{0} est connecté",
|
||||
"FailedLoginAttemptWithUserName": "Échec d'une tentative de connexion de {0}",
|
||||
"Favorites": "Favoris",
|
||||
"Folders": "Dossiers",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderAlbumArtists": "Artistes de l'album",
|
||||
"HeaderCameraUploads": "Photos transférées",
|
||||
"HeaderContinueWatching": "Continuer à regarder",
|
||||
"HeaderFavoriteAlbums": "Favorite Albums",
|
||||
"HeaderFavoriteArtists": "Favorite Artists",
|
||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
||||
"HeaderFavoriteShows": "Favorite Shows",
|
||||
"HeaderFavoriteSongs": "Favorite Songs",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderFavoriteAlbums": "Albums favoris",
|
||||
"HeaderFavoriteArtists": "Artistes favoris",
|
||||
"HeaderFavoriteEpisodes": "Épisodes favoris",
|
||||
"HeaderFavoriteShows": "Séries favorites",
|
||||
"HeaderFavoriteSongs": "Chansons favorites",
|
||||
"HeaderLiveTV": "TV en direct",
|
||||
"HeaderNextUp": "À Suivre",
|
||||
"HeaderRecordingGroups": "Recording Groups",
|
||||
"HomeVideos": "Home videos",
|
||||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"Latest": "Latest",
|
||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||
"MixedContent": "Mixed content",
|
||||
"Movies": "Movies",
|
||||
"Music": "Music",
|
||||
"MusicVideos": "Music videos",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameSeasonNumber": "Season {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Application update available",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
||||
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
|
||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionVideoPlayback": "Video playback started",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
|
||||
"HeaderRecordingGroups": "Groupes d'enregistrements",
|
||||
"HomeVideos": "Vidéos personnelles",
|
||||
"Inherit": "Hériter",
|
||||
"ItemAddedWithName": "{0} a été ajouté à la médiathèque",
|
||||
"ItemRemovedWithName": "{0} a été supprimé de la médiathèque",
|
||||
"LabelIpAddressValue": "Adresse IP : {0}",
|
||||
"LabelRunningTimeValue": "Durée : {0}",
|
||||
"Latest": "Derniers",
|
||||
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
|
||||
"MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers la version {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
|
||||
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
|
||||
"MixedContent": "Contenu mixte",
|
||||
"Movies": "Films",
|
||||
"Music": "Musique",
|
||||
"MusicVideos": "Vidéos musicales",
|
||||
"NameInstallFailed": "{0} échec d'installation",
|
||||
"NameSeasonNumber": "Saison {0}",
|
||||
"NameSeasonUnknown": "Saison Inconnue",
|
||||
"NewVersionIsAvailable": "Une nouvelle version du serveur Jellyfin est disponible au téléchargement.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Mise à jour de l'application disponible",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Mise à jour de l'application installée",
|
||||
"NotificationOptionAudioPlayback": "Lecture audio démarrée",
|
||||
"NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée",
|
||||
"NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée",
|
||||
"NotificationOptionInstallationFailed": "Échec d'installation",
|
||||
"NotificationOptionNewLibraryContent": "Nouveau contenu ajouté",
|
||||
"NotificationOptionPluginError": "Erreur d'extension",
|
||||
"NotificationOptionPluginInstalled": "Extension installée",
|
||||
"NotificationOptionPluginUninstalled": "Extension désinstallée",
|
||||
"NotificationOptionPluginUpdateInstalled": "Mise à jour d'extension installée",
|
||||
"NotificationOptionServerRestartRequired": "Un redémarrage du serveur est requis",
|
||||
"NotificationOptionTaskFailed": "Échec de tâche planifiée",
|
||||
"NotificationOptionUserLockedOut": "Utilisateur verrouillé",
|
||||
"NotificationOptionVideoPlayback": "Lecture vidéo démarrée",
|
||||
"NotificationOptionVideoPlaybackStopped": "Lecture vidéo arrêtée",
|
||||
"Photos": "Photos",
|
||||
"Playlists": "Playlists",
|
||||
"Plugin": "Plugin",
|
||||
"PluginInstalledWithName": "{0} was installed",
|
||||
"PluginUninstalledWithName": "{0} was uninstalled",
|
||||
"PluginUpdatedWithName": "{0} was updated",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} failed",
|
||||
"ScheduledTaskStartedWithName": "{0} started",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"Shows": "Series",
|
||||
"Songs": "Songs",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
|
||||
"Playlists": "Listes de lecture",
|
||||
"Plugin": "Extension",
|
||||
"PluginInstalledWithName": "{0} a été installé",
|
||||
"PluginUninstalledWithName": "{0} a été désinstallé",
|
||||
"PluginUpdatedWithName": "{0} a été mis à jour",
|
||||
"ProviderValue": "Fournisseur : {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} a échoué",
|
||||
"ScheduledTaskStartedWithName": "{0} a commencé",
|
||||
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
|
||||
"Shows": "Émissions",
|
||||
"Songs": "Chansons",
|
||||
"StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
||||
"Sync": "Sync",
|
||||
"System": "System",
|
||||
"TvShows": "TV Shows",
|
||||
"User": "User",
|
||||
"UserCreatedWithName": "User {0} has been created",
|
||||
"UserDeletedWithName": "User {0} has been deleted",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserOfflineFromDevice": "{0} has disconnected from {1}",
|
||||
"UserOnlineFromDevice": "{0} is online from {1}",
|
||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}",
|
||||
"SubtitlesDownloadedForItem": "Les sous-titres de {0} ont été téléchargés",
|
||||
"Sync": "Synchroniser",
|
||||
"System": "Système",
|
||||
"TvShows": "Séries Télé",
|
||||
"User": "Utilisateur",
|
||||
"UserCreatedWithName": "L'utilisateur {0} a été créé",
|
||||
"UserDeletedWithName": "L'utilisateur {0} a été supprimé",
|
||||
"UserDownloadingItemWithValues": "{0} est en train de télécharger {1}",
|
||||
"UserLockedOutWithName": "L'utilisateur {0} a été verrouillé",
|
||||
"UserOfflineFromDevice": "{0} s'est déconnecté depuis {1}",
|
||||
"UserOnlineFromDevice": "{0} s'est connecté depuis {1}",
|
||||
"UserPasswordChangedWithName": "Le mot de passe pour l'utilisateur {0} a été modifié",
|
||||
"UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
|
||||
"ValueSpecialEpisodeName": "Spécial - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"NameInstallFailed": "{0} échec d'installation",
|
||||
"NameSeasonNumber": "Saison {0}",
|
||||
"NameSeasonUnknown": "Saison Inconnue",
|
||||
"NewVersionIsAvailable": "Une nouvelle version d'Jellyfin Serveur est disponible au téléchargement.",
|
||||
"NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Mise à jour de l'application disponible",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Mise à jour de l'application installée",
|
||||
"NotificationOptionAudioPlayback": "Lecture audio démarrée",
|
||||
@@ -89,7 +89,7 @@
|
||||
"UserOnlineFromDevice": "{0} s'est connecté depuis {1}",
|
||||
"UserPasswordChangedWithName": "Le mot de passe pour l'utilisateur {0} a été modifié",
|
||||
"UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} est entrain de lire {1} sur {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre librairie",
|
||||
"ValueSpecialEpisodeName": "Spécial - {0}",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"Albums": "Albums",
|
||||
"Albums": "אלבומים",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"Application": "Application",
|
||||
"Artists": "Artists",
|
||||
"Application": "אפליקציה",
|
||||
"Artists": "אמנים",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"Books": "ספרים",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
|
||||
@@ -34,17 +34,17 @@
|
||||
"LabelRunningTimeValue": "Durata: {0}",
|
||||
"Latest": "Più recenti",
|
||||
"MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata",
|
||||
"MessageServerConfigurationUpdated": "La configurazione del server è stata aggiornata",
|
||||
"MixedContent": "Contenuto misto",
|
||||
"Movies": "Film",
|
||||
"Music": "Musica",
|
||||
"MusicVideos": "Video musicali",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameInstallFailed": "{0} installazione fallita",
|
||||
"NameSeasonNumber": "Stagione {0}",
|
||||
"NameSeasonUnknown": "Stagione sconosciuto",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato",
|
||||
"NotificationOptionAudioPlayback": "La riproduzione audio è iniziata",
|
||||
@@ -70,12 +70,12 @@
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} fallito",
|
||||
"ScheduledTaskStartedWithName": "{0} avviati",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"ServerNameNeedsToBeRestarted": "{0} deve essere riavviato",
|
||||
"Shows": "Programmi",
|
||||
"Songs": "Canzoni",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.",
|
||||
"SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}",
|
||||
"SubtitlesDownloadedForItem": "Sottotitoli scaricati per {0}",
|
||||
"Sync": "Sincronizza",
|
||||
"System": "Sistema",
|
||||
@@ -91,7 +91,7 @@
|
||||
"UserPolicyUpdatedWithName": "La politica dell'utente è stata aggiornata per {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
|
||||
"ValueSpecialEpisodeName": "Speciale - {0}",
|
||||
"VersionNumber": "Versione {0}"
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
"AppDeviceValues": "Qoldanba: {0}, Qurylǵy: {1}",
|
||||
"Application": "Qoldanba",
|
||||
"Artists": "Oryndaýshylar",
|
||||
"AuthenticationSucceededWithUserName": "{0} túpnusqalyǵyn rastalýy sátti",
|
||||
"AuthenticationSucceededWithUserName": "{0} túpnusqalyq rastalýy sátti aıaqtaldy",
|
||||
"Books": "Kitaptar",
|
||||
"CameraImageUploadedFrom": "Jańa sýret {0} kamerasynan júktep alyndy",
|
||||
"CameraImageUploadedFrom": "{0} kamerasynan jańa sýret júktep alyndy",
|
||||
"Channels": "Arnalar",
|
||||
"ChapterNameValue": "{0}-sahna",
|
||||
"Collections": "Jıyntyqtar",
|
||||
"DeviceOfflineWithName": "{0} ajyratylǵan",
|
||||
"DeviceOnlineWithName": "{0} qosylǵan",
|
||||
"FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz",
|
||||
"FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz aıaqtaldy",
|
||||
"Favorites": "Tańdaýlylar",
|
||||
"Folders": "Qaltalar",
|
||||
"Genres": "Janrlar",
|
||||
@@ -28,13 +28,13 @@
|
||||
"HeaderRecordingGroups": "Jazba toptary",
|
||||
"HomeVideos": "Úılik beıneler",
|
||||
"Inherit": "Muraǵa ıelený",
|
||||
"ItemAddedWithName": "{0} tasyǵyshhanaǵa ústelindi",
|
||||
"ItemAddedWithName": "{0} tasyǵyshhanaǵa ústeldi",
|
||||
"ItemRemovedWithName": "{0} tasyǵyshhanadan alastaldy",
|
||||
"LabelIpAddressValue": "IP-mekenjaıy: {0}",
|
||||
"LabelRunningTimeValue": "Oınatý ýaqyty: {0}",
|
||||
"Latest": "Eń keıingi",
|
||||
"MessageApplicationUpdated": "Jellyfin Serveri jańartyldy",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Serveri {0} deńgeıge jańartyldy",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Serveri {0} nusqasyna jańartyldy",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server teńsheliminiń {0} bólimi jańartyldy",
|
||||
"MessageServerConfigurationUpdated": "Server teńshelimi jańartyldy",
|
||||
"MixedContent": "Aralas mazmun",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "Artistas",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
||||
"Books": "Livros",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"CameraImageUploadedFrom": "Uma nova imagem da câmera foi submetida de {0}",
|
||||
"Channels": "Canais",
|
||||
"ChapterNameValue": "Capítulo {0}",
|
||||
"Collections": "Coletâneas",
|
||||
@@ -30,21 +30,21 @@
|
||||
"Inherit": "Herdar",
|
||||
"ItemAddedWithName": "{0} foi adicionado à biblioteca",
|
||||
"ItemRemovedWithName": "{0} foi removido da biblioteca",
|
||||
"LabelIpAddressValue": "Endereço ip: {0}",
|
||||
"LabelIpAddressValue": "Endereço IP: {0}",
|
||||
"LabelRunningTimeValue": "Tempo de execução: {0}",
|
||||
"Latest": "Recente",
|
||||
"MessageApplicationUpdated": "O servidor Jellyfin foi atualizado",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageApplicationUpdatedTo": "O Servidor Jellyfin foi atualizado para {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "A seção {0} da configuração do servidor foi atualizada",
|
||||
"MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada",
|
||||
"MixedContent": "Conteúdo misto",
|
||||
"Movies": "Filmes",
|
||||
"Music": "Música",
|
||||
"MusicVideos": "Vídeos musicais",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameInstallFailed": "A instalação de {0} falhou",
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameSeasonUnknown": "Temporada Desconhecida",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NewVersionIsAvailable": "Uma nova versão do servidor Jellyfin está disponível para download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Atualização de aplicativo disponível",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Atualização de aplicativo instalada",
|
||||
"NotificationOptionAudioPlayback": "Reprodução de áudio iniciada",
|
||||
@@ -70,12 +70,12 @@
|
||||
"ProviderValue": "Provedor: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} falhou",
|
||||
"ScheduledTaskStartedWithName": "{0} iniciada",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"ServerNameNeedsToBeRestarted": "O servidor {0} precisa ser reiniciado",
|
||||
"Shows": "Séries",
|
||||
"Songs": "Músicas",
|
||||
"StartupEmbyServerIsLoading": "O Servidor Jellyfin está carregando. Por favor tente novamente em breve.",
|
||||
"SubtitleDownloadFailureForItem": "Download de legendas falhou para {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Houve um problema ao baixar as legendas de {0} para {1}",
|
||||
"SubtitlesDownloadedForItem": "Legendas baixadas para {0}",
|
||||
"Sync": "Sincronizar",
|
||||
"System": "Sistema",
|
||||
@@ -91,7 +91,7 @@
|
||||
"UserPolicyUpdatedWithName": "A política de usuário foi atualizada para {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} iniciou a reprodução de {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} parou de reproduzir {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueHasBeenAddedToLibrary": "{0} foi adicionado a sua biblioteca",
|
||||
"ValueSpecialEpisodeName": "Especial - {0}",
|
||||
"VersionNumber": "Versão {0}"
|
||||
}
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
{
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"Application": "Application",
|
||||
"Artists": "Artists",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"Books": "Books",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"Channels": "Channels",
|
||||
"ChapterNameValue": "Chapter {0}",
|
||||
"Collections": "Collections",
|
||||
"Albums": "Albumi",
|
||||
"AppDeviceValues": "Aplikacija: {0}, Naprava: {1}",
|
||||
"Application": "Aplikacija",
|
||||
"Artists": "Izvajalci",
|
||||
"AuthenticationSucceededWithUserName": "{0} preverjanje uspešno",
|
||||
"Books": "Knjige",
|
||||
"CameraImageUploadedFrom": "Nova fotografija je bila naložena z {0}",
|
||||
"Channels": "Kanali",
|
||||
"ChapterNameValue": "Poglavje {0}",
|
||||
"Collections": "Zbirke",
|
||||
"DeviceOfflineWithName": "{0} has disconnected",
|
||||
"DeviceOnlineWithName": "{0} is connected",
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"Favorites": "Favorites",
|
||||
"Folders": "Folders",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderContinueWatching": "Continue Watching",
|
||||
"HeaderFavoriteAlbums": "Favorite Albums",
|
||||
"HeaderFavoriteArtists": "Favorite Artists",
|
||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
||||
"HeaderFavoriteShows": "Favorite Shows",
|
||||
"HeaderFavoriteSongs": "Favorite Songs",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Next Up",
|
||||
"HeaderRecordingGroups": "Recording Groups",
|
||||
"HomeVideos": "Home videos",
|
||||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"Latest": "Latest",
|
||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"DeviceOnlineWithName": "{0} je povezan",
|
||||
"FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}",
|
||||
"Favorites": "Priljubljeni",
|
||||
"Folders": "Mape",
|
||||
"Genres": "Zvrsti",
|
||||
"HeaderAlbumArtists": "Izvajalci albuma",
|
||||
"HeaderCameraUploads": "Posnetki kamere",
|
||||
"HeaderContinueWatching": "Nadaljuj gledanje",
|
||||
"HeaderFavoriteAlbums": "Priljubljeni albumi",
|
||||
"HeaderFavoriteArtists": "Priljubljeni izvajalci",
|
||||
"HeaderFavoriteEpisodes": "Priljubljene epizode",
|
||||
"HeaderFavoriteShows": "Priljubljene serije",
|
||||
"HeaderFavoriteSongs": "Priljubljene pesmi",
|
||||
"HeaderLiveTV": "TV v živo",
|
||||
"HeaderNextUp": "Sledi",
|
||||
"HeaderRecordingGroups": "Zbirke posnetkov",
|
||||
"HomeVideos": "Domači posnetki",
|
||||
"Inherit": "Podeduj",
|
||||
"ItemAddedWithName": "{0} je dodan v knjižnico",
|
||||
"ItemRemovedWithName": "{0} je bil odstranjen iz knjižnice",
|
||||
"LabelIpAddressValue": "IP naslov: {0}",
|
||||
"LabelRunningTimeValue": "Čas trajanja: {0}",
|
||||
"Latest": "Najnovejše",
|
||||
"MessageApplicationUpdated": "Jellyfin strežnik je bil posodobljen",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin strežnik je bil posodobljen na {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||
"MixedContent": "Mixed content",
|
||||
"Movies": "Movies",
|
||||
"Music": "Music",
|
||||
"MusicVideos": "Music videos",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameSeasonNumber": "Season {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Application update available",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||
"NotificationOptionInstallationFailed": "Installation failure",
|
||||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionPluginError": "Plugin failure",
|
||||
"NotificationOptionPluginInstalled": "Plugin installed",
|
||||
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
|
||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||
"MessageServerConfigurationUpdated": "Nastavitve strežnika so bile posodobljene",
|
||||
"MixedContent": "Razne vsebine",
|
||||
"Movies": "Filmi",
|
||||
"Music": "Glasba",
|
||||
"MusicVideos": "Glasbeni posnetki",
|
||||
"NameInstallFailed": "{0} namestitev neuspešna",
|
||||
"NameSeasonNumber": "Sezona {0}",
|
||||
"NameSeasonUnknown": "Season neznana",
|
||||
"NewVersionIsAvailable": "Nova razničica Jellyfin strežnika je na voljo za prenos.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Posodobitev aplikacije je na voljo",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Posodobitev aplikacije je bila nameščena",
|
||||
"NotificationOptionAudioPlayback": "Predvajanje zvoka začeto",
|
||||
"NotificationOptionAudioPlaybackStopped": "Predvajanje zvoka zaustavljeno",
|
||||
"NotificationOptionCameraImageUploaded": "Posnetek kamere naložen",
|
||||
"NotificationOptionInstallationFailed": "Napaka pri nameščanju",
|
||||
"NotificationOptionNewLibraryContent": "Nove vsebine dodane",
|
||||
"NotificationOptionPluginError": "Napaka dodatka",
|
||||
"NotificationOptionPluginInstalled": "Dodatek nameščen",
|
||||
"NotificationOptionPluginUninstalled": "Dodatek odstranjen",
|
||||
"NotificationOptionPluginUpdateInstalled": "Posodobitev dodatka nameščena",
|
||||
"NotificationOptionServerRestartRequired": "Potreben je ponovni zagon strežnika",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionVideoPlayback": "Video playback started",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
"Application": "Application",
|
||||
"Artists": "Artists",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"Books": "Books",
|
||||
"Albums": "Albümler",
|
||||
"AppDeviceValues": "Uygulama: {0}, Aygıt: {1}",
|
||||
"Application": "Uygulama",
|
||||
"Artists": "Sanatçılar",
|
||||
"AuthenticationSucceededWithUserName": "{0} başarı ile giriş yaptı",
|
||||
"Books": "Kitaplar",
|
||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
||||
"Channels": "Channels",
|
||||
"Channels": "Kanallar",
|
||||
"ChapterNameValue": "Chapter {0}",
|
||||
"Collections": "Collections",
|
||||
"DeviceOfflineWithName": "{0} has disconnected",
|
||||
@@ -17,8 +17,8 @@
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
"HeaderCameraUploads": "Camera Uploads",
|
||||
"HeaderContinueWatching": "Continue Watching",
|
||||
"HeaderFavoriteAlbums": "Favorite Albums",
|
||||
"HeaderContinueWatching": "İzlemeye Devam Et",
|
||||
"HeaderFavoriteAlbums": "Favori Albümler",
|
||||
"HeaderFavoriteArtists": "Favorite Artists",
|
||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
||||
"HeaderFavoriteShows": "Favori Showlar",
|
||||
@@ -30,21 +30,21 @@
|
||||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
"ItemRemovedWithName": "{0} was removed from the library",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"LabelIpAddressValue": "Ip adresi: {0}",
|
||||
"LabelRunningTimeValue": "Çalışma süresi: {0}",
|
||||
"Latest": "Latest",
|
||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
||||
"MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
||||
"MixedContent": "Mixed content",
|
||||
"Movies": "Movies",
|
||||
"Music": "Music",
|
||||
"MusicVideos": "Music videos",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameSeasonNumber": "Season {0}",
|
||||
"NameSeasonUnknown": "Season Unknown",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
"Music": "Müzik",
|
||||
"MusicVideos": "Müzik videoları",
|
||||
"NameInstallFailed": "{0} kurulum başarısız",
|
||||
"NameSeasonNumber": "Sezon {0}",
|
||||
"NameSeasonUnknown": "Bilinmeyen Sezon",
|
||||
"NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Application update available",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||
|
||||
@@ -34,14 +34,14 @@
|
||||
"LabelRunningTimeValue": "运行时间:{0}",
|
||||
"Latest": "最新",
|
||||
"MessageApplicationUpdated": "Jellyfin 服务器已更新",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server 的版本已更新为 {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "服务器配置 {0} 部分已更新",
|
||||
"MessageServerConfigurationUpdated": "服务器配置已更新",
|
||||
"MixedContent": "混合内容",
|
||||
"Movies": "电影",
|
||||
"Music": "音乐",
|
||||
"MusicVideos": "音乐视频",
|
||||
"NameInstallFailed": "{0} installation failed",
|
||||
"NameInstallFailed": "{0} 安装失败",
|
||||
"NameSeasonNumber": "季 {0}",
|
||||
"NameSeasonUnknown": "未知季",
|
||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
||||
@@ -70,7 +70,7 @@
|
||||
"ProviderValue": "提供商:{0}",
|
||||
"ScheduledTaskFailedWithName": "{0} 已失败",
|
||||
"ScheduledTaskStartedWithName": "{0} 已开始",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"ServerNameNeedsToBeRestarted": "{0} 需要重新启动",
|
||||
"Shows": "节目",
|
||||
"Songs": "歌曲",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin 服务器加载中。请稍后再试。",
|
||||
|
||||
@@ -11,7 +11,6 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -35,7 +34,6 @@ namespace Emby.Server.Implementations.Localization
|
||||
private readonly Dictionary<string, Dictionary<string, ParentalRating>> _allParentalRatings =
|
||||
new Dictionary<string, Dictionary<string, ParentalRating>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly ILogger _logger;
|
||||
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
|
||||
@@ -44,40 +42,38 @@ namespace Emby.Server.Implementations.Localization
|
||||
/// Initializes a new instance of the <see cref="LocalizationManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="loggerFactory">The logger factory</param>
|
||||
public LocalizationManager(
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
IJsonSerializer jsonSerializer,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_configurationManager = configurationManager;
|
||||
_fileSystem = fileSystem;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_logger = loggerFactory.CreateLogger(nameof(LocalizationManager));
|
||||
}
|
||||
|
||||
public async Task LoadAll()
|
||||
{
|
||||
const string ratingsResource = "Emby.Server.Implementations.Localization.Ratings.";
|
||||
const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings.";
|
||||
|
||||
// Extract from the assembly
|
||||
foreach (var resource in _assembly.GetManifestResourceNames())
|
||||
{
|
||||
if (!resource.StartsWith(ratingsResource))
|
||||
if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string countryCode = resource.Substring(ratingsResource.Length, 2);
|
||||
string countryCode = resource.Substring(RatingsResource.Length, 2);
|
||||
var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
using (var str = _assembly.GetManifestResourceStream(resource))
|
||||
using (var reader = new StreamReader(str))
|
||||
{
|
||||
string line;
|
||||
while ((line = await reader.ReadLineAsync()) != null)
|
||||
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
@@ -102,7 +98,7 @@ namespace Emby.Server.Implementations.Localization
|
||||
_allParentalRatings[countryCode] = dict;
|
||||
}
|
||||
|
||||
await LoadCultures();
|
||||
await LoadCultures().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string NormalizeFormKD(string text)
|
||||
@@ -121,14 +117,14 @@ namespace Emby.Server.Implementations.Localization
|
||||
{
|
||||
List<CultureDto> list = new List<CultureDto>();
|
||||
|
||||
const string path = "Emby.Server.Implementations.Localization.iso6392.txt";
|
||||
const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt";
|
||||
|
||||
using (var stream = _assembly.GetManifestResourceStream(path))
|
||||
using (var stream = _assembly.GetManifestResourceStream(ResourcePath))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = await reader.ReadLineAsync();
|
||||
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
@@ -154,11 +150,11 @@ namespace Emby.Server.Implementations.Localization
|
||||
string[] threeletterNames;
|
||||
if (string.IsNullOrWhiteSpace(parts[1]))
|
||||
{
|
||||
threeletterNames = new [] { parts[0] };
|
||||
threeletterNames = new[] { parts[0] };
|
||||
}
|
||||
else
|
||||
{
|
||||
threeletterNames = new [] { parts[0], parts[1] };
|
||||
threeletterNames = new[] { parts[0], parts[1] };
|
||||
}
|
||||
|
||||
list.Add(new CultureDto
|
||||
@@ -218,6 +214,7 @@ namespace Emby.Server.Implementations.Localization
|
||||
/// Gets the ratings.
|
||||
/// </summary>
|
||||
/// <param name="countryCode">The country code.</param>
|
||||
/// <returns>The ratings</returns>
|
||||
private Dictionary<string, ParentalRating> GetRatings(string countryCode)
|
||||
{
|
||||
_allParentalRatings.TryGetValue(countryCode, out var value);
|
||||
@@ -227,9 +224,12 @@ namespace Emby.Server.Implementations.Localization
|
||||
|
||||
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Gets the rating level.
|
||||
/// </summary>
|
||||
/// <param name="rating">Rating field</param>
|
||||
/// <returns>The rating level</returns>>
|
||||
public int? GetRatingLevel(string rating)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rating))
|
||||
@@ -301,6 +301,7 @@ namespace Emby.Server.Implementations.Localization
|
||||
{
|
||||
culture = _configurationManager.Configuration.UICulture;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
culture = DefaultCulture;
|
||||
@@ -346,8 +347,8 @@ namespace Emby.Server.Implementations.Localization
|
||||
|
||||
var namespaceName = GetType().Namespace + "." + prefix;
|
||||
|
||||
await CopyInto(dictionary, namespaceName + "." + baseFilename);
|
||||
await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture));
|
||||
await CopyInto(dictionary, namespaceName + "." + baseFilename).ConfigureAwait(false);
|
||||
await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)).ConfigureAwait(false);
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
@@ -359,7 +360,7 @@ namespace Emby.Server.Implementations.Localization
|
||||
// If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain
|
||||
if (stream != null)
|
||||
{
|
||||
var dict = await _jsonSerializer.DeserializeFromStreamAsync<Dictionary<string, string>>(stream);
|
||||
var dict = await _jsonSerializer.DeserializeFromStreamAsync<Dictionary<string, string>>(stream).ConfigureAwait(false);
|
||||
|
||||
foreach (var key in dict.Keys)
|
||||
{
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Emby.Server.Implementations.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Correclty implements the <see cref="IDisposable"/> interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an <see cref="IsDisposed"/> property.
|
||||
/// </summary>
|
||||
public abstract class DisposableManagedObjectBase : IDisposable
|
||||
{
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Override this method and dispose any objects you own the lifetime of if disposing is true;
|
||||
/// </summary>
|
||||
/// <param name="disposing">True if managed objects should be disposed, if false, only unmanaged resources should be released.</param>
|
||||
protected abstract void Dispose(bool disposing);
|
||||
|
||||
|
||||
//TODO Remove and reimplement using the IsDisposed property directly.
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ObjectDisposedException"/> if the <see cref="IsDisposed"/> property is true.
|
||||
/// </summary>
|
||||
/// <seealso cref="IsDisposed"/>
|
||||
/// <exception cref="ObjectDisposedException">Thrown if the <see cref="IsDisposed"/> property is true.</exception>
|
||||
/// <seealso cref="Dispose()"/>
|
||||
protected virtual void ThrowIfDisposed()
|
||||
{
|
||||
if (IsDisposed) throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Sets or returns a boolean indicating whether or not this instance has been disposed.
|
||||
/// </summary>
|
||||
/// <seealso cref="Dispose()"/>
|
||||
public bool IsDisposed
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this object instance and all internally managed resources.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Sets the <see cref="IsDisposed"/> property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IsDisposed"/>
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Emby.Server.Implementations.Networking;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Net
|
||||
{
|
||||
@@ -19,7 +18,10 @@ namespace Emby.Server.Implementations.Net
|
||||
|
||||
public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort)
|
||||
{
|
||||
if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort));
|
||||
if (remotePort < 0)
|
||||
{
|
||||
throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort));
|
||||
}
|
||||
|
||||
var addressFamily = remoteAddress.AddressFamily == IpAddressFamily.InterNetwork
|
||||
? AddressFamily.InterNetwork
|
||||
@@ -42,8 +44,7 @@ namespace Emby.Server.Implementations.Net
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (retVal != null)
|
||||
retVal.Dispose();
|
||||
retVal?.Dispose();
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -55,7 +56,10 @@ namespace Emby.Server.Implementations.Net
|
||||
/// <param name="localPort">An integer specifying the local port to bind the acceptSocket to.</param>
|
||||
public ISocket CreateUdpSocket(int localPort)
|
||||
{
|
||||
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
|
||||
if (localPort < 0)
|
||||
{
|
||||
throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
|
||||
}
|
||||
|
||||
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
|
||||
try
|
||||
@@ -65,8 +69,7 @@ namespace Emby.Server.Implementations.Net
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (retVal != null)
|
||||
retVal.Dispose();
|
||||
retVal?.Dispose();
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -74,7 +77,10 @@ namespace Emby.Server.Implementations.Net
|
||||
|
||||
public ISocket CreateUdpBroadcastSocket(int localPort)
|
||||
{
|
||||
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
|
||||
if (localPort < 0)
|
||||
{
|
||||
throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
|
||||
}
|
||||
|
||||
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
|
||||
try
|
||||
@@ -86,8 +92,7 @@ namespace Emby.Server.Implementations.Net
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (retVal != null)
|
||||
retVal.Dispose();
|
||||
retVal?.Dispose();
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -99,7 +104,10 @@ namespace Emby.Server.Implementations.Net
|
||||
/// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
|
||||
public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
|
||||
{
|
||||
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
|
||||
if (localPort < 0)
|
||||
{
|
||||
throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
|
||||
}
|
||||
|
||||
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
|
||||
try
|
||||
@@ -114,8 +122,7 @@ namespace Emby.Server.Implementations.Net
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (retVal != null)
|
||||
retVal.Dispose();
|
||||
retVal?.Dispose();
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -130,10 +137,25 @@ namespace Emby.Server.Implementations.Net
|
||||
/// <returns></returns>
|
||||
public ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort)
|
||||
{
|
||||
if (ipAddress == null) throw new ArgumentNullException(nameof(ipAddress));
|
||||
if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", nameof(ipAddress));
|
||||
if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", nameof(multicastTimeToLive));
|
||||
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
|
||||
if (ipAddress == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ipAddress));
|
||||
}
|
||||
|
||||
if (ipAddress.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("ipAddress cannot be an empty string.", nameof(ipAddress));
|
||||
}
|
||||
|
||||
if (multicastTimeToLive <= 0)
|
||||
{
|
||||
throw new ArgumentException("multicastTimeToLive cannot be zero or less.", nameof(multicastTimeToLive));
|
||||
}
|
||||
|
||||
if (localPort < 0)
|
||||
{
|
||||
throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
|
||||
}
|
||||
|
||||
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
|
||||
|
||||
@@ -172,87 +194,13 @@ namespace Emby.Server.Implementations.Net
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (retVal != null)
|
||||
retVal.Dispose();
|
||||
retVal?.Dispose();
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream CreateNetworkStream(ISocket socket, bool ownsSocket)
|
||||
{
|
||||
var netSocket = (UdpSocket)socket;
|
||||
|
||||
return new SocketStream(netSocket.Socket, ownsSocket);
|
||||
}
|
||||
=> new NetworkStream(((UdpSocket)socket).Socket, ownsSocket);
|
||||
}
|
||||
|
||||
public class SocketStream : Stream
|
||||
{
|
||||
private readonly Socket _socket;
|
||||
|
||||
public SocketStream(Socket socket, bool ownsSocket)
|
||||
{
|
||||
_socket = socket;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_socket.Send(buffer, offset, count, SocketFlags.None);
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
return _socket.BeginSend(buffer, offset, count, SocketFlags.None, callback, state);
|
||||
}
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
{
|
||||
_socket.EndSend(asyncResult);
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return _socket.Receive(buffer, offset, count, SocketFlags.None);
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
return _socket.BeginReceive(buffer, offset, count, SocketFlags.None, callback, state);
|
||||
}
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
return _socket.EndReceive(asyncResult);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,12 +11,15 @@ namespace Emby.Server.Implementations.Net
|
||||
// THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS
|
||||
// Be careful to check any changes compile and work for all platform projects it is shared in.
|
||||
|
||||
public sealed class UdpSocket : DisposableManagedObjectBase, ISocket
|
||||
public sealed class UdpSocket : ISocket, IDisposable
|
||||
{
|
||||
private Socket _Socket;
|
||||
private int _LocalPort;
|
||||
private Socket _socket;
|
||||
private int _localPort;
|
||||
private bool _disposed = false;
|
||||
|
||||
public Socket Socket => _Socket;
|
||||
public Socket Socket => _socket;
|
||||
|
||||
public IpAddressInfo LocalIPAddress { get; }
|
||||
|
||||
private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
|
||||
{
|
||||
@@ -35,11 +38,11 @@ namespace Emby.Server.Implementations.Net
|
||||
{
|
||||
if (socket == null) throw new ArgumentNullException(nameof(socket));
|
||||
|
||||
_Socket = socket;
|
||||
_LocalPort = localPort;
|
||||
_socket = socket;
|
||||
_localPort = localPort;
|
||||
LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
|
||||
|
||||
_Socket.Bind(new IPEndPoint(ip, _LocalPort));
|
||||
_socket.Bind(new IPEndPoint(ip, _localPort));
|
||||
|
||||
InitReceiveSocketAsyncEventArgs();
|
||||
}
|
||||
@@ -101,32 +104,26 @@ namespace Emby.Server.Implementations.Net
|
||||
{
|
||||
if (socket == null) throw new ArgumentNullException(nameof(socket));
|
||||
|
||||
_Socket = socket;
|
||||
_Socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
|
||||
_socket = socket;
|
||||
_socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
|
||||
|
||||
InitReceiveSocketAsyncEventArgs();
|
||||
}
|
||||
|
||||
public IpAddressInfo LocalIPAddress
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||
|
||||
return _Socket.BeginReceiveFrom(buffer, offset, count, SocketFlags.None, ref receivedFromEndPoint, callback, buffer);
|
||||
return _socket.BeginReceiveFrom(buffer, offset, count, SocketFlags.None, ref receivedFromEndPoint, callback, buffer);
|
||||
}
|
||||
|
||||
public int Receive(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return _Socket.Receive(buffer, 0, buffer.Length, SocketFlags.None);
|
||||
return _socket.Receive(buffer, 0, buffer.Length, SocketFlags.None);
|
||||
}
|
||||
|
||||
public SocketReceiveResult EndReceive(IAsyncResult result)
|
||||
@@ -136,7 +133,7 @@ namespace Emby.Server.Implementations.Net
|
||||
var sender = new IPEndPoint(IPAddress.Any, 0);
|
||||
var remoteEndPoint = (EndPoint)sender;
|
||||
|
||||
var receivedBytes = _Socket.EndReceiveFrom(result, ref remoteEndPoint);
|
||||
var receivedBytes = _socket.EndReceiveFrom(result, ref remoteEndPoint);
|
||||
|
||||
var buffer = (byte[])result.AsyncState;
|
||||
|
||||
@@ -236,37 +233,42 @@ namespace Emby.Server.Implementations.Net
|
||||
|
||||
var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint);
|
||||
|
||||
return _Socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state);
|
||||
return _socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state);
|
||||
}
|
||||
|
||||
public int EndSendTo(IAsyncResult result)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return _Socket.EndSendTo(result);
|
||||
return _socket.EndSendTo(result);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (disposing)
|
||||
if (_disposed)
|
||||
{
|
||||
var socket = _Socket;
|
||||
if (socket != null)
|
||||
socket.Dispose();
|
||||
|
||||
var tcs = _currentReceiveTaskCompletionSource;
|
||||
if (tcs != null)
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
}
|
||||
var sendTcs = _currentSendTaskCompletionSource;
|
||||
if (sendTcs != null)
|
||||
{
|
||||
sendTcs.TrySetCanceled();
|
||||
}
|
||||
throw new ObjectDisposedException(nameof(UdpSocket));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_socket?.Dispose();
|
||||
_currentReceiveTaskCompletionSource?.TrySetCanceled();
|
||||
_currentSendTaskCompletionSource?.TrySetCanceled();
|
||||
|
||||
_socket = null;
|
||||
_currentReceiveTaskCompletionSource = null;
|
||||
_currentSendTaskCompletionSource = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
|
||||
{
|
||||
if (endpoint == null)
|
||||
|
||||
@@ -7,11 +7,11 @@ using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace Emby.Server.Implementations.Networking
|
||||
{
|
||||
@@ -22,14 +22,12 @@ namespace Emby.Server.Implementations.Networking
|
||||
public event EventHandler NetworkChanged;
|
||||
public Func<string[]> LocalSubnetsFn { get; set; }
|
||||
|
||||
public NetworkManager(
|
||||
ILoggerFactory loggerFactory,
|
||||
IEnvironmentInfo environment)
|
||||
public NetworkManager(ILoggerFactory loggerFactory)
|
||||
{
|
||||
Logger = loggerFactory.CreateLogger(nameof(NetworkManager));
|
||||
|
||||
// In FreeBSD these events cause a crash
|
||||
if (environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.BSD)
|
||||
if (OperatingSystem.Id != OperatingSystemId.BSD)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using MediaBrowser.Model.Reflection;
|
||||
|
||||
namespace Emby.Server.Implementations.Reflection
|
||||
{
|
||||
public class AssemblyInfo : IAssemblyInfo
|
||||
{
|
||||
public Stream GetManifestResourceStream(Type type, string resource)
|
||||
{
|
||||
return type.Assembly.GetManifestResourceStream(resource);
|
||||
}
|
||||
|
||||
public string[] GetManifestResourceNames(Type type)
|
||||
{
|
||||
return type.Assembly.GetManifestResourceNames();
|
||||
}
|
||||
|
||||
public Assembly[] GetCurrentAssemblies()
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,13 @@ namespace Emby.Server.Implementations
|
||||
string programDataPath,
|
||||
string logDirectoryPath,
|
||||
string configurationDirectoryPath,
|
||||
string cacheDirectoryPath)
|
||||
string cacheDirectoryPath,
|
||||
string webDirectoryPath)
|
||||
: base(programDataPath,
|
||||
logDirectoryPath,
|
||||
configurationDirectoryPath,
|
||||
cacheDirectoryPath)
|
||||
cacheDirectoryPath,
|
||||
webDirectoryPath)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -116,14 +116,14 @@ namespace Emby.Server.Implementations.Session
|
||||
_authRepo = authRepo;
|
||||
_deviceManager = deviceManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_deviceManager.DeviceOptionsUpdated += _deviceManager_DeviceOptionsUpdated;
|
||||
_deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated;
|
||||
}
|
||||
|
||||
private void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs<Tuple<string, DeviceOptions>> e)
|
||||
private void OnDeviceManagerDeviceOptionsUpdated(object sender, GenericEventArgs<Tuple<string, DeviceOptions>> e)
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (string.Equals(session.DeviceId, e.Argument.Item1))
|
||||
if (string.Equals(session.DeviceId, e.Argument.Item1, StringComparison.Ordinal))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(e.Argument.Item2.CustomName))
|
||||
{
|
||||
@@ -138,11 +138,29 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
private bool _disposed = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose stuff
|
||||
}
|
||||
|
||||
_deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated;
|
||||
|
||||
_disposed = true;
|
||||
_deviceManager.DeviceOptionsUpdated -= _deviceManager_DeviceOptionsUpdated;
|
||||
}
|
||||
|
||||
public void CheckDisposed()
|
||||
@@ -157,7 +175,7 @@ namespace Emby.Server.Implementations.Session
|
||||
/// Gets all connections.
|
||||
/// </summary>
|
||||
/// <value>All connections.</value>
|
||||
public IEnumerable<SessionInfo> Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate).ToList();
|
||||
public IEnumerable<SessionInfo> Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate);
|
||||
|
||||
private void OnSessionStarted(SessionInfo info)
|
||||
{
|
||||
@@ -171,20 +189,27 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs
|
||||
{
|
||||
SessionInfo = info
|
||||
|
||||
}, _logger);
|
||||
EventHelper.QueueEventIfNotNull(
|
||||
SessionStarted,
|
||||
this,
|
||||
new SessionEventArgs
|
||||
{
|
||||
SessionInfo = info
|
||||
},
|
||||
_logger);
|
||||
}
|
||||
|
||||
private void OnSessionEnded(SessionInfo info)
|
||||
{
|
||||
EventHelper.QueueEventIfNotNull(SessionEnded, this, new SessionEventArgs
|
||||
{
|
||||
SessionInfo = info
|
||||
EventHelper.QueueEventIfNotNull(
|
||||
SessionEnded,
|
||||
this,
|
||||
new SessionEventArgs
|
||||
{
|
||||
SessionInfo = info
|
||||
|
||||
}, _logger);
|
||||
},
|
||||
_logger);
|
||||
|
||||
info.Dispose();
|
||||
}
|
||||
@@ -192,9 +217,6 @@ namespace Emby.Server.Implementations.Session
|
||||
public void UpdateDeviceName(string sessionId, string deviceName)
|
||||
{
|
||||
var session = GetSession(sessionId);
|
||||
|
||||
var key = GetSessionKey(session.Client, session.DeviceId);
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
session.DeviceName = deviceName;
|
||||
@@ -210,10 +232,10 @@ namespace Emby.Server.Implementations.Session
|
||||
/// <param name="deviceName">Name of the device.</param>
|
||||
/// <param name="remoteEndPoint">The remote end point.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <returns>SessionInfo.</returns>
|
||||
/// <exception cref="ArgumentNullException">user</exception>
|
||||
/// <exception cref="UnauthorizedAccessException"></exception>
|
||||
public SessionInfo LogSessionActivity(string appName,
|
||||
public SessionInfo LogSessionActivity(
|
||||
string appName,
|
||||
string appVersion,
|
||||
string deviceId,
|
||||
string deviceName,
|
||||
@@ -226,10 +248,12 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
throw new ArgumentNullException(nameof(appName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(appVersion))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(appVersion));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(deviceId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(deviceId));
|
||||
@@ -260,10 +284,12 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
if ((activityDate - lastActivityDate).TotalSeconds > 10)
|
||||
{
|
||||
SessionActivity?.Invoke(this, new SessionEventArgs
|
||||
{
|
||||
SessionInfo = session
|
||||
});
|
||||
SessionActivity?.Invoke(
|
||||
this,
|
||||
new SessionEventArgs
|
||||
{
|
||||
SessionInfo = session
|
||||
});
|
||||
}
|
||||
|
||||
return session;
|
||||
@@ -304,6 +330,7 @@ namespace Emby.Server.Implementations.Session
|
||||
/// <summary>
|
||||
/// Updates the now playing item id.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem, bool updateLastCheckInTime)
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.MediaSourceId))
|
||||
@@ -418,7 +445,7 @@ namespace Emby.Server.Implementations.Session
|
||||
});
|
||||
|
||||
sessionInfo.UserId = user == null ? Guid.Empty : user.Id;
|
||||
sessionInfo.UserName = user == null ? null : user.Name;
|
||||
sessionInfo.UserName = user?.Name;
|
||||
sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary);
|
||||
sessionInfo.RemoteEndPoint = remoteEndPoint;
|
||||
sessionInfo.Client = appName;
|
||||
@@ -432,7 +459,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
sessionInfo.AdditionalUsers = new SessionUserInfo[] { };
|
||||
sessionInfo.AdditionalUsers = Array.Empty<SessionUserInfo>();
|
||||
}
|
||||
|
||||
return sessionInfo;
|
||||
@@ -449,9 +476,9 @@ namespace Emby.Server.Implementations.Session
|
||||
ServerId = _appHost.SystemId
|
||||
};
|
||||
|
||||
var username = user == null ? null : user.Name;
|
||||
var username = user?.Name;
|
||||
|
||||
sessionInfo.UserId = user == null ? Guid.Empty : user.Id;
|
||||
sessionInfo.UserId = user?.Id ?? Guid.Empty;
|
||||
sessionInfo.UserName = username;
|
||||
sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary);
|
||||
sessionInfo.RemoteEndPoint = remoteEndPoint;
|
||||
@@ -508,6 +535,7 @@ namespace Emby.Server.Implementations.Session
|
||||
_idleTimer = new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
}
|
||||
|
||||
private void StopIdleCheckTimer()
|
||||
{
|
||||
if (_idleTimer != null)
|
||||
@@ -539,9 +567,9 @@ namespace Emby.Server.Implementations.Session
|
||||
Item = session.NowPlayingItem,
|
||||
ItemId = session.NowPlayingItem == null ? Guid.Empty : session.NowPlayingItem.Id,
|
||||
SessionId = session.Id,
|
||||
MediaSourceId = session.PlayState == null ? null : session.PlayState.MediaSourceId,
|
||||
PositionTicks = session.PlayState == null ? null : session.PlayState.PositionTicks
|
||||
});
|
||||
MediaSourceId = session.PlayState?.MediaSourceId,
|
||||
PositionTicks = session.PlayState?.PositionTicks
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -616,18 +644,22 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
// Nothing to save here
|
||||
// Fire events to inform plugins
|
||||
EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
|
||||
{
|
||||
Item = libraryItem,
|
||||
Users = users,
|
||||
MediaSourceId = info.MediaSourceId,
|
||||
MediaInfo = info.Item,
|
||||
DeviceName = session.DeviceName,
|
||||
ClientName = session.Client,
|
||||
DeviceId = session.DeviceId,
|
||||
Session = session
|
||||
EventHelper.QueueEventIfNotNull(
|
||||
PlaybackStart,
|
||||
this,
|
||||
new PlaybackProgressEventArgs
|
||||
{
|
||||
Item = libraryItem,
|
||||
Users = users,
|
||||
MediaSourceId = info.MediaSourceId,
|
||||
MediaInfo = info.Item,
|
||||
DeviceName = session.DeviceName,
|
||||
ClientName = session.Client,
|
||||
DeviceId = session.DeviceId,
|
||||
Session = session
|
||||
|
||||
}, _logger);
|
||||
},
|
||||
_logger);
|
||||
|
||||
StartIdleCheckTimer();
|
||||
}
|
||||
@@ -667,6 +699,7 @@ namespace Emby.Server.Implementations.Session
|
||||
/// <summary>
|
||||
/// Used to report playback progress for an item
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated)
|
||||
{
|
||||
CheckDisposed();
|
||||
@@ -695,21 +728,23 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs
|
||||
{
|
||||
Item = libraryItem,
|
||||
Users = users,
|
||||
PlaybackPositionTicks = session.PlayState.PositionTicks,
|
||||
MediaSourceId = session.PlayState.MediaSourceId,
|
||||
MediaInfo = info.Item,
|
||||
DeviceName = session.DeviceName,
|
||||
ClientName = session.Client,
|
||||
DeviceId = session.DeviceId,
|
||||
IsPaused = info.IsPaused,
|
||||
PlaySessionId = info.PlaySessionId,
|
||||
IsAutomated = isAutomated,
|
||||
Session = session
|
||||
});
|
||||
PlaybackProgress?.Invoke(
|
||||
this,
|
||||
new PlaybackProgressEventArgs
|
||||
{
|
||||
Item = libraryItem,
|
||||
Users = users,
|
||||
PlaybackPositionTicks = session.PlayState.PositionTicks,
|
||||
MediaSourceId = session.PlayState.MediaSourceId,
|
||||
MediaInfo = info.Item,
|
||||
DeviceName = session.DeviceName,
|
||||
ClientName = session.Client,
|
||||
DeviceId = session.DeviceId,
|
||||
IsPaused = info.IsPaused,
|
||||
PlaySessionId = info.PlaySessionId,
|
||||
IsAutomated = isAutomated,
|
||||
Session = session
|
||||
});
|
||||
|
||||
if (!isAutomated)
|
||||
{
|
||||
@@ -830,8 +865,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
MediaSourceInfo mediaSource = null;
|
||||
|
||||
var hasMediaSources = libraryItem as IHasMediaSources;
|
||||
if (hasMediaSources != null)
|
||||
if (libraryItem is IHasMediaSources hasMediaSources)
|
||||
{
|
||||
mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
|
||||
}
|
||||
@@ -848,7 +882,8 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
var msString = info.PositionTicks.HasValue ? (info.PositionTicks.Value / 10000).ToString(CultureInfo.InvariantCulture) : "unknown";
|
||||
|
||||
_logger.LogInformation("Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms",
|
||||
_logger.LogInformation(
|
||||
"Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms",
|
||||
session.Client,
|
||||
session.ApplicationVersion,
|
||||
info.Item.Name,
|
||||
@@ -887,20 +922,24 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs
|
||||
{
|
||||
Item = libraryItem,
|
||||
Users = users,
|
||||
PlaybackPositionTicks = info.PositionTicks,
|
||||
PlayedToCompletion = playedToCompletion,
|
||||
MediaSourceId = info.MediaSourceId,
|
||||
MediaInfo = info.Item,
|
||||
DeviceName = session.DeviceName,
|
||||
ClientName = session.Client,
|
||||
DeviceId = session.DeviceId,
|
||||
Session = session
|
||||
EventHelper.QueueEventIfNotNull(
|
||||
PlaybackStopped,
|
||||
this,
|
||||
new PlaybackStopEventArgs
|
||||
{
|
||||
Item = libraryItem,
|
||||
Users = users,
|
||||
PlaybackPositionTicks = info.PositionTicks,
|
||||
PlayedToCompletion = playedToCompletion,
|
||||
MediaSourceId = info.MediaSourceId,
|
||||
MediaInfo = info.Item,
|
||||
DeviceName = session.DeviceName,
|
||||
ClientName = session.Client,
|
||||
DeviceId = session.DeviceId,
|
||||
Session = session
|
||||
|
||||
}, _logger);
|
||||
},
|
||||
_logger);
|
||||
}
|
||||
|
||||
private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed)
|
||||
@@ -936,11 +975,10 @@ namespace Emby.Server.Implementations.Session
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="throwOnMissing">if set to <c>true</c> [throw on missing].</param>
|
||||
/// <returns>SessionInfo.</returns>
|
||||
/// <exception cref="ResourceNotFoundException"></exception>
|
||||
/// <exception cref="ResourceNotFoundException">sessionId</exception>
|
||||
private SessionInfo GetSession(string sessionId, bool throwOnMissing = true)
|
||||
{
|
||||
var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId));
|
||||
|
||||
var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal));
|
||||
if (session == null && throwOnMissing)
|
||||
{
|
||||
throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
|
||||
@@ -952,7 +990,7 @@ namespace Emby.Server.Implementations.Session
|
||||
private SessionInfo GetSessionToRemoteControl(string sessionId)
|
||||
{
|
||||
// Accept either device id or session id
|
||||
var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId));
|
||||
var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal));
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
@@ -1061,10 +1099,12 @@ namespace Emby.Server.Implementations.Session
|
||||
var series = episode.Series;
|
||||
if (series != null)
|
||||
{
|
||||
var episodes = series.GetEpisodes(user, new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
})
|
||||
var episodes = series.GetEpisodes(
|
||||
user,
|
||||
new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
})
|
||||
.Where(i => !i.IsVirtualItem)
|
||||
.SkipWhile(i => i.Id != episode.Id)
|
||||
.ToList();
|
||||
@@ -1100,9 +1140,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return new List<BaseItem>();
|
||||
}
|
||||
|
||||
var byName = item as IItemByName;
|
||||
|
||||
if (byName != null)
|
||||
if (item is IItemByName byName)
|
||||
{
|
||||
return byName.GetTaggedItems(new InternalItemsQuery(user)
|
||||
{
|
||||
@@ -1152,7 +1190,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
_logger.LogError("A non-existant item Id {0} was passed into TranslateItemForInstantMix", id);
|
||||
_logger.LogError("A non-existent item Id {0} was passed into TranslateItemForInstantMix", id);
|
||||
return new List<BaseItem>();
|
||||
}
|
||||
|
||||
@@ -1163,13 +1201,15 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
var generalCommand = new GeneralCommand
|
||||
{
|
||||
Name = GeneralCommandType.DisplayContent.ToString()
|
||||
Name = GeneralCommandType.DisplayContent.ToString(),
|
||||
Arguments =
|
||||
{
|
||||
["ItemId"] = command.ItemId,
|
||||
["ItemName"] = command.ItemName,
|
||||
["ItemType"] = command.ItemType
|
||||
}
|
||||
};
|
||||
|
||||
generalCommand.Arguments["ItemId"] = command.ItemId;
|
||||
generalCommand.Arguments["ItemName"] = command.ItemName;
|
||||
generalCommand.Arguments["ItemType"] = command.ItemType;
|
||||
|
||||
return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1410,7 +1450,8 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName);
|
||||
|
||||
var session = LogSessionActivity(request.App,
|
||||
var session = LogSessionActivity(
|
||||
request.App,
|
||||
request.AppVersion,
|
||||
request.DeviceId,
|
||||
request.DeviceName,
|
||||
@@ -1454,9 +1495,9 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
Logout(auth);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
_logger.LogError(ex, "Error while logging out.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1572,7 +1613,8 @@ namespace Emby.Server.Implementations.Session
|
||||
ReportCapabilities(session, capabilities, true);
|
||||
}
|
||||
|
||||
private void ReportCapabilities(SessionInfo session,
|
||||
private void ReportCapabilities(
|
||||
SessionInfo session,
|
||||
ClientCapabilities capabilities,
|
||||
bool saveCapabilities)
|
||||
{
|
||||
@@ -1580,10 +1622,12 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
if (saveCapabilities)
|
||||
{
|
||||
CapabilitiesChanged?.Invoke(this, new SessionEventArgs
|
||||
{
|
||||
SessionInfo = session
|
||||
});
|
||||
CapabilitiesChanged?.Invoke(
|
||||
this,
|
||||
new SessionEventArgs
|
||||
{
|
||||
SessionInfo = session
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -63,6 +63,28 @@ public sealed class HttpPostedFile : IDisposable
|
||||
_position = offset;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => true;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => _end - _offset;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _position - _offset;
|
||||
set
|
||||
{
|
||||
if (value > Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
|
||||
_position = Seek(value, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
@@ -178,27 +200,5 @@ public sealed class HttpPostedFile : IDisposable
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => true;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => _end - _offset;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _position - _offset;
|
||||
set
|
||||
{
|
||||
if (value > Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
|
||||
_position = Seek(value, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Sorting
|
||||
{
|
||||
var audio = x as IHasAlbumArtist;
|
||||
|
||||
return audio != null ? audio.AlbumArtists.FirstOrDefault() : null;
|
||||
return audio?.AlbumArtists.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -18,17 +18,17 @@ namespace Emby.Server.Implementations.Sorting
|
||||
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private static string GetValue(BaseItem item)
|
||||
{
|
||||
var hasSeries = item as IHasSeries;
|
||||
|
||||
return hasSeries != null ? hasSeries.FindSeriesSortName() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name => ItemSortBy.SeriesSortName;
|
||||
|
||||
private static string GetValue(BaseItem item)
|
||||
{
|
||||
var hasSeries = item as IHasSeries;
|
||||
|
||||
return hasSeries?.FindSeriesSortName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
@@ -39,11 +38,10 @@ namespace Emby.Server.Implementations.Updates
|
||||
/// <summary>
|
||||
/// The completed installations
|
||||
/// </summary>
|
||||
private ConcurrentBag<InstallationInfo> CompletedInstallationsInternal { get; set; }
|
||||
private ConcurrentBag<InstallationInfo> _completedInstallationsInternal;
|
||||
|
||||
public IEnumerable<InstallationInfo> CompletedInstallations => CompletedInstallationsInternal;
|
||||
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
|
||||
|
||||
#region PluginUninstalled Event
|
||||
/// <summary>
|
||||
/// Occurs when [plugin uninstalled].
|
||||
/// </summary>
|
||||
@@ -57,9 +55,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
{
|
||||
PluginUninstalled?.Invoke(this, new GenericEventArgs<IPlugin> { Argument = plugin });
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PluginUpdated Event
|
||||
/// <summary>
|
||||
/// Occurs when [plugin updated].
|
||||
/// </summary>
|
||||
@@ -77,9 +73,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
|
||||
_applicationHost.NotifyPendingRestart();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PluginInstalled Event
|
||||
/// <summary>
|
||||
/// Occurs when [plugin updated].
|
||||
/// </summary>
|
||||
@@ -96,7 +90,6 @@ namespace Emby.Server.Implementations.Updates
|
||||
|
||||
_applicationHost.NotifyPendingRestart();
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
@@ -115,12 +108,8 @@ namespace Emby.Server.Implementations.Updates
|
||||
/// <value>The application host.</value>
|
||||
private readonly IApplicationHost _applicationHost;
|
||||
|
||||
private readonly ICryptoProvider _cryptographyProvider;
|
||||
private readonly IZipClient _zipClient;
|
||||
|
||||
// netframework or netcore
|
||||
private readonly string _packageRuntime;
|
||||
|
||||
public InstallationManager(
|
||||
ILoggerFactory loggerFactory,
|
||||
IApplicationHost appHost,
|
||||
@@ -129,9 +118,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
IJsonSerializer jsonSerializer,
|
||||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
ICryptoProvider cryptographyProvider,
|
||||
IZipClient zipClient,
|
||||
string packageRuntime)
|
||||
IZipClient zipClient)
|
||||
{
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
@@ -139,18 +126,16 @@ namespace Emby.Server.Implementations.Updates
|
||||
}
|
||||
|
||||
CurrentInstallations = new List<Tuple<InstallationInfo, CancellationTokenSource>>();
|
||||
CompletedInstallationsInternal = new ConcurrentBag<InstallationInfo>();
|
||||
_completedInstallationsInternal = new ConcurrentBag<InstallationInfo>();
|
||||
|
||||
_logger = loggerFactory.CreateLogger(nameof(InstallationManager));
|
||||
_applicationHost = appHost;
|
||||
_appPaths = appPaths;
|
||||
_httpClient = httpClient;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
_zipClient = zipClient;
|
||||
_packageRuntime = packageRuntime;
|
||||
_logger = loggerFactory.CreateLogger(nameof(InstallationManager));
|
||||
}
|
||||
|
||||
private static Version GetPackageVersion(PackageVersionInfo version)
|
||||
@@ -222,11 +207,6 @@ namespace Emby.Server.Implementations.Updates
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(version.runtimes) || version.runtimes.IndexOf(_packageRuntime, StringComparison.OrdinalIgnoreCase) == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
versions.Add(version);
|
||||
}
|
||||
|
||||
@@ -448,7 +428,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
CurrentInstallations.Remove(tuple);
|
||||
}
|
||||
|
||||
CompletedInstallationsInternal.Add(installationInfo);
|
||||
_completedInstallationsInternal.Add(installationInfo);
|
||||
|
||||
PackageInstallationCompleted?.Invoke(this, installationEventArgs);
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Xml;
|
||||
using MediaBrowser.Model.Xml;
|
||||
|
||||
namespace Emby.Server.Implementations.Xml
|
||||
{
|
||||
public class XmlReaderSettingsFactory : IXmlReaderSettingsFactory
|
||||
{
|
||||
public XmlReaderSettings Create(bool enableValidation)
|
||||
{
|
||||
var settings = new XmlReaderSettings();
|
||||
|
||||
if (!enableValidation)
|
||||
{
|
||||
settings.ValidationType = ValidationType.None;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user