fix: Better safeguards for HOME directory usage

- Attempt to detect if `HOME` is defined and available. If not, error
  out.
- Attempt to create `$HOME/.config` if `$HOME` is available.
- If logic in code attempts to grab the app data dir path before it's
  set up, an exception is thrown.
pull/76/head
Robert Dailey 2 years ago
parent e14357fd73
commit 035836db49

@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Version information in help output has been fixed.
- If a HOME directory is not available, throw an error to the user (use `--app-data` instead).
- Create `$HOME/.config` (on Linux) if it does not exist.
[appdata]: https://github.com/recyclarr/recyclarr/wiki/File-Structure

@ -1,4 +1,4 @@
namespace Common;
namespace Common;
internal class DefaultEnvironment : IEnvironment
{
@ -6,4 +6,9 @@ internal class DefaultEnvironment : IEnvironment
{
return Environment.GetFolderPath(folder);
}
public string GetFolderPath(Environment.SpecialFolder folder, Environment.SpecialFolderOption option)
{
return Environment.GetFolderPath(folder, option);
}
}

@ -1,6 +1,7 @@
namespace Common;
namespace Common;
public interface IEnvironment
{
public string GetFolderPath(Environment.SpecialFolder folder);
string GetFolderPath(Environment.SpecialFolder folder, Environment.SpecialFolderOption option);
}

@ -1,4 +1,5 @@
using AutoFixture.NUnit3;
using Common;
using NSubstitute;
using NUnit.Framework;
using Recyclarr.Command;
@ -13,25 +14,29 @@ namespace Recyclarr.Tests.Command.Initialization.Init;
public class InitializeAppDataPathTest
{
[Test, AutoMockData]
public void Do_not_override_path_if_null(
public void Use_default_app_data_if_not_specified(
[Frozen] IEnvironment env,
[Frozen] IAppPaths paths,
SonarrCommand cmd,
InitializeAppDataPath sut)
{
env.GetFolderPath(Arg.Any<Environment.SpecialFolder>(), Arg.Any<Environment.SpecialFolderOption>())
.Returns("app_data");
sut.Initialize(cmd);
paths.DidNotReceiveWithAnyArgs().SetAppDataPath(default!);
env.Received().GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create);
paths.Received().SetAppDataPath("app_data");
}
[Test, AutoMockData]
public void Override_path_if_not_null(
public void Use_specified_app_data_if_user_provided(
[Frozen] IAppPaths paths,
SonarrCommand cmd,
InitializeAppDataPath sut)
{
cmd.AppDataDirectory = "path";
sut.Initialize(cmd);
paths.Received().SetAppDataPath("path");
}
}

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.IO.Abstractions;
using TrashLib;
@ -6,6 +7,7 @@ namespace Recyclarr;
public class AppPaths : IAppPaths
{
private readonly IFileSystem _fs;
private string? _appDataPath;
public AppPaths(IFileSystem fs)
{
@ -14,12 +16,15 @@ public class AppPaths : IAppPaths
public string DefaultConfigFilename => "recyclarr.yml";
public void SetAppDataPath(string path) => AppDataPath = path;
public void SetAppDataPath(string path) => _appDataPath = path;
public string AppDataPath { get; private set; } = "";
public string ConfigPath => _fs.Path.Combine(AppDataPath, DefaultConfigFilename);
public string SettingsPath => _fs.Path.Combine(AppDataPath, "settings.yml");
public string LogDirectory => _fs.Path.Combine(AppDataPath, "logs");
public string RepoDirectory => _fs.Path.Combine(AppDataPath, "repo");
public string CacheDirectory => _fs.Path.Combine(AppDataPath, "cache");
[SuppressMessage("Design", "CA1024:Use properties where appropriate")]
public string GetAppDataPath()
=> _appDataPath ?? throw new DirectoryNotFoundException("Application data directory not set!");
public string ConfigPath => _fs.Path.Combine(GetAppDataPath(), DefaultConfigFilename);
public string SettingsPath => _fs.Path.Combine(GetAppDataPath(), "settings.yml");
public string LogDirectory => _fs.Path.Combine(GetAppDataPath(), "logs");
public string RepoDirectory => _fs.Path.Combine(GetAppDataPath(), "repo");
public string CacheDirectory => _fs.Path.Combine(GetAppDataPath(), "cache");
}

@ -1,4 +1,6 @@
using System.IO.Abstractions;
using CliFx.Exceptions;
using Common;
using TrashLib;
namespace Recyclarr.Command.Initialization.Init;
@ -7,20 +9,40 @@ public class InitializeAppDataPath : IServiceInitializer
{
private readonly IFileSystem _fs;
private readonly IAppPaths _paths;
private readonly IEnvironment _env;
public InitializeAppDataPath(IFileSystem fs, IAppPaths paths)
public InitializeAppDataPath(IFileSystem fs, IAppPaths paths, IEnvironment env)
{
_fs = fs;
_paths = paths;
_env = env;
}
public void Initialize(ServiceCommand cmd)
{
// If the user did not explicitly specify an app data directory, perform some system introspection to verify if
// the user has a home directory.
if (string.IsNullOrEmpty(cmd.AppDataDirectory))
{
// If we can't even get the $HOME directory value, throw an exception. User must explicitly specify it with
// --app-data.
var home = _env.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (string.IsNullOrEmpty(home))
{
throw new CommandException(
"The system does not have a HOME directory, so the application cannot determine where to place " +
"data files. Please use the --app-data option to explicitly set a location for these files.");
}
// Set app data path to application directory value (e.g. `$HOME/.config` on Linux) and ensure it is
// created.
_paths.SetAppDataPath(_env.GetFolderPath(Environment.SpecialFolder.ApplicationData,
Environment.SpecialFolderOption.Create));
return;
}
// Ensure user-specified app data directory is created and use it.
_fs.Directory.CreateDirectory(cmd.AppDataDirectory);
_paths.SetAppDataPath(cmd.AppDataDirectory);
}

@ -3,7 +3,7 @@ namespace TrashLib;
public interface IAppPaths
{
void SetAppDataPath(string path);
string AppDataPath { get; }
string GetAppDataPath();
string ConfigPath { get; }
string SettingsPath { get; }
string LogDirectory { get; }

Loading…
Cancel
Save