Merge branch 'master' into async

pull/847/head
Bond-009 5 years ago committed by GitHub
commit 64a4f259a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -55,15 +55,77 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
# Style Definitions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const
# Style Definitions (From Roslyn)
# Non-private static fields are PascalCase
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
# Constants are PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
dotnet_naming_symbols.constants.applicable_kinds = field, local
dotnet_naming_symbols.constants.required_modifiers = const
dotnet_naming_style.constant_style.capitalization = pascal_case
# Static fields are camelCase and start with s_
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_style.static_field_style.capitalization = camel_case
dotnet_naming_style.static_field_style.required_prefix = _
# Instance fields are camelCase and start with _
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
dotnet_naming_symbols.instance_fields.applicable_kinds = field
dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Locals and parameters are camelCase
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
dotnet_naming_style.camel_case_style.capitalization = camel_case
# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_style.local_function_style.capitalization = pascal_case
# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.all_members.applicable_kinds = *
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
###############################
# C# Coding Conventions #
###############################

@ -38,7 +38,9 @@ namespace Emby.Dlna
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILoggerFactory loggerFactory,
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
IJsonSerializer jsonSerializer,
IServerApplicationHost appHost,
IAssemblyInfo assemblyInfo)
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;

@ -36,7 +36,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
};
}
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
: base(config, logger, xmlReaderSettingsFactory)
{
}
}

@ -1,3 +1,4 @@
using System;
using System.IO;
using MediaBrowser.Common.Configuration;
@ -14,50 +15,44 @@ namespace Emby.Server.Implementations.AppBase
/// </summary>
protected BaseApplicationPaths(
string programDataPath,
string appFolderPath,
string logDirectoryPath = null,
string configurationDirectoryPath = null,
string cacheDirectoryPath = null)
string logDirectoryPath,
string configurationDirectoryPath,
string cacheDirectoryPath)
{
ProgramDataPath = programDataPath;
ProgramSystemPath = appFolderPath;
LogDirectoryPath = logDirectoryPath;
ConfigurationDirectoryPath = configurationDirectoryPath;
CachePath = cacheDirectoryPath;
DataPath = Path.Combine(ProgramDataPath, "data");
}
/// <summary>
/// Gets the path to the program data folder
/// </summary>
/// <value>The program data path.</value>
public string ProgramDataPath { get; private set; }
/// <summary>
/// Gets the path to the system folder
/// </summary>
public string ProgramSystemPath { get; private set; }
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
/// <summary>
/// The _data directory
/// </summary>
private string _dataDirectory;
/// <summary>
/// Gets the folder path to the data directory
/// </summary>
/// <value>The data directory.</value>
private string _dataPath;
public string DataPath
{
get
{
if (_dataDirectory == null)
{
_dataDirectory = Path.Combine(ProgramDataPath, "data");
Directory.CreateDirectory(_dataDirectory);
}
return _dataDirectory;
}
get => _dataPath;
private set => _dataPath = Directory.CreateDirectory(value).FullName;
}
private const string _virtualDataPath = "%AppDataPath%";
public string VirtualDataPath => _virtualDataPath;
/// <summary>
/// Gets the magic strings used for virtual path manipulation.
/// </summary>
public string VirtualDataPath { get; } = "%AppDataPath%";
/// <summary>
/// Gets the image cache path.
@ -83,55 +78,17 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The plugin configurations path.</value>
public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates");
/// <summary>
/// The _log directory
/// </summary>
private string _logDirectoryPath;
/// <summary>
/// Gets the path to the log directory
/// </summary>
/// <value>The log directory path.</value>
public string LogDirectoryPath
{
get
{
if (string.IsNullOrEmpty(_logDirectoryPath))
{
_logDirectoryPath = Path.Combine(ProgramDataPath, "logs");
Directory.CreateDirectory(_logDirectoryPath);
}
return _logDirectoryPath;
}
set => _logDirectoryPath = value;
}
/// <summary>
/// The _config directory
/// </summary>
private string _configurationDirectoryPath;
public string LogDirectoryPath { get; private set; }
/// <summary>
/// Gets the path to the application configuration root directory
/// </summary>
/// <value>The configuration directory path.</value>
public string ConfigurationDirectoryPath
{
get
{
if (string.IsNullOrEmpty(_configurationDirectoryPath))
{
_configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
Directory.CreateDirectory(_configurationDirectoryPath);
}
return _configurationDirectoryPath;
}
set => _configurationDirectoryPath = value;
}
public string ConfigurationDirectoryPath { get; private set; }
/// <summary>
/// Gets the path to the system configuration file
@ -139,29 +96,11 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The system configuration file path.</value>
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
/// <summary>
/// The _cache directory
/// </summary>
private string _cachePath;
/// <summary>
/// Gets the folder path to the cache directory
/// </summary>
/// <value>The cache directory.</value>
public string CachePath
{
get
{
if (string.IsNullOrEmpty(_cachePath))
{
_cachePath = Path.Combine(ProgramDataPath, "cache");
Directory.CreateDirectory(_cachePath);
}
return _cachePath;
}
set => _cachePath = value;
}
public string CachePath { get; set; }
/// <summary>
/// Gets the folder path to the temp directory within the cache folder

@ -171,16 +171,29 @@ namespace Emby.Server.Implementations.AppBase
private void UpdateCachePath()
{
string cachePath;
// If the configuration file has no entry (i.e. not set in UI)
if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
{
cachePath = null;
// If the current live configuration has no entry (i.e. not set on CLI/envvars, during startup)
if (string.IsNullOrWhiteSpace(((BaseApplicationPaths)CommonApplicationPaths).CachePath))
{
// Set cachePath to a default value under ProgramDataPath
cachePath = Path.Combine(((BaseApplicationPaths)CommonApplicationPaths).ProgramDataPath, "cache");
}
else
{
// Set cachePath to the existing live value; will require restart if UI value is removed (but not replaced)
// TODO: Figure out how to re-grab this from the CLI/envvars while running
cachePath = ((BaseApplicationPaths)CommonApplicationPaths).CachePath;
}
}
else
{
cachePath = Path.Combine(CommonConfiguration.CachePath, "cache");
// Set cachePath to the new UI-set value
cachePath = CommonConfiguration.CachePath;
}
Logger.LogInformation("Setting cache path to " + cachePath);
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
}

