Rewrite rules for determining app paths and use XDG_CONFIG_HOME for configDir (#781)

Re-write rules for determining dataDir, configDir and logDir.  Generally, arguments from command line take precedence, then JELLYFIN env vars, before using XDG names.

Co-Authored-By: ploughpuff <33969763+ploughpuff@users.noreply.github.com>
This commit is contained in:
ploughpuff 2019-02-13 15:35:14 +00:00 committed by Bond-009
parent eb4b705167
commit a2dd2ddd55
3 changed files with 132 additions and 154 deletions

View File

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

View File

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

View File

@ -139,112 +139,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"></param>
/// <returns>ServerApplicationPaths</returns>
private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options) private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
{ {
string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH"); // dataDir
if (string.IsNullOrEmpty(programDataPath)) // 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) dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
{
programDataPath = options.DataDir; if (string.IsNullOrEmpty(dataDir))
}
else
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
} }
else else
{ {
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored. // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); dataDir = 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)) // 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)) // configDir
{ // IF --configdir
Console.WriteLine("Cannot continue without path to program data folder (try -programdata)"); // ELSE IF $JELLYFIN_CONFIG_DIR
Environment.Exit(1); // ELSE IF --datadir, use <datadir>/config (assume portable run)
} // ELSE IF <datadir>/config exists, use that
else // ELSE IF windows, use <datadir>/config
{ // ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
Directory.CreateDirectory(programDataPath); // ELSE $HOME/.config/jellyfin
} var configDir = options.ConfigDir;
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir)) if (string.IsNullOrEmpty(configDir))
{ {
if (options.ConfigDir != null) configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{ {
configDir = options.ConfigDir; if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
} {
else // Hang config folder off already set dataDir
{ configDir = Path.Combine(dataDir, "config");
// Let BaseApplicationPaths set up the default value }
configDir = null; 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) // cacheDir
{ // IF --cachedir
Directory.CreateDirectory(configDir); // 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 (string.IsNullOrEmpty(cacheDir))
{ {
if (options.CacheDir != null) cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
if (string.IsNullOrEmpty(cacheDir))
{ {
cacheDir = options.CacheDir; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
}
else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// $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))
{ {
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) // logDir
{ // IF --logdir
Directory.CreateDirectory(cacheDir); // 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 (string.IsNullOrEmpty(logDir))
{ {
if (options.LogDir != null) logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
if (string.IsNullOrEmpty(logDir))
{ {
logDir = options.LogDir; // Hang log folder off already set dataDir
} logDir = Path.Combine(dataDir, "log");
else
{
// Let BaseApplicationPaths set up the default value
logDir = null;
} }
} }
if (logDir != null) // Ensure the main folders exist before we continue
try
{ {
Directory.CreateDirectory(dataDir);
Directory.CreateDirectory(logDir); 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(dataDir, logDir, configDir, cacheDir);
return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir);
} }
private static async Task CreateLogger(IApplicationPaths appPaths) private static async Task CreateLogger(IApplicationPaths appPaths)