@ -105,6 +105,7 @@ using MediaBrowser.Providers.Subtitles;
using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using ServiceStack;
using ServiceStack.Text.Jsv;
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
@ -202,7 +203,7 @@ namespace Emby.Server.Implementations
/// Gets all concrete types.
/// </summary>
/// <value>All concrete types.</value>
public Tuple<Type, string>[] AllConcreteTypes { get; protected set; }
public Type[] AllConcreteTypes { get; protected set; }
/// <summary>
/// The disposable parts
@ -219,8 +220,6 @@ namespace Emby.Server.Implementations
protected IEnvironmentInfo EnvironmentInfo { get; set; }
private IBlurayExaminer BlurayExaminer { get; set; }
public PackageVersionClass SystemUpdateLevel
{
get
@ -232,12 +231,7 @@ namespace Emby.Server.Implementations
}
}
public virtual string OperatingSystemDisplayName => EnvironmentInfo.OperatingSystemName;
/// <summary>
/// The container
/// </summary>
protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
protected IServiceProvider _serviceProvider;
/// <summary>
/// Gets the server configuration manager.
@ -309,7 +303,6 @@ namespace Emby.Server.Implementations
/// <value>The user data repository.</value>
private IUserDataManager UserDataManager { get; set; }
private IUserRepository UserRepository { get; set; }
internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
internal SqliteItemRepository ItemRepository { get; set; }
private INotificationManager NotificationManager { get; set; }
@ -453,138 +446,58 @@ namespace Emby.Server.Implementations
/// <value>The name.</value>
public string Name => ApplicationProductName;
private static Tuple<Assembly, string> GetAssembly(Type type)
{
var assembly = type.GetTypeInfo().Assembly;
return new Tuple<Assembly, string>(assembly, null);
}
public virtual IStreamHelper CreateStreamHelper()
{
return new StreamHelper();
}
/// <summary>
/// Creates an instance of type and resolves all constructor dependancies
/// Creates an instance of type and resolves all constructor dependencies
/// </summary>
/// <param name="type">The type.</param>
/// <returns>System.Object.</returns>
public object CreateInstance(Type type)
{
return Container.GetInstance(type);
}
=> ActivatorUtilities.CreateInstance(_serviceProvider, type);
/// <summary>
/// Creates an instance of type and resolves all constructor dependencies
/// </summary>
/// <param name="type">The type.</param>
/// <returns>System.Object.</returns>
public T CreateInstance<T>()
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
/// <summary>
/// Creates the instance safe.
/// </summary>
/// <param name="typeInfo">The type information.</param>
/// <returns>System.Object.</returns>
protected object CreateInstanceSafe(Tuple<Type, string> typeInfo)
protected object CreateInstanceSafe(Type type)
{
var type = typeInfo.Item1;
try
{
return Container.GetInstance(type);
Logger.LogWarning("Creating instance of {Type}", type);
return ActivatorUtilities.CreateInstance(_serviceProvider, type);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error creating {type}", type.FullName);
// Don't blow up in release mode
Logger.LogError(ex, "Error creating {Type}", type);
return null;
}
}
/// <summary>
/// Registers the specified obj.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj">The obj.</param>
/// <param name="manageLifetime">if set to <c>true</c> [manage lifetime].</param>
protected void RegisterSingleInstance<T>(T obj, bool manageLifetime = true)
where T : class
{
Container.RegisterInstance<T>(obj);
if (manageLifetime)
{
var disposable = obj as IDisposable;
if (disposable != null)
{
DisposableParts.Add(disposable);
}
}
}
/// <summary>
/// Registers the single instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="func">The func.</param>
protected void RegisterSingleInstance<T>(Func<T> func)
where T : class
{
Container.RegisterSingleton(func);
}
/// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
public T Resolve<T>()
{
return (T)Container.GetRegistration(typeof(T), true).GetInstance();
}
/// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
public T TryResolve<T>()
{
var result = Container.GetRegistration(typeof(T), false);
if (result == null)
{
return default(T);
}
return (T)result.GetInstance();
}
/// <summary>
/// Loads the assembly.
/// </summary>
/// <param name="file">The file.</param>
/// <returns>Assembly.</returns>
protected Tuple<Assembly, string> LoadAssembly(string file)
{
try
{
var assembly = Assembly.Load(File.ReadAllBytes(file));
return new Tuple<Assembly, string>(assembly, file);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading assembly {File}", file);
return null;
}
}
public T Resolve<T>() => _serviceProvider.GetService<T>();
/// <summary>
/// Gets the export types.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>IEnumerable{Type}.</returns>
public IEnumerable<Tuple<Type, string>> GetExportTypes<T>()
public IEnumerable<Type> GetExportTypes<T>()
{
var currentType = typeof(T);
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i.Item1));
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
}
/// <summary>
@ -596,9 +509,10 @@ namespace Emby.Server.Implementations
public IEnumerable<T> GetExports<T>(bool manageLifetime = true)
{
var parts = GetExportTypes<T>()
.Select(CreateInstanceSafe)
.Select(x => CreateInstanceSafe(x))
.Where(i => i != null)
.Cast<T>();
.Cast<T>()
.ToList(); // Convert to list so this isn't executed for each iteration
if (manageLifetime)
{
@ -611,33 +525,6 @@ namespace Emby.Server.Implementations
return parts;
}
public List<Tuple<T, string>> GetExportsWithInfo<T>(bool manageLifetime = true)
{
var parts = GetExportTypes<T>()
.Select(i =>
{
var obj = CreateInstanceSafe(i);
if (obj == null)
{
return null;
}
return new Tuple<T, string>((T)obj, i.Item2);
})
.Where(i => i != null)
.ToList();
if (manageLifetime)
{
lock (DisposableParts)
{
DisposableParts.AddRange(parts.Select(i => i.Item1).OfType<IDisposable>());
}
}
return parts;
}
/// <summary>
/// Runs the startup tasks.
/// </summary>
@ -691,7 +578,7 @@ namespace Emby.Server.Implementations
}
}
public async Task Init()
public async Task Init(IServiceCollection serviceCollection)
{
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@ -721,7 +608,7 @@ namespace Emby.Server.Implementations
SetHttpLimit();
await RegisterResources();
await RegisterResources(serviceCollection);
FindParts();
}
@ -736,104 +623,103 @@ namespace Emby.Server.Implementations
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
protected async Task RegisterResources()
protected async Task RegisterResources(IServiceCollection serviceCollection)
{
RegisterSingleInstance(ConfigurationManager);
RegisterSingleInstance<IApplicationHost>(this);
serviceCollection.AddSingleton(ConfigurationManager);
serviceCollection.AddSingleton<IApplicationHost>(this);
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
RegisterSingleInstance(JsonSerializer);
serviceCollection.AddSingleton(JsonSerializer);
RegisterSingleInstance(LoggerFactory, false);
RegisterSingleInstance(Logger);
serviceCollection.AddSingleton(LoggerFactory);
serviceCollection.AddLogging();
serviceCollection.AddSingleton(Logger);
RegisterSingleInstance(EnvironmentInfo);
serviceCollection.AddSingleton(EnvironmentInfo);
RegisterSingleInstance(FileSystemManager);
serviceCollection.AddSingleton(FileSystemManager);
HttpClient = CreateHttpClient();
RegisterSingleInstance(HttpClient);
serviceCollection.AddSingleton(HttpClient);
RegisterSingleInstance(NetworkManager);
serviceCollection.AddSingleton(NetworkManager);
IsoManager = new IsoManager();
RegisterSingleInstance(IsoManager);
serviceCollection.AddSingleton(IsoManager);
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
RegisterSingleInstance(TaskManager);
serviceCollection.AddSingleton(TaskManager);
RegisterSingleInstance(XmlSerializer);
serviceCollection.AddSingleton(XmlSerializer);
ProcessFactory = new ProcessFactory();
RegisterSingleInstance(ProcessFactory);
serviceCollection.AddSingleton(ProcessFactory);
var streamHelper = CreateStreamHelper();
ApplicationHost.StreamHelper = streamHelper;
RegisterSingleInstance(streamHelper);
ApplicationHost.StreamHelper = new StreamHelper();
serviceCollection.AddSingleton(StreamHelper);
RegisterSingleInstance(CryptographyProvider);
serviceCollection.AddSingleton(CryptographyProvider);
SocketFactory = new SocketFactory();
RegisterSingleInstance(SocketFactory);
serviceCollection.AddSingleton(SocketFactory);
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime);
RegisterSingleInstance(InstallationManager);
serviceCollection.AddSingleton(InstallationManager);
ZipClient = new ZipClient(FileSystemManager);
RegisterSingleInstance(ZipClient);
serviceCollection.AddSingleton(ZipClient);
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
RegisterSingleInstance(HttpResultFactory);
serviceCollection.AddSingleton(HttpResultFactory);
RegisterSingleInstance<IServerApplicationHost>(this);
RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);
serviceCollection.AddSingleton<IServerApplicationHost>(this);
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
RegisterSingleInstance(ServerConfigurationManager);
serviceCollection.AddSingleton(ServerConfigurationManager);
IAssemblyInfo assemblyInfo = new AssemblyInfo();
RegisterSingleInstance(assemblyInfo);
var assemblyInfo = new AssemblyInfo();
serviceCollection.AddSingleton<IAssemblyInfo>(assemblyInfo);
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory);
await LocalizationManager.LoadAll();
RegisterSingleInstance<ILocalizationManager>(LocalizationManager);
serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
BlurayExaminer = new BdInfoExaminer(FileSystemManager);
RegisterSingleInstance(BlurayExaminer);
serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
serviceCollection.AddSingleton<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
RegisterSingleInstance(UserDataManager);
serviceCollection.AddSingleton(UserDataManager);
UserRepository = GetUserRepository();
// This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it
RegisterSingleInstance(UserRepository);
serviceCollection.AddSingleton(UserRepository);
var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager);
DisplayPreferencesRepository = displayPreferencesRepo;
RegisterSingleInstance(DisplayPreferencesRepository);
serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo);
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo);
RegisterSingleInstance<IItemRepository>(ItemRepository);
serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
AuthenticationRepository = GetAuthenticationRepository();
RegisterSingleInstance(AuthenticationRepository);
serviceCollection.AddSingleton(AuthenticationRepository);
UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager, CryptographyProvider);
RegisterSingleInstance(UserManager);
serviceCollection.AddSingleton(UserManager);
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
RegisterSingleInstance(LibraryManager);
serviceCollection.AddSingleton(LibraryManager);
// TODO wtaylor: investigate use of second music manager
var musicManager = new MusicManager(LibraryManager);
RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
serviceCollection.AddSingleton<IMusicManager>(new MusicManager(LibraryManager));
LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
RegisterSingleInstance(LibraryMonitor);
serviceCollection.AddSingleton(LibraryMonitor);
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager));
serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
CertificateInfo = GetCertificateInfo(true);
Certificate = GetCertificate(CertificateInfo);
@ -848,81 +734,82 @@ namespace Emby.Server.Implementations
GetParseFn);
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
RegisterSingleInstance(HttpServer);
serviceCollection.AddSingleton(HttpServer);
ImageProcessor = GetImageProcessor();
RegisterSingleInstance(ImageProcessor);
serviceCollection.AddSingleton(ImageProcessor);
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
RegisterSingleInstance(TVSeriesManager);
serviceCollection.AddSingleton(TVSeriesManager);
var encryptionManager = new EncryptionManager();
RegisterSingleInstance<IEncryptionManager>(encryptionManager);
serviceCollection.AddSingleton<IEncryptionManager>(encryptionManager);
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager);
RegisterSingleInstance(DeviceManager);
serviceCollection.AddSingleton(DeviceManager);
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder);
RegisterSingleInstance(MediaSourceManager);
serviceCollection.AddSingleton(MediaSourceManager);
SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager);
RegisterSingleInstance(SubtitleManager);
serviceCollection.AddSingleton(SubtitleManager);
ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
RegisterSingleInstance(ProviderManager);
serviceCollection.AddSingleton(ProviderManager);
DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager);
RegisterSingleInstance(DtoService);
serviceCollection.AddSingleton(DtoService);
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
RegisterSingleInstance(ChannelManager);
serviceCollection.AddSingleton(ChannelManager);
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
RegisterSingleInstance(SessionManager);
serviceCollection.AddSingleton(SessionManager);
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo);
RegisterSingleInstance<IDlnaManager>(dlnaManager);
serviceCollection.AddSingleton<IDlnaManager>(
new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo));
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
RegisterSingleInstance(CollectionManager);
serviceCollection.AddSingleton(CollectionManager);
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
RegisterSingleInstance(PlaylistManager);
serviceCollection.AddSingleton(PlaylistManager);
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
RegisterSingleInstance(LiveTvManager);
serviceCollection.AddSingleton(LiveTvManager);
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
RegisterSingleInstance(UserViewManager);
serviceCollection.AddSingleton(UserViewManager);
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
RegisterSingleInstance(NotificationManager);
serviceCollection.AddSingleton(NotificationManager);
RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
serviceCollection.AddSingleton<IDeviceDiscovery>(
new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
RegisterSingleInstance(ChapterManager);
serviceCollection.AddSingleton(ChapterManager);
RegisterMediaEncoder(assemblyInfo);
RegisterMediaEncoder(serviceCollection);
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
RegisterSingleInstance(EncodingManager);
serviceCollection.AddSingleton(EncodingManager);
var activityLogRepo = GetActivityLogRepository();
RegisterSingleInstance(activityLogRepo);
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
serviceCollection.AddSingleton(activityLogRepo);
serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
RegisterSingleInstance<IAuthorizationContext>(authContext);
RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager);
RegisterSingleInstance(AuthService);
serviceCollection.AddSingleton(AuthService);
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
RegisterSingleInstance(SubtitleEncoder);
serviceCollection.AddSingleton(SubtitleEncoder);
RegisterSingleInstance(CreateResourceFileManager());
serviceCollection.AddSingleton(CreateResourceFileManager());
displayPreferencesRepo.Initialize();
@ -935,6 +822,8 @@ namespace Emby.Server.Implementations
((UserDataManager)UserDataManager).Repository = userDataRepo;
ItemRepository.Initialize(userDataRepo, UserManager);
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
_serviceProvider = serviceCollection.BuildServiceProvider();
}
protected virtual IBrotliCompressor CreateBrotliCompressor()
@ -1066,7 +955,7 @@ namespace Emby.Server.Implementations
/// Registers the media encoder.
/// </summary>
/// <returns>Task.</returns>
private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo)
private void RegisterMediaEncoder(IServiceCollection serviceCollection)
{
string encoderPath = null;
string probePath = null;
@ -1098,7 +987,7 @@ namespace Emby.Server.Implementations
5000);
MediaEncoder = mediaEncoder;
RegisterSingleInstance(MediaEncoder);
serviceCollection.AddSingleton(MediaEncoder);
}
/// <summary>
@ -1174,7 +1063,10 @@ namespace Emby.Server.Implementations
}
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
Plugins = GetExportsWithInfo<IPlugin>().Select(LoadPlugin).Where(i => i != null).ToArray();
Plugins = GetExports<IPlugin>()
.Select(LoadPlugin)
.Where(i => i != null)
.ToArray();
HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>());
@ -1208,19 +1100,15 @@ namespace Emby.Server.Implementations
IsoManager.AddParts(GetExports<IIsoMounter>());
}
private IPlugin LoadPlugin(Tuple<IPlugin, string> info)
private IPlugin LoadPlugin(IPlugin plugin)
{
var plugin = info.Item1;
var assemblyFilePath = info.Item2;
try
{
var assemblyPlugin = plugin as IPluginAssembly;
if (assemblyPlugin != null)
if (plugin is IPluginAssembly assemblyPlugin)
{
var assembly = plugin.GetType().Assembly;
var assemblyName = assembly.GetName();
var assemblyFilePath = assembly.Location;
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
@ -1264,78 +1152,15 @@ namespace Emby.Server.Implementations
{
Logger.LogInformation("Loading assemblies");
var assemblyInfos = GetComposablePartAssemblies();
foreach (var assemblyInfo in assemblyInfos)
{
var assembly = assemblyInfo.Item1;
var path = assemblyInfo.Item2;
if (path == null)
{
Logger.LogInformation("Loading {assemblyName}", assembly.FullName);
}
else
AllConcreteTypes = GetComposablePartAssemblies()
.SelectMany(x => x.ExportedTypes)
.Where(type =>
{
Logger.LogInformation("Loading {assemblyName} from {path}", assembly.FullName, path);
}
}
AllConcreteTypes = assemblyInfos
.SelectMany(GetTypes)
.Where(info =>
{
var t = info.Item1;
return t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType;
return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType;
})
.ToArray();
}
/// <summary>
/// Gets a list of types within an assembly
/// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference
/// </summary>
protected List<Tuple<Type, string>> GetTypes(Tuple<Assembly, string> assemblyInfo)
{
if (assemblyInfo == null)
{
return new List<Tuple<Type, string>>();
}
var assembly = assemblyInfo.Item1;
try
{
// This null checking really shouldn't be needed but adding it due to some
// unhandled exceptions in mono 5.0 that are a little hard to hunt down
var types = assembly.GetTypes() ?? new Type[] { };
return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList();
}
catch (ReflectionTypeLoadException ex)
{
if (ex.LoaderExceptions != null)
{
foreach (var loaderException in ex.LoaderExceptions)
{
if (loaderException != null)
{
Logger.LogError("LoaderException: " + loaderException.Message);
}
}
}
// If it fails we can still get a list of the Types it was able to resolve
var types = ex.Types ?? new Type[] { };
return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading types from assembly");
return new List<Tuple<Type, string>>();
}
}
private CertificateInfo CertificateInfo { get; set; }
protected X509Certificate Certificate { get; private set; }
@ -1546,150 +1371,63 @@ namespace Emby.Server.Implementations
/// Gets the composable part assemblies.
/// </summary>
/// <returns>IEnumerable{Assembly}.</returns>
protected List<Tuple<Assembly, string>> GetComposablePartAssemblies()
protected IEnumerable<Assembly> GetComposablePartAssemblies()
{
var list = GetPluginAssemblies(ApplicationPaths.PluginsPath);
// Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
// This will prevent the .dll file from getting locked, and allow us to replace it when needed
if (Directory.Exists(ApplicationPaths.PluginsPath))
{
foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly))
{
Logger.LogInformation("Loading assembly {Path}", file);
yield return Assembly.LoadFrom(file);
}
}
// Include composable parts in the Api assembly
list.Add(GetAssembly(typeof(ApiEntryPoint)));
yield return typeof(ApiEntryPoint).Assembly;
// Include composable parts in the Dashboard assembly
list.Add(GetAssembly(typeof(DashboardService)));
yield return typeof(DashboardService).Assembly;
// Include composable parts in the Model assembly
list.Add(GetAssembly(typeof(SystemInfo)));
yield return typeof(SystemInfo).Assembly;
// Include composable parts in the Common assembly
list.Add(GetAssembly(typeof(IApplicationHost)));
yield return typeof(IApplicationHost).Assembly;
// Include composable parts in the Controller assembly
list.Add(GetAssembly(typeof(IServerApplicationHost)));
yield return typeof(IServerApplicationHost).Assembly;
// Include composable parts in the Providers assembly
list.Add(GetAssembly(typeof(ProviderUtils)));
yield return typeof(ProviderUtils).Assembly;
// Include composable parts in the Photos assembly
list.Add(GetAssembly(typeof(PhotoProvider)));
yield return typeof(PhotoProvider).Assembly;
// Emby.Server implementations
list.Add(GetAssembly(typeof(InstallationManager)));
yield return typeof(InstallationManager).Assembly;
// MediaEncoding
list.Add(GetAssembly(typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder)));
yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
// Dlna
list.Add(GetAssembly(typeof(DlnaEntryPoint)));
yield return typeof(DlnaEntryPoint).Assembly;
// Local metadata
list.Add(GetAssembly(typeof(BoxSetXmlSaver)));
yield return typeof(BoxSetXmlSaver).Assembly;
// Notifications
list.Add(GetAssembly(typeof(NotificationManager)));
yield return typeof(NotificationManager).Assembly;
// Xbmc
list.Add(GetAssembly(typeof(ArtistNfoProvider)));
list.AddRange(GetAssembliesWithPartsInternal().Select(i => new Tuple<Assembly, string>(i, null)));
yield return typeof(ArtistNfoProvider).Assembly;
return list.ToList();
}
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
private List<Tuple<Assembly, string>> GetPluginAssemblies(string path)
{
try
foreach (var i in GetAssembliesWithPartsInternal())
{
return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly))
.Select(LoadAssembly)
.Where(a => a != null)
.ToList();
}
catch (DirectoryNotFoundException)
{
return new List<Tuple<Assembly, string>>();
yield return i;
}
}
private IEnumerable<string> FilterAssembliesToLoad(IEnumerable<string> paths)
{
var exclude = new[]
{
"mbplus.dll",
"mbintros.dll",
"embytv.dll",
"Messenger.dll",
"Messages.dll",
"MediaBrowser.Plugins.TvMazeProvider.dll",
"MBBookshelf.dll",
"MediaBrowser.Channels.Adult.YouJizz.dll",
"MediaBrowser.Channels.Vine-co.dll",
"MediaBrowser.Plugins.Vimeo.dll",
"MediaBrowser.Channels.Vevo.dll",
"MediaBrowser.Plugins.Twitch.dll",
"MediaBrowser.Channels.SvtPlay.dll",
"MediaBrowser.Plugins.SoundCloud.dll",
"MediaBrowser.Plugins.SnesBox.dll",
"MediaBrowser.Plugins.RottenTomatoes.dll",
"MediaBrowser.Plugins.Revision3.dll",
"MediaBrowser.Plugins.NesBox.dll",
"MBChapters.dll",
"MediaBrowser.Channels.LeagueOfLegends.dll",
"MediaBrowser.Plugins.ADEProvider.dll",
"MediaBrowser.Channels.BallStreams.dll",
"MediaBrowser.Channels.Adult.Beeg.dll",
"ChannelDownloader.dll",
"Hamstercat.Emby.EmbyBands.dll",
"EmbyTV.dll",
"MediaBrowser.Channels.HitboxTV.dll",
"MediaBrowser.Channels.HockeyStreams.dll",
"MediaBrowser.Plugins.ITV.dll",
"MediaBrowser.Plugins.Lastfm.dll",
"ServerRestart.dll",
"MediaBrowser.Plugins.NotifyMyAndroidNotifications.dll",
"MetadataViewer.dll"
};
var minRequiredVersions = new Dictionary<string, Version>(StringComparer.OrdinalIgnoreCase)
{
{ "moviethemesongs.dll", new Version(1, 6) },
{ "themesongs.dll", new Version(1, 2) }
};
return paths.Where(path =>
{
var filename = Path.GetFileName(path);
if (exclude.Contains(filename ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (minRequiredVersions.TryGetValue(filename, out Version minRequiredVersion))
{
try
{
var version = Version.Parse(FileVersionInfo.GetVersionInfo(path).FileVersion);
if (version < minRequiredVersion)
{
Logger.LogInformation("Not loading {filename} {version} because the minimum supported version is {minRequiredVersion}. Please update to the newer version", filename, version, minRequiredVersion);
return false;
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error getting version number from {path}", path);
return false;
}
}
return true;
});
}
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
/// <summary>
/// Gets the system status.
@ -1718,7 +1456,7 @@ namespace Emby.Server.Implementations
SupportsHttps = SupportsHttps,
HttpsPortNumber = HttpsPort,
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
OperatingSystemDisplayName = OperatingSystemDisplayName,
OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName,
CanSelfRestart = CanSelfRestart,
CanSelfUpdate = CanSelfUpdate,
CanLaunchWebBrowser = CanLaunchWebBrowser,
@ -1788,7 +1526,7 @@ namespace Emby.Server.Implementations
public async Task<string> GetWanApiUrl(CancellationToken cancellationToken)
{
var url = "http://ipv4.icanhazip.com";
const string url = "http://ipv4.icanhazip.com";
try
{
using (var response = await HttpClient.Get(new HttpRequestOptions

@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
{
class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly IChannelManager _channelManager;
private readonly IUserManager _userManager;

@ -22,11 +22,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
<PackageReference Include="sharpcompress" Version="0.22.0" />
<PackageReference Include="SimpleInjector" Version="4.4.2" />
<PackageReference Include="SQLitePCL.pretty.core" Version="1.1.8" />
<PackageReference Include="SQLitePCLRaw.core" Version="1.1.11" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
</ItemGroup>

@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
class UserDataChangeNotifier : IServerEntryPoint
public class UserDataChangeNotifier : IServerEntryPoint
{
private readonly ISessionManager _sessionManager;
private readonly ILogger _logger;

@ -9,7 +9,7 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
class SpecialFolderResolver : FolderResolver<Folder>
public class SpecialFolderResolver : FolderResolver<Folder>
{
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;

@ -157,56 +157,56 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null;
string numberString = null;
string attributeValue;
double doubleValue;
// Check for channel number with the format from SatIp
// #EXTINF:0,84. VOX Schweiz
// #EXTINF:0,84.0 - VOX Schweiz
if (!string.IsNullOrWhiteSpace(nameInExtInf))
if (attributes.TryGetValue("tvg-chno", out attributeValue))
{
var numberIndex = nameInExtInf.IndexOf(' ');
if (numberIndex > 0)
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
{
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
{
numberString = numberPart;
}
numberString = attributeValue;
}
}
if (!string.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
if (!IsValidChannelNumber(numberString))
{
if (attributes.TryGetValue("tvg-id", out string value))
if (attributes.TryGetValue("tvg-id", out attributeValue))
{
if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleValue))
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
{
numberString = value;
numberString = attributeValue;
}
else if (attributes.TryGetValue("channel-id", out attributeValue))
{
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
{
numberString = attributeValue;
}
}
}
}
if (!string.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
if (!IsValidChannelNumber(numberString))
{
if (attributes.TryGetValue("channel-id", out string value))
if (String.IsNullOrWhiteSpace(numberString))
{
numberString = value;
// Using this as a fallback now as this leads to Problems with channels like "5 USA"
// where 5 isnt ment to be the channel number
// Check for channel number with the format from SatIp
// #EXTINF:0,84. VOX Schweiz
// #EXTINF:0,84.0 - VOX Schweiz
if (!string.IsNullOrWhiteSpace(nameInExtInf))
{
var numberIndex = nameInExtInf.IndexOf(' ');
if (numberIndex > 0)
{
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
{
numberString = numberPart;
}
}
}
}
}
if (!string.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
if (!IsValidChannelNumber(numberString))
@ -214,7 +214,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
numberString = null;
}
if (string.IsNullOrWhiteSpace(numberString))
if (!string.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
else
{
if (string.IsNullOrWhiteSpace(mediaUrl))
{

@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var now = DateTime.UtcNow;
var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
//OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile;

@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <summary>
/// Class ChapterImagesTask
/// </summary>
class ChapterImagesTask : IScheduledTask
public class ChapterImagesTask : IScheduledTask
{
/// <summary>
/// The _logger

@ -15,21 +15,17 @@ namespace Emby.Server.Implementations
/// </summary>
public ServerApplicationPaths(
string programDataPath,
string appFolderPath,
string applicationResourcesPath,
string logDirectoryPath = null,
string configurationDirectoryPath = null,
string cacheDirectoryPath = null)
string logDirectoryPath,
string configurationDirectoryPath,
string cacheDirectoryPath)
: base(programDataPath,
appFolderPath,
logDirectoryPath,
configurationDirectoryPath,
cacheDirectoryPath)
{
ApplicationResourcesPath = applicationResourcesPath;
}
public string ApplicationResourcesPath { get; private set; }
public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
/// <summary>
/// Gets the path to the base root media directory
@ -148,7 +144,6 @@ namespace Emby.Server.Implementations
set => _internalMetadataPath = value;
}
private const string _virtualInternalMetadataPath = "%MetadataPath%";
public string VirtualInternalMetadataPath => _virtualInternalMetadataPath;
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
}
}

@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
class AiredEpisodeOrderComparer : IBaseItemComparer
public class AiredEpisodeOrderComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.

@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
class SeriesSortNameComparer : IBaseItemComparer
public class SeriesSortNameComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.

@ -282,7 +282,7 @@ namespace Jellyfin.Drawing.Skia
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
// decode
var _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
_ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
origin = codec.EncodedOrigin;

@ -18,15 +18,17 @@ namespace Jellyfin.Server
public override bool CanSelfRestart => StartupOptions.RestartPath != null;
protected override bool SupportsDualModeSockets => true;
protected override void RestartInternal() => Program.Restart();
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
=> new[] { typeof(CoreAppHost).Assembly };
{
yield return typeof(CoreAppHost).Assembly;
}
protected override void ShutdownInternal() => Program.Shutdown();
protected override bool SupportsDualModeSockets => true;
protected override IHttpListener CreateHttpListener()
=> new WebSocketSharpListener(
Logger,
@ -37,7 +39,6 @@ namespace Jellyfin.Server
CryptographyProvider,
SupportsDualModeSockets,
FileSystemManager,
EnvironmentInfo
);
EnvironmentInfo);
}
}

@ -5,11 +5,14 @@
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<!-- We need C# 7.1 for async main-->
<LangVersion>latest</LangVersion>
<!-- Disable documentation warnings (for now) -->
<NoWarn>SA1600;CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -20,6 +23,10 @@
<EmbeddedResource Include="Resources/Configuration/*" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
@ -41,9 +48,8 @@
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.12" />
<PackageReference Include="SQLitePCLRaw.core" Version="1.1.12" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.12" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.13" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.13" />
</ItemGroup>
<ItemGroup>

@ -21,6 +21,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.AspNetCore;
@ -56,13 +57,28 @@ namespace Jellyfin.Server
errs => Task.FromResult(0)).ConfigureAwait(false);
}
public static void Shutdown()
{
if (!_tokenSource.IsCancellationRequested)
{
_tokenSource.Cancel();
}
}
public static void Restart()
{
_restartOnShutdown = true;
Shutdown();
}
private static async Task StartApp(StartupOptions options)
{
ServerApplicationPaths appPaths = CreateApplicationPaths(options);
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
await CreateLogger(appPaths);
await CreateLogger(appPaths).ConfigureAwait(false);
_logger = _loggerFactory.CreateLogger("Main");
AppDomain.CurrentDomain.UnhandledException += (sender, e)
@ -75,6 +91,7 @@ namespace Jellyfin.Server
{
return; // Already shutting down
}
e.Cancel = true;
_logger.LogInformation("Ctrl+C, shutting down");
Environment.ExitCode = 128 + 2;
@ -88,6 +105,7 @@ namespace Jellyfin.Server
{
return; // Already shutting down
}
_logger.LogInformation("Received a SIGTERM signal, shutting down");
Environment.ExitCode = 128 + 15;
Shutdown();
@ -101,7 +119,7 @@ namespace Jellyfin.Server
SQLitePCL.Batteries_V2.Init();
// Allow all https requests
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; } );
var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, null, appPaths.TempDirectory, true);
@ -114,18 +132,18 @@ namespace Jellyfin.Server
new NullImageEncoder(),
new NetworkManager(_loggerFactory, environmentInfo)))
{
await appHost.Init();
await appHost.Init(new ServiceCollection()).ConfigureAwait(false);
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
await appHost.RunStartupTasks();
await appHost.RunStartupTasks().ConfigureAwait(false);
// TODO: read input for a stop command
try
{
// Block main thread until shutdown
await Task.Delay(-1, _tokenSource.Token);
await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
@ -139,112 +157,156 @@ namespace Jellyfin.Server
}
}
/// <summary>
/// Create the data, config and log paths from the variety of inputs(command line args,
/// environment variables) or decide on what default to use. For Windows it's %AppPath%
/// for everything else the XDG approach is followed:
/// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
/// </summary>
/// <param name="options">StartupOptions</param>
/// <returns>ServerApplicationPaths</returns>
private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
{
string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
if (string.IsNullOrEmpty(programDataPath))
// dataDir
// IF --datadir
// ELSE IF $JELLYFIN_DATA_PATH
// ELSE IF windows, use <%APPDATA%>/jellyfin
// ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin
// ELSE use $HOME/.local/share/jellyfin
var dataDir = options.DataDir;
if (string.IsNullOrEmpty(dataDir))
{
if (options.DataDir != null)
{
programDataPath = options.DataDir;
}
else
dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
if (string.IsNullOrEmpty(dataDir))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
}
else
{
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
// If $XDG_DATA_HOME is either not set or empty, $HOME/.local/share should be used.
if (string.IsNullOrEmpty(programDataPath))
dataDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
if (string.IsNullOrEmpty(dataDir))
{
programDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
}
}
programDataPath = Path.Combine(programDataPath, "jellyfin");
dataDir = Path.Combine(dataDir, "jellyfin");
}
}
if (string.IsNullOrEmpty(programDataPath))
{
Console.WriteLine("Cannot continue without path to program data folder (try -programdata)");
Environment.Exit(1);
}
else
{
Directory.CreateDirectory(programDataPath);
}
// configDir
// IF --configdir
// ELSE IF $JELLYFIN_CONFIG_DIR
// ELSE IF --datadir, use <datadir>/config (assume portable run)
// ELSE IF <datadir>/config exists, use that
// ELSE IF windows, use <datadir>/config
// ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
// ELSE $HOME/.config/jellyfin
var configDir = options.ConfigDir;
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{
if (options.ConfigDir != null)
{
configDir = options.ConfigDir;
}
else
configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{
// Let BaseApplicationPaths set up the default value
configDir = null;
if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Hang config folder off already set dataDir
configDir = Path.Combine(dataDir, "config");
}
else
{
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
configDir = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME /.config should be used.
if (string.IsNullOrEmpty(configDir))
{
configDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
}
configDir = Path.Combine(configDir, "jellyfin");
}
}
}
if (configDir != null)
{
Directory.CreateDirectory(configDir);
}
// cacheDir
// IF --cachedir
// ELSE IF $JELLYFIN_CACHE_DIR
// ELSE IF windows, use <datadir>/cache
// ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin
// ELSE HOME/.cache/jellyfin
var cacheDir = options.CacheDir;
string cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
if (string.IsNullOrEmpty(cacheDir))
{
if (options.CacheDir != null)
{
cacheDir = options.CacheDir;
}
else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
if (string.IsNullOrEmpty(cacheDir))
{
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
// If $XDG_CACHE_HOME is either not set or empty, $HOME/.cache should be used.
if (string.IsNullOrEmpty(cacheDir))
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
// Hang cache folder off already set dataDir
cacheDir = Path.Combine(dataDir, "cache");
}
else
{
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
if (string.IsNullOrEmpty(cacheDir))
{
cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
}
cacheDir = Path.Combine(cacheDir, "jellyfin");
}
cacheDir = Path.Combine(cacheDir, "jellyfin");
}
}
if (cacheDir != null)
{
Directory.CreateDirectory(cacheDir);
}
// logDir
// IF --logdir
// ELSE IF $JELLYFIN_LOG_DIR
// ELSE IF --datadir, use <datadir>/log (assume portable run)
// ELSE <datadir>/log
var logDir = options.LogDir;
string logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
if (string.IsNullOrEmpty(logDir))
{
if (options.LogDir != null)
{
logDir = options.LogDir;
}
else
logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
if (string.IsNullOrEmpty(logDir))
{
// Let BaseApplicationPaths set up the default value
logDir = null;
// Hang log folder off already set dataDir
logDir = Path.Combine(dataDir, "log");
}
}
if (logDir != null)
// Ensure the main folders exist before we continue
try
{
Directory.CreateDirectory(dataDir);
Directory.CreateDirectory(logDir);
Directory.CreateDirectory(configDir);
Directory.CreateDirectory(cacheDir);
}
catch (IOException ex)
{
Console.Error.WriteLine("Error whilst attempting to create folder");
Console.Error.WriteLine(ex.ToString());
Environment.Exit(1);
}
string appPath = AppContext.BaseDirectory;
return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir);
return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir);
}
private static async Task CreateLogger(IApplicationPaths appPaths)
@ -263,6 +325,7 @@ namespace Jellyfin.Server
await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
}
}
var configuration = new ConfigurationBuilder()
.SetBasePath(appPaths.ConfigurationDirectoryPath)
.AddJsonFile("logging.json")
@ -290,7 +353,7 @@ namespace Jellyfin.Server
}
}
public static IImageEncoder GetImageEncoder(
private static IImageEncoder GetImageEncoder(
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILocalizationManager localizationManager)
@ -331,26 +394,12 @@ namespace Jellyfin.Server
{
return MediaBrowser.Model.System.OperatingSystem.BSD;
}
throw new Exception($"Can't resolve OS with description: '{osDescription}'");
}
}
}
public static void Shutdown()
{
if (!_tokenSource.IsCancellationRequested)
{
_tokenSource.Cancel();
}
}
public static void Restart()
{
_restartOnShutdown = true;
Shutdown();
}
private static void StartNewInstance(StartupOptions options)
{
_logger.LogInformation("Starting new instance");

@ -13,7 +13,7 @@ namespace Jellyfin.Server.SocketSharp
{
internal static string GetParameter(string header, string attr)
{
int ap = header.IndexOf(attr);
int ap = header.IndexOf(attr, StringComparison.Ordinal);
if (ap == -1)
{
return null;
@ -82,9 +82,7 @@ namespace Jellyfin.Server.SocketSharp
}
else
{
//
// We use a substream, as in 2.x we will support large uploads streamed to disk,
//
var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
files[e.Name] = sub;
}
@ -127,8 +125,12 @@ namespace Jellyfin.Server.SocketSharp
public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
protected bool validate_cookies, validate_query_string, validate_form;
protected bool checked_cookies, checked_query_string, checked_form;
protected bool validate_cookies { get; set; }
protected bool validate_query_string { get; set; }
protected bool validate_form { get; set; }
protected bool checked_cookies { get; set; }
protected bool checked_query_string { get; set; }
protected bool checked_form { get; set; }
private static void ThrowValidationException(string name, string key, string value)
{
@ -138,8 +140,12 @@ namespace Jellyfin.Server.SocketSharp
v = v.Substring(0, 16) + "...\"";
}
string msg = string.Format("A potentially dangerous Request.{0} value was " +
"detected from the client ({1}={2}).", name, key, v);
string msg = string.Format(
CultureInfo.InvariantCulture,
"A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
name,
key,
v);
throw new Exception(msg);
}
@ -179,6 +185,7 @@ namespace Jellyfin.Server.SocketSharp
for (int idx = 1; idx < len; idx++)
{
char next = val[idx];
// See http://secunia.com/advisories/14325
if (current == '<' || current == '\xff1c')
{
@ -256,6 +263,7 @@ namespace Jellyfin.Server.SocketSharp
value.Append((char)c);
}
}
if (c == -1)
{
AddRawKeyValue(form, key, value);
@ -271,6 +279,7 @@ namespace Jellyfin.Server.SocketSharp
key.Append((char)c);
}
}
if (c == -1)
{
AddRawKeyValue(form, key, value);
@ -308,6 +317,7 @@ namespace Jellyfin.Server.SocketSharp
result.Append(key);
result.Append('=');
}
result.Append(pair.Value);
}
@ -429,13 +439,13 @@ namespace Jellyfin.Server.SocketSharp
real = position + d;
break;
default:
throw new ArgumentException(nameof(origin));
throw new ArgumentException("Unknown SeekOrigin value", nameof(origin));
}
long virt = real - offset;
if (virt < 0 || virt > Length)
{
throw new ArgumentException();
throw new ArgumentException("Invalid position", nameof(d));
}
position = s.Seek(real, SeekOrigin.Begin);
@ -491,11 +501,6 @@ namespace Jellyfin.Server.SocketSharp
public Stream InputStream => stream;
}
private class Helpers
{
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
}
internal static class StrUtils
{
public static bool StartsWith(string str1, string str2, bool ignore_case)
@ -533,12 +538,17 @@ namespace Jellyfin.Server.SocketSharp
public class Element
{
public string ContentType;
public string Name;
public string Filename;
public Encoding Encoding;
public long Start;
public long Length;
public string ContentType { get; set; }
public string Name { get; set; }
public string Filename { get; set; }
public Encoding Encoding { get; set; }
public long Start { get; set; }
public long Length { get; set; }
public override string ToString()
{
@ -547,15 +557,23 @@ namespace Jellyfin.Server.SocketSharp
}
}
private const byte LF = (byte)'\n';
private const byte CR = (byte)'\r';
private Stream data;
private string boundary;
private byte[] boundary_bytes;
private byte[] boundaryBytes;
private byte[] buffer;
private bool at_eof;
private bool atEof;
private Encoding encoding;
private StringBuilder sb;
private const byte LF = (byte)'\n', CR = (byte)'\r';
private StringBuilder sb;
// See RFC 2046
// In the case of multipart entities, in which one or more different
@ -570,18 +588,48 @@ namespace Jellyfin.Server.SocketSharp
public HttpMultipart(Stream data, string b, Encoding encoding)
{
this.data = data;
//DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET
//var ms = new MemoryStream(32 * 1024);
//data.CopyTo(ms);
//this.data = ms;
boundary = b;
boundary_bytes = encoding.GetBytes(b);
buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--'
boundaryBytes = encoding.GetBytes(b);
buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
this.encoding = encoding;
sb = new StringBuilder();
}
public Element ReadNextElement()
{
if (atEof || ReadBoundary())
{
return null;
}
var elem = new Element();
string header;
while ((header = ReadHeaders()) != null)
{
if (StrUtils.StartsWith(header, "Content-Disposition:", true))
{
elem.Name = GetContentDispositionAttribute(header, "name");
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
}
else if (StrUtils.StartsWith(header, "Content-Type:", true))
{
elem.ContentType = header.Substring("Content-Type:".Length).Trim();
elem.Encoding = GetEncoding(elem.ContentType);
}
}
long start = data.Position;
elem.Start = start;
long pos = MoveToNextBoundary();
if (pos == -1)
{
return null;
}
elem.Length = pos - start;
return elem;
}
private string ReadLine()
{
// CRLF or LF are ok as line endings.
@ -600,6 +648,7 @@ namespace Jellyfin.Server.SocketSharp
{
break;
}
got_cr = b == CR;
sb.Append((char)b);
}
@ -769,7 +818,7 @@ namespace Jellyfin.Server.SocketSharp
return -1;
}
if (!CompareBytes(boundary_bytes, buffer))
if (!CompareBytes(boundaryBytes, buffer))
{
state = 0;
data.Position = retval + 2;
@ -785,7 +834,7 @@ namespace Jellyfin.Server.SocketSharp
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
{
at_eof = true;
atEof = true;
}
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
{
@ -800,6 +849,7 @@ namespace Jellyfin.Server.SocketSharp
c = data.ReadByte();
continue;
}
data.Position = retval + 2;
if (got_cr)
{
@ -818,42 +868,6 @@ namespace Jellyfin.Server.SocketSharp
return retval;
}
public Element ReadNextElement()
{
if (at_eof || ReadBoundary())
{
return null;
}
var elem = new Element();
string header;
while ((header = ReadHeaders()) != null)
{
if (StrUtils.StartsWith(header, "Content-Disposition:", true))
{
elem.Name = GetContentDispositionAttribute(header, "name");
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
}
else if (StrUtils.StartsWith(header, "Content-Type:", true))
{
elem.ContentType = header.Substring("Content-Type:".Length).Trim();
elem.Encoding = GetEncoding(elem.ContentType);
}
}
long start = 0;
start = data.Position;
elem.Start = start;
long pos = MoveToNextBoundary();
if (pos == -1)
{
return null;
}
elem.Length = pos - start;
return elem;
}
private static string StripPath(string path)
{
if (path == null || path.Length == 0)

@ -24,6 +24,7 @@ namespace Jellyfin.Server.SocketSharp
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed = false;
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
{
@ -40,9 +41,9 @@ namespace Jellyfin.Server.SocketSharp
_logger = logger;
WebSocket = socket;
socket.OnMessage += socket_OnMessage;
socket.OnClose += socket_OnClose;
socket.OnError += socket_OnError;
socket.OnMessage += OnSocketMessage;
socket.OnClose += OnSocketClose;
socket.OnError += OnSocketError;
}
public Task ConnectAsServerAsync()
@ -53,29 +54,22 @@ namespace Jellyfin.Server.SocketSharp
return _taskCompletionSource.Task;
}
void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e)
private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e)
{
_logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty);
//Closed?.Invoke(this, EventArgs.Empty);
// Closed?.Invoke(this, EventArgs.Empty);
}
void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e)
private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e)
{
_taskCompletionSource.TrySetResult(true);
Closed?.Invoke(this, EventArgs.Empty);
}
void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e)
private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e)
{
//if (!string.IsNullOrEmpty(e.Data))
//{
// if (OnReceive != null)
// {
// OnReceive(e.Data);
// }
// return;
//}
if (OnReceiveBytes != null)
{
OnReceiveBytes(e.RawData);
@ -118,6 +112,7 @@ namespace Jellyfin.Server.SocketSharp
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
@ -126,16 +121,23 @@ namespace Jellyfin.Server.SocketSharp
/// <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)
{
WebSocket.OnMessage -= socket_OnMessage;
WebSocket.OnClose -= socket_OnClose;
WebSocket.OnError -= socket_OnError;
WebSocket.OnMessage -= OnSocketMessage;
WebSocket.OnClose -= OnSocketClose;
WebSocket.OnError -= OnSocketError;
_cancellationTokenSource.Cancel();
WebSocket.CloseAsync().GetAwaiter().GetResult();
}
_disposed = true;
}
/// <summary>
@ -143,11 +145,5 @@ namespace Jellyfin.Server.SocketSharp
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; }
/// <summary>
/// Gets or sets the on receive.
/// </summary>
/// <value>The on receive.</value>
public Action<string> OnReceive { get; set; }
}
}

@ -91,8 +91,11 @@ namespace Jellyfin.Server.SocketSharp
{
var url = request.Url.ToString();
logger.LogInformation("{0} {1}. UserAgent: {2}",
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
logger.LogInformation(
"{0} {1}. UserAgent: {2}",
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod,
url,
request.UserAgent ?? string.Empty);
}
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
@ -200,7 +203,7 @@ namespace Jellyfin.Server.SocketSharp
}
catch (ObjectDisposedException)
{
//TODO Investigate and properly fix.
// TODO: Investigate and properly fix.
}
catch (Exception ex)
{
@ -222,38 +225,39 @@ namespace Jellyfin.Server.SocketSharp
public Task Stop()
{
_disposeCancellationTokenSource.Cancel();
if (_listener != null)
{
_listener.Close();
}
_listener?.Close();
return Task.CompletedTask;
}
/// <summary>
/// Releases the unmanaged resources and disposes of the managed resources used.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool _disposed;
private readonly object _disposeLock = new object();
/// <summary>
/// Releases the unmanaged resources and disposes of the managed resources used.
/// </summary>
/// <param name="disposing">Whether or not the managed resources should be disposed</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
lock (_disposeLock)
if (_disposed)
{
if (_disposed) return;
if (disposing)
{
Stop();
}
return;
}
//release unmanaged resources here...
_disposed = true;
if (disposing)
{
Stop().GetAwaiter().GetResult();
}
_disposed = true;
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using Emby.Server.Implementations.HttpServer;
@ -24,7 +25,7 @@ namespace Jellyfin.Server.SocketSharp
this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
//HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
// HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
}
private static string GetHandlerPathIfAny(string listenerUrl)
@ -41,7 +42,7 @@ namespace Jellyfin.Server.SocketSharp
}
var startHostUrl = listenerUrl.Substring(pos + "://".Length);
var endPos = startHostUrl.IndexOf('/');
var endPos = startHostUrl.IndexOf('/', StringComparison.Ordinal);
if (endPos == -1)
{
return null;
@ -69,9 +70,11 @@ namespace Jellyfin.Server.SocketSharp
public string UserHostAddress => request.UserHostAddress;
public string XForwardedFor => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
public string XForwardedFor
=> string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
public int? XForwardedPort => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]);
public int? XForwardedPort
=> string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture);
public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"];
@ -107,6 +110,7 @@ namespace Jellyfin.Server.SocketSharp
switch (crlf)
{
case 0:
{
if (c == '\r')
{
crlf = 1;
@ -121,29 +125,39 @@ namespace Jellyfin.Server.SocketSharp
{
throw new ArgumentException("net_WebHeaderInvalidControlChars");
}
break;
}
case 1:
{
if (c == '\n')
{
crlf = 2;
break;
}
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
case 2:
{
if (c == ' ' || c == '\t')
{
crlf = 0;
break;
}
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
}
}
if (crlf != 0)
{
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
return name;
}
@ -156,6 +170,7 @@ namespace Jellyfin.Server.SocketSharp
return true;
}
}
return false;
}
@ -343,6 +358,7 @@ namespace Jellyfin.Server.SocketSharp
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
this.pathInfo = NormalizePathInfo(pathInfo, mode);
}
return this.pathInfo;
}
}
@ -444,7 +460,7 @@ namespace Jellyfin.Server.SocketSharp
public string ContentType => request.ContentType;
public Encoding contentEncoding;
private Encoding contentEncoding;
public Encoding ContentEncoding
{
get => contentEncoding ?? request.ContentEncoding;
@ -502,6 +518,7 @@ namespace Jellyfin.Server.SocketSharp
i++;
}
}
return httpFiles;
}
}

@ -13,12 +13,12 @@ using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
using IRequest = MediaBrowser.Model.Services.IRequest;
namespace Jellyfin.Server.SocketSharp
{
public class WebSocketSharpResponse : IHttpResponse
{
private readonly ILogger _logger;
private readonly HttpListenerResponse _response;
public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request)
@ -30,7 +30,9 @@ namespace Jellyfin.Server.SocketSharp
}
public IRequest Request { get; private set; }
public Dictionary<string, object> Items { get; private set; }
public object OriginalResponse => _response;
public int StatusCode
@ -51,7 +53,7 @@ namespace Jellyfin.Server.SocketSharp
set => _response.ContentType = value;
}
//public ICookies Cookies { get; set; }
public QueryParamCollection Headers => _response.Headers;
public void AddHeader(string name, string value)
{
@ -64,8 +66,6 @@ namespace Jellyfin.Server.SocketSharp
_response.AddHeader(name, value);
}
public QueryParamCollection Headers => _response.Headers;
public string GetHeader(string name)
{
return _response.Headers[name];
@ -114,9 +114,9 @@ namespace Jellyfin.Server.SocketSharp
public void SetContentLength(long contentLength)
{
//you can happily set the Content-Length header in Asp.Net
//but HttpListener will complain if you do - you have to set ContentLength64 on the response.
//workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
// you can happily set the Content-Length header in Asp.Net
// but HttpListener will complain if you do - you have to set ContentLength64 on the response.
// workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
_response.ContentLength64 = contentLength;
}
@ -147,15 +147,12 @@ namespace Jellyfin.Server.SocketSharp
{
sb.Append($";domain={cookie.Domain}");
}
//else if (restrictAllCookiesToDomain != null)
//{
// sb.Append($";domain={restrictAllCookiesToDomain}");
//}
if (cookie.Secure)
{
sb.Append(";Secure");
}
if (cookie.HttpOnly)
{
sb.Append(";HttpOnly");
@ -164,7 +161,6 @@ namespace Jellyfin.Server.SocketSharp
return sb.ToString();
}
public bool SendChunked
{
get => _response.SendChunked;

@ -11,7 +11,7 @@ namespace MediaBrowser.Api.Session
/// <summary>
/// Class SessionInfoWebSocketListener
/// </summary>
class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>
public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>
{
/// <summary>
/// Gets the name.

@ -11,7 +11,7 @@ namespace MediaBrowser.Api.System
/// <summary>
/// Class SessionInfoWebSocketListener
/// </summary>
class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState>
public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState>
{
/// <summary>
/// Gets the name.

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Updates;
using Microsoft.Extensions.DependencyInjection;
namespace MediaBrowser.Common
{
@ -13,12 +14,6 @@ namespace MediaBrowser.Common
/// </summary>
public interface IApplicationHost
{
/// <summary>
/// Gets the display name of the operating system.
/// </summary>
/// <value>The display name of the operating system.</value>
string OperatingSystemDisplayName { get; }
/// <summary>
/// Gets the name.
/// </summary>
@ -104,13 +99,6 @@ namespace MediaBrowser.Common
/// <returns>``0.</returns>
T Resolve<T>();
/// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
T TryResolve<T>();
/// <summary>
/// Shuts down.
/// </summary>
@ -131,7 +119,7 @@ namespace MediaBrowser.Common
/// <summary>
/// Inits this instance.
/// </summary>
Task Init();
Task Init(IServiceCollection serviceCollection);
/// <summary>
/// Creates the instance.

@ -11,6 +11,10 @@
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>

@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Providers
{
class PlaylistXmlProvider : BaseXmlProvider<Playlist>
public class PlaylistXmlProvider : BaseXmlProvider<Playlist>
{
private readonly ILogger _logger;
private readonly IProviderManager _providerManager;

@ -24,12 +24,12 @@ namespace MediaBrowser.Model.System
/// Gets or sets the server version.
/// </summary>
/// <value>The version.</value>
public string Version { get; set; }
public string Version { get; set; }
/// <summary>
/// Gets or sets the operating sytem.
/// Gets or sets the operating system.
/// </summary>
/// <value>The operating sytem.</value>
/// <value>The operating system.</value>
public string OperatingSystem { get; set; }
/// <summary>

@ -1,3 +1,4 @@
using System;
using System.Runtime.InteropServices;
using MediaBrowser.Model.Updates;
@ -136,7 +137,7 @@ namespace MediaBrowser.Model.System
/// </summary>
public SystemInfo()
{
CompletedInstallations = new InstallationInfo[] { };
CompletedInstallations = Array.Empty<InstallationInfo>();
}
}
}

@ -14,7 +14,7 @@ using MediaBrowser.Providers.Movies;
namespace MediaBrowser.Providers.BoxSets
{
class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
public class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClient _httpClient;

@ -16,7 +16,7 @@ using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Movies
{
class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
public class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;

@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Music
{
class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo>
public class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo>
{
protected override void MergeData(MetadataResult<MusicVideo> source, MetadataResult<MusicVideo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{

@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Photos
{
class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo>
public class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo>
{
protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{

@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Photos
{
class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo>
public class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo>
{
protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{

@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Playlists
{
class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo>
public class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo>
{
protected override IList<BaseItem> GetChildrenForMetadataUpdates(Playlist item)
{

@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV.Omdb
{
class OmdbEpisodeProvider :
public class OmdbEpisodeProvider :
IRemoteMetadataProvider<Episode, EpisodeInfo>,
IHasOrder
{

@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV.TheMovieDb
{
class MovieDbEpisodeProvider :
public class MovieDbEpisodeProvider :
MovieDbProviderBase,
IRemoteMetadataProvider<Episode, EpisodeInfo>,
IHasOrder

@ -1 +1 @@
Subproject commit 094c1deae91c51b8bbf8ebb16a55758af110f04d
Subproject commit c7ce1ac8eccd50f1bd759b30fbe60ea797ffe86e

@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Parsers
{
class MovieNfoParser : BaseNfoParser<Video>
public class MovieNfoParser : BaseNfoParser<Video>
{
protected override bool SupportsUrlAfterClosingXmlTag => true;

24
build

@ -23,8 +23,9 @@ usage() {
echo -e "Usage:"
echo -e " $ build --list-platforms"
echo -e " $ build --list-actions <platform>"
echo -e " $ build [-b/--web-branch <web_branch>] <platform> <action>"
echo -e " $ build [-k/--keep-artifacts] [-b/--web-branch <web_branch>] <platform> <action>"
echo -e ""
echo -e "The 'keep-artifacts' option preserves build artifacts, e.g. Docker images for system package builds."
echo -e "The web_branch defaults to the same branch name as the current main branch."
echo -e "To build all platforms, use 'all'."
echo -e "To perform all build actions, use 'all'."
@ -67,6 +68,14 @@ if [[ $1 == '--list-actions' ]]; then
exit 0
fi
# Parse keep-artifacts option
if [[ $1 == '-k' || $1 == '--keep-artifacts' ]]; then
keep_artifacts="y"
shift 1
else
keep_artifacts="n"
fi
# Parse branch option
if [[ $1 == '-b' || $1 == '--web-branch' ]]; then
web_branch="$2"
@ -193,6 +202,13 @@ for target_platform in ${platform[@]}; do
echo -e "> Processing platform ${target_platform}"
date_start=$( date +%s )
pushd ${target_platform}
cleanup() {
echo -e ">> Processing action clean"
if [[ -f clean.sh && -x clean.sh ]]; then
./clean.sh ${keep_artifacts}
fi
}
trap cleanup EXIT INT
for target_action in ${action[@]}; do
echo -e ">> Processing action ${target_action}"
if [[ -f ${target_action}.sh && -x ${target_action}.sh ]]; then
@ -204,12 +220,8 @@ for target_platform in ${platform[@]}; do
target_dir="../../../jellyfin-build/${target_platform}"
mkdir -p ${target_dir}
mv pkg-dist/* ${target_dir}/
echo -e ">> Processing action clean"
if [[ -f clean.sh && -x clean.sh ]]; then
./clean.sh
fi
fi
cleanup
date_end=$( date +%s )
echo -e "> Completed platform ${target_platform} in $( expr ${date_end} - ${date_start} ) seconds."
popd

@ -55,6 +55,8 @@ These builds are not necessarily run from the `build` script, but are present fo
* The `clean` action should always `exit 0` even if no work is done or it fails.
* The `clean` action can be passed a variable as argument 1, named `keep_artifacts`, containing either the value `y` or `n`. It is indended to handle situations when the user runs `build --keep-artifacts` and should be handled intelligently. Usually, this is used to preserve Docker images while still removing temporary directories.
### Output Files
* Upon completion of the defined actions, at least one output file must be created in the `<platform>/pkg-dist` directory.

@ -1,15 +1,27 @@
FROM centos:7
ARG HOME=/build
RUN mkdir /build && \
yum install -y @buildsys-build rpmdevtools yum-plugins-core && \
rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm && \
rpmdev-setuptree
WORKDIR /build/rpmbuild
COPY ./deployment/centos-package-x64/pkg-src/jellyfin.spec SPECS
COPY ./deployment/centos-package-x64/pkg-src/ SOURCES
RUN spectool -g -R SPECS/jellyfin.spec && \
rpmbuild -bs SPECS/jellyfin.spec && \
yum-builddep -y SRPMS/jellyfin-*.src.rpm && \
rpmbuild -bb SPECS/jellyfin.spec;
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG PLATFORM_DIR=/jellyfin/deployment/centos-package-x64
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=2.2
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
# Prepare CentOS build environment
RUN yum update -y \
&& yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel \
&& rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \
&& rpmdev-setuptree \
&& yum install -y dotnet-sdk-${SDK_VERSION} \
&& ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \
&& mkdir -p ${SOURCE_DIR}/SPECS \
&& ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
&& mkdir -p ${SOURCE_DIR}/SOURCES \
&& ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES
VOLUME ${ARTIFACT_DIR}/
COPY . ${SOURCE_DIR}/
ENTRYPOINT ["/docker-build.sh"]

@ -1 +0,0 @@
../fedora-package-x64/clean.sh

@ -0,0 +1,34 @@
#!/usr/bin/env bash
source ../common.build.sh
keep_artifacts="${1}"
WORKDIR="$( pwd )"
VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )"
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
package_source_dir="${WORKDIR}/pkg-src"
output_dir="${WORKDIR}/pkg-dist"
current_user="$( whoami )"
image_name="jellyfin-centos-build"
rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \
|| sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null
rm -rf "${package_temporary_dir}" &>/dev/null \
|| sudo rm -rf "${package_temporary_dir}" &>/dev/null
rm -rf "${output_dir}" &>/dev/null \
|| sudo rm -rf "${output_dir}" &>/dev/null
if [[ ${keep_artifacts} == 'n' ]]; then
docker_sudo=""
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
&& [[ ! ${EUID:-1000} -eq 0 ]] \
&& [[ ! ${USER} == "root" ]] \
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
docker_sudo=sudo
fi
${docker_sudo} docker image rm ${image_name} --force
fi

@ -0,0 +1,20 @@
#!/bin/bash
# Builds the RPM inside the Docker container
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
ls -al SOURCES/pkg-src/
# Build RPM
spectool -g -R SPECS/jellyfin.spec
rpmbuild -bs SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/"
rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/"
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/rpm
mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/

@ -1 +0,0 @@
../fedora-package-x64/package.sh

@ -0,0 +1,80 @@
#!/usr/bin/env bash
source ../common.build.sh
WORKDIR="$( pwd )"
VERSION="$( grep '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )"
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
output_dir="${WORKDIR}/pkg-dist"
pkg_src_dir="${WORKDIR}/pkg-src"
current_user="$( whoami )"
image_name="jellyfin-centos-build"
# Determine if sudo should be used for Docker
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
&& [[ ! ${EUID:-1000} -eq 0 ]] \
&& [[ ! ${USER} == "root" ]] \
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
docker_sudo="sudo"
else
docker_sudo=""
fi
# Create RPM source archive
GNU_TAR=1
mkdir -p "${package_temporary_dir}"
echo "Bundling all sources for RPM build."
tar \
--transform "s,^\.,jellyfin-${VERSION}," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" \
-C "../.." ./ || GNU_TAR=0
if [ $GNU_TAR -eq 0 ]; then
echo "The installed tar binary did not support --transform. Using workaround."
mkdir -p "${package_temporary_dir}/jellyfin"
# Not GNU tar
tar \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-zcf \
"${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \
-C "../.." ./
echo "Extracting filtered package."
tar -xzf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}/jellyfin-${VERSION}"
echo "Removing filtered package."
rm -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz"
echo "Repackaging package into final tarball."
tar -czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}"
fi
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the RPMs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the RPMs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" \
|| sudo chown -R "${current_user}" "${package_temporary_dir}"
# Move the RPMs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/rpm/* "${output_dir}"

@ -1,23 +1,21 @@
FROM debian:9
ARG SOURCEDIR=/repo
FROM microsoft/dotnet:2.2-sdk-stretch
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-x64
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
# Prepare Debian build environment
RUN apt-get update \
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts \
&& wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg \
&& mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ \
&& wget -q https://packages.microsoft.com/config/debian/9/prod.list \
&& mv prod.list /etc/apt/sources.list.d/microsoft-prod.list \
&& chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg \
&& chown root:root /etc/apt/sources.list.d/microsoft-prod.list \
&& apt-get update
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev \
&& ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \
&& mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
WORKDIR ${SOURCEDIR}
COPY . .
COPY ./deployment/debian-package-x64/pkg-src ./debian
VOLUME ${ARTIFACT_DIR}/
RUN yes | mk-build-deps -i debian/control \
&& dpkg-buildpackage -us -uc
COPY . ${SOURCE_DIR}/
WORKDIR /
ENTRYPOINT ["/docker-build.sh"]

@ -2,6 +2,28 @@
source ../common.build.sh
VERSION=`get_version ../..`
keep_artifacts="${1}"
clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION}
WORKDIR="$( pwd )"
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
output_dir="${WORKDIR}/pkg-dist"
current_user="$( whoami )"
image_name="jellyfin-debian-build"
rm -rf "${package_temporary_dir}" &>/dev/null \
|| sudo rm -rf "${package_temporary_dir}" &>/dev/null
rm -rf "${output_dir}" &>/dev/null \
|| sudo rm -rf "${output_dir}" &>/dev/null
if [[ ${keep_artifacts} == 'n' ]]; then
docker_sudo=""
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
&& [[ ! ${EUID:-1000} -eq 0 ]] \
&& [[ ! ${USER} == "root" ]] \
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
docker_sudo=sudo
fi
${docker_sudo} docker image rm ${image_name} --force
fi

@ -0,0 +1,19 @@
#!/bin/bash
# Builds the DEB inside the Docker container
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image
sed -i '/dotnet-sdk-2.2,/d' debian/control
# Build DEB
dpkg-buildpackage -us -uc
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/

@ -2,30 +2,30 @@
source ../common.build.sh
VERSION=`get_version ../..`
# TODO get the version in the package automatically. And using the changelog to decide the debian package suffix version.
# Build a Jellyfin .deb file with Docker on Linux
# Places the output .deb file in the parent directory
package_temporary_dir="`pwd`/pkg-dist-tmp"
output_dir="`pwd`/pkg-dist"
current_user="`whoami`"
image_name="jellyfin-debuild"
cleanup() {
set +o errexit
docker image rm $image_name --force
rm -rf "$package_temporary_dir"
}
trap cleanup EXIT INT
docker build ../.. -t "$image_name" -f ./Dockerfile --build-arg SOURCEDIR="/jellyfin-${VERSION}"
mkdir -p "$package_temporary_dir"
mkdir -p "$output_dir"
docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find / -maxdepth 1 -type f -name "jellyfin*" -exec mv {} /temp \;'
chown -R "$current_user" "$package_temporary_dir" \
|| sudo chown -R "$current_user" "$package_temporary_dir"
mv "$package_temporary_dir"/* "$output_dir"
WORKDIR="$( pwd )"
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
output_dir="${WORKDIR}/pkg-dist"
current_user="$( whoami )"
image_name="jellyfin-debian-build"
# Determine if sudo should be used for Docker
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
&& [[ ! ${EUID:-1000} -eq 0 ]] \
&& [[ ! ${USER} == "root" ]] \
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
docker_sudo="sudo"
else
docker_sudo=""
fi
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the DEBs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}"

@ -2,10 +2,12 @@
NAME=jellyfin
restart_cmds=("s6-svc -t /var/run/s6/services/${NAME}" \
"systemctl restart ${NAME}" \
"service ${NAME} restart" \
"/etc/init.d/${NAME} restart")
restart_cmds=(
"systemctl restart ${NAME}"
"service ${NAME} restart"
"/etc/init.d/${NAME} restart"
"s6-svc -t /var/run/s6/services/${NAME}"
)
for restart_cmd in "${restart_cmds[@]}"; do
cmd=$(echo "$restart_cmd" | awk '{print $1}')

@ -19,13 +19,17 @@ JELLYFIN_LOG_DIRECTORY="/var/log/jellyfin"
JELLYFIN_CACHE_DIRECTORY="/var/cache/jellyfin"
# Restart script for in-app server control
JELLYFIN_RESTART_OPT="--restartpath /usr/lib/jellyfin/restart.sh"
JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh"
# [OPTIONAL] ffmpeg binary paths
#JELLYFIN_FFMPEG_OPTS="--ffmpeg /usr/bin/ffmpeg --ffprobe /usr/bin/ffprobe"
# [OPTIONAL] ffmpeg binary paths, overriding the UI-configured values
#JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/bin/ffmpeg"
#JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/bin/ffprobe"
# [OPTIONAL] Additional user-defined options for the binary
#JELLYFIN_ADD_OPTS=""
# [OPTIONAL] run Jellyfin as a headless service
#JELLYFIN_SERVICE_OPT="--service"
# [OPTIONAL] run Jellyfin without the web app
#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp"
#
# SysV init/Upstart options
@ -34,4 +38,4 @@ JELLYFIN_RESTART_OPT="--restartpath /usr/lib/jellyfin/restart.sh"
# Application username
JELLYFIN_USER="jellyfin"
# Full application command
JELLYFIN_ARGS="--datadir $JELLYFIN_DATA_DIRECTORY --configdir $JELLYFIN_CONFIG_DIRECTORY --logdir $JELLYFIN_LOG_DIRECTORY --cachedir $JELLYFIN_CACHE_DIRECTORY $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPTS $JELLYFIN_ADD_OPTS"
JELLYFIN_ARGS="--datadir=$JELLYFIN_DATA_DIRECTORY --configdir=$JELLYFIN_CONFIG_DIRECTORY --logdir=$JELLYFIN_LOG_DIRECTORY --cachedir=$JELLYFIN_CACHE_DIRECTORY $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_FFPROBE_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT"

@ -10,15 +10,15 @@ Cmnd_Alias STARTSERVER_INITD = /etc/init.d/jellyfin start
Cmnd_Alias STOPSERVER_INITD = /etc/init.d/jellyfin stop
%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSV
%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSV
%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSV
%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD
%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD
%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD
%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_INITD
%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_INITD
%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_INITD
jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSV
jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSV
jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSV
jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD
jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD
jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD
jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_INITD
jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_INITD
jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_INITD
Defaults!RESTARTSERVER_SYSV !requiretty
Defaults!STARTSERVER_SYSV !requiretty
@ -31,7 +31,7 @@ Defaults!STARTSERVER_INITD !requiretty
Defaults!STOPSERVER_INITD !requiretty
#Allow the server to mount iso images
%jellyfin ALL=(ALL) NOPASSWD: /bin/mount
%jellyfin ALL=(ALL) NOPASSWD: /bin/umount
jellyfin ALL=(ALL) NOPASSWD: /bin/mount
jellyfin ALL=(ALL) NOPASSWD: /bin/umount
Defaults:%jellyfin !requiretty
Defaults:jellyfin !requiretty

@ -6,7 +6,7 @@ After = network.target
Type = simple
EnvironmentFile = /etc/default/jellyfin
User = jellyfin
ExecStart = /usr/bin/jellyfin --datadir ${JELLYFIN_DATA_DIRECTORY} --configdir ${JELLYFIN_CONFIG_DIRECTORY} --logdir ${JELLYFIN_LOG_DIRECTORY} --cachedir ${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPTS} ${JELLYFIN_ADD_OPTS}
ExecStart = /usr/bin/jellyfin --datadir=${JELLYFIN_DATA_DIRECTORY} --configdir=${JELLYFIN_CONFIG_DIRECTORY} --logdir=${JELLYFIN_LOG_DIRECTORY} --cachedir=${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT}
Restart = on-failure
TimeoutSec = 15

@ -1,15 +1,27 @@
FROM fedora:29
ARG HOME=/build
RUN mkdir /build && \
dnf install -y @buildsys-build rpmdevtools dnf-plugins-core && \
dnf copr enable -y @dotnet-sig/dotnet && \
rpmdev-setuptree
WORKDIR /build/rpmbuild
COPY ./deployment/fedora-package-x64/pkg-src/jellyfin.spec SPECS
COPY ./deployment/fedora-package-x64/pkg-src/ SOURCES
RUN spectool -g -R SPECS/jellyfin.spec && \
rpmbuild -bs SPECS/jellyfin.spec && \
dnf build-dep -y SRPMS/jellyfin-*.src.rpm && \
rpmbuild -bb SPECS/jellyfin.spec;
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG PLATFORM_DIR=/jellyfin/deployment/fedora-package-x64
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=2.2
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
# Prepare Fedora build environment
RUN dnf update -y \
&& dnf install -y @buildsys-build rpmdevtools dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel \
&& dnf copr enable -y @dotnet-sig/dotnet \
&& rpmdev-setuptree \
&& dnf install -y dotnet-sdk-${SDK_VERSION} \
&& ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \
&& mkdir -p ${SOURCE_DIR}/SPECS \
&& ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
&& mkdir -p ${SOURCE_DIR}/SOURCES \
&& ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES
VOLUME ${ARTIFACT_DIR}/
COPY . ${SOURCE_DIR}/
ENTRYPOINT ["/docker-build.sh"]

@ -2,17 +2,33 @@
source ../common.build.sh
VERSION=`get_version ../..`
package_temporary_dir="`pwd`/pkg-dist-tmp"
pkg_src_dir="`pwd`/pkg-src"
image_name="jellyfin-rpmbuild"
docker_sudo=""
if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \
[ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then
docker_sudo=sudo
fi
keep_artifacts="${1}"
WORKDIR="$( pwd )"
VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )"
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
package_source_dir="${WORKDIR}/pkg-src"
output_dir="${WORKDIR}/pkg-dist"
current_user="$( whoami )"
image_name="jellyfin-fedora-build"
rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \
|| sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null
$docker_sudo docker image rm $image_name --force
rm -rf "$package_temporary_dir"
rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz"
rm -rf "${package_temporary_dir}" &>/dev/null \
|| sudo rm -rf "${package_temporary_dir}" &>/dev/null
rm -rf "${output_dir}" &>/dev/null \
|| sudo rm -rf "${output_dir}" &>/dev/null
if [[ ${keep_artifacts} == 'n' ]]; then
docker_sudo=""
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
&& [[ ! ${EUID:-1000} -eq 0 ]] \
&& [[ ! ${USER} == "root" ]] \
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
docker_sudo=sudo
fi
${docker_sudo} docker image rm ${image_name} --force
fi

@ -0,0 +1,20 @@
#!/bin/bash
# Builds the RPM inside the Docker container
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
ls -al SOURCES/pkg-src/
# Build RPM
spectool -g -R SPECS/jellyfin.spec
rpmbuild -bs SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/"
rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/"
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/rpm
mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/

@ -1,38 +1,29 @@
#!/usr/bin/env sh
#!/usr/bin/env bash
source ../common.build.sh
VERSION=`get_version ../..`
WORKDIR="$( pwd )"
VERSION="$( grep '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )"
# TODO get the version in the package automatically. And using the changelog to decide the debian package suffix version.
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
output_dir="${WORKDIR}/pkg-dist"
pkg_src_dir="${WORKDIR}/pkg-src"
current_user="$( whoami )"
image_name="jellyfin-fedora-build"
# Build a Jellyfin .rpm file with Docker on Linux
# Places the output .rpm file in the parent directory
set -o errexit
set -o xtrace
set -o nounset
package_temporary_dir="`pwd`/pkg-dist-tmp"
output_dir="`pwd`/pkg-dist"
pkg_src_dir="`pwd`/pkg-src"
current_user="`whoami`"
image_name="jellyfin-rpmbuild"
docker_sudo=""
if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \
[ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then
docker_sudo=sudo
# Determine if sudo should be used for Docker
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
&& [[ ! ${EUID:-1000} -eq 0 ]] \
&& [[ ! ${USER} == "root" ]] \
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
docker_sudo="sudo"
else
docker_sudo=""
fi
cleanup() {
set +o errexit
$docker_sudo docker image rm $image_name --force
rm -rf "$package_temporary_dir"
rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz"
}
trap cleanup EXIT INT
# Create RPM source archive
GNU_TAR=1
mkdir -p "$package_temporary_dir"
mkdir -p "${package_temporary_dir}"
echo "Bundling all sources for RPM build."
tar \
--transform "s,^\.,jellyfin-${VERSION}," \
@ -47,12 +38,12 @@ tar \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" \
-czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" \
-C "../.." ./ || GNU_TAR=0
if [ $GNU_TAR -eq 0 ]; then
echo "The installed tar binary did not support --transform. Using workaround."
mkdir -p "$package_temporary_dir/jellyfin-${VERSION}"
mkdir -p "${package_temporary_dir}/jellyfin"
# Not GNU tar
tar \
--exclude='.git*' \
@ -67,20 +58,23 @@ if [ $GNU_TAR -eq 0 ]; then
--exclude='*.deb' \
--exclude='*.rpm' \
-zcf \
"$package_temporary_dir/jellyfin-${VERSION}/jellyfin.tar.gz" \
-C "../.." \
./
"${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \
-C "../.." ./
echo "Extracting filtered package."
tar -xzf "$package_temporary_dir/jellyfin-${VERSION}/jellyfin.tar.gz" -C "$package_temporary_dir/jellyfin-${VERSION}"
tar -xzf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}/jellyfin-${VERSION}"
echo "Removing filtered package."
rm "$package_temporary_dir/jellyfin-${VERSION}/jellyfin.tar.gz"
rm -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz"
echo "Repackaging package into final tarball."
tar -zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" -C "$package_temporary_dir" "jellyfin-${VERSION}"
tar -czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}"
fi
$docker_sudo docker build ../.. -t "$image_name" -f ./Dockerfile
mkdir -p "$output_dir"
$docker_sudo docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find /build/rpmbuild -maxdepth 3 -type f -name "jellyfin*.rpm" -exec mv {} /temp \;'
chown -R "$current_user" "$package_temporary_dir" \
|| sudo chown -R "$current_user" "$package_temporary_dir"
mv "$package_temporary_dir"/*.rpm "$output_dir"
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the RPMs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the RPMs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" \
|| sudo chown -R "${current_user}" "${package_temporary_dir}"
# Move the RPMs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/rpm/* "${output_dir}"

@ -14,15 +14,22 @@
# General options
#
# Tell jellyfin wich ffmpeg/ffprobe to use
# JELLYFIN_FFMPEG="--ffmpeg /usr/bin/ffmpeg --ffprobe /usr/bin/ffprobe"
# Program directories
JELLYFIN_DATA_DIRECTORY="/var/lib/jellyfin"
JELLYFIN_CONFIG_DIRECTORY="/etc/jellyfin"
JELLYFIN_LOG_DIRECTORY="/var/log/jellyfin"
JELLYFIN_CACHE_DIRECTORY="/var/cache/jellyfin"
# In-App service control
JELLYFIN_RESTART_OPT="--restartpath /usr/libexec/jellyfin/restart.sh"
# Additional options for the binary
JELLYFIN_ADD_OPTS=""
JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh"
# [OPTIONAL] ffmpeg binary paths, overriding the UI-configured values
#JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/bin/ffmpeg"
#JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/bin/ffprobe"
# [OPTIONAL] run Jellyfin as a headless service
#JELLYFIN_SERVICE_OPT="--service"
# [OPTIONAL] run Jellyfin without the web app
#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp"

@ -5,7 +5,7 @@ Description=Jellyfin is a free software media system that puts you in control of
[Service]
EnvironmentFile=/etc/sysconfig/jellyfin
WorkingDirectory=/var/lib/jellyfin
ExecStart=/usr/bin/jellyfin --datadir ${JELLYFIN_DATA_DIRECTORY} --configdir ${JELLYFIN_CONFIG_DIRECTORY} --logdir ${JELLYFIN_LOG_DIRECTORY} --cachedir ${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_ADD_OPTS} ${JELLYFIN_FFMPEG}
ExecStart=/usr/bin/jellyfin --datadir=${JELLYFIN_DATA_DIRECTORY} --configdir=${JELLYFIN_CONFIG_DIRECTORY} --logdir=${JELLYFIN_LOG_DIRECTORY} --cachedir=${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT}
TimeoutSec=15
Restart=on-failure
User=jellyfin

@ -4,16 +4,16 @@ Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemctl start jellyfin, /bin/systemc
Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemctl stop jellyfin, /bin/systemctl stop jellyfin
%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD
%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD
%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD
jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD
jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD
jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD
Defaults!RESTARTSERVER_SYSTEMD !requiretty
Defaults!STARTSERVER_SYSTEMD !requiretty
Defaults!STOPSERVER_SYSTEMD !requiretty
# Uncomment to allow the server to mount iso images
# %jellyfin ALL=(ALL) NOPASSWD: /bin/mount
# %jellyfin ALL=(ALL) NOPASSWD: /bin/umount
# Allow the server to mount iso images
jellyfin ALL=(ALL) NOPASSWD: /bin/mount
jellyfin ALL=(ALL) NOPASSWD: /bin/umount
Defaults:%jellyfin !requiretty
Defaults:jellyfin !requiretty

@ -0,0 +1,21 @@
FROM microsoft/dotnet:2.2-sdk-bionic
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-x64
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
# Prepare Ubuntu build environment
RUN apt-get update \
&& apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev \
&& ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \
&& mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
VOLUME ${ARTIFACT_DIR}/
COPY . ${SOURCE_DIR}/
ENTRYPOINT ["/docker-build.sh"]

@ -0,0 +1,29 @@
#!/usr/bin/env bash
source ../common.build.sh
keep_artifacts="${1}"
WORKDIR="$( pwd )"
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
output_dir="${WORKDIR}/pkg-dist"
current_user="$( whoami )"
image_name="jellyfin-ubuntu-build"
rm -rf "${package_temporary_dir}" &>/dev/null \
|| sudo rm -rf "${package_temporary_dir}" &>/dev/null
rm -rf "${output_dir}" &>/dev/null \
|| sudo rm -rf "${output_dir}" &>/dev/null
if [[ ${keep_artifacts} == 'n' ]]; then
docker_sudo=""
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
&& [[ ! ${EUID:-1000} -eq 0 ]] \
&& [[ ! ${USER} == "root" ]] \
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
docker_sudo=sudo
fi
${docker_sudo} docker image rm ${image_name} --force
fi

@ -0,0 +1,19 @@
#!/bin/bash
# Builds the DEB inside the Docker container
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image
sed -i '/dotnet-sdk-2.2,/d' debian/control
# Build DEB
dpkg-buildpackage -us -uc
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/

@ -0,0 +1,31 @@
#!/usr/bin/env bash
source ../common.build.sh
WORKDIR="$( pwd )"
package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
output_dir="${WORKDIR}/pkg-dist"
current_user="$( whoami )"
image_name="jellyfin-ubuntu-build"
# Determine if sudo should be used for Docker
if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
&& [[ ! ${EUID:-1000} -eq 0 ]] \
&& [[ ! ${USER} == "root" ]] \
&& [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
docker_sudo="sudo"
else
docker_sudo=""
fi
# Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the DEBs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory
mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}"

@ -0,0 +1 @@
../debian-package-x64/pkg-src

@ -3,11 +3,19 @@
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<!-- disable warning SA1101: Prefix local calls with 'this.' -->
<Rule Id="SA1101" Action="None" />
<!-- disable warning SA1130: Use lambda syntax -->
<Rule Id="SA1130" Action="None" />
<!-- disable warning SA1200: 'using' directive must appear within a namespace declaration -->
<Rule Id="SA1200" Action="None" />
<!-- disable warning SA1309: Fields must not begin with an underscore -->
<Rule Id="SA1309" Action="None" />
<!-- disable warning SA1512: Single-line comments must not be followed by blank line -->
<Rule Id="SA1512" Action="None" />
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
<Rule Id="SA1633" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
<!-- disable warning CA1054: Change the type of parameter url from string to System.Uri -->
<Rule Id="CA1054" Action="None" />
</Rules>
</RuleSet>

Loading…
Cancel
Save