feat: Change default location of 'recyclarr.yml'

The default is now located at `~/.config/recyclarr/recyclarr.yml`. The
previous location (next to the executable) is still supported, but
deprecated. A `recyclarr.yml` at the old/previous location will always
be loaded first.
pull/76/head
Robert Dailey 2 years ago
parent f825bbab2e
commit 7f1b85f33a

@ -12,10 +12,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New `--app-data` option for overriding the location of the application data directory.
### Changed
- The default location for the default YAML file (`recyclarr.yml`) has been changed to the
[application data directory](appdata). This is the same location of the `settings.yml` file.
### Deprecated
- The `recyclarr.yml` file should no longer be located adjacent to the `recyclarr` executable.
### Fixed
- Version information in help output has been fixed.
[appdata]: https://github.com/recyclarr/recyclarr/wiki/File-Structure
## [2.0.2] - 2022-05-20
### Fixed

@ -0,0 +1,6 @@
namespace Common;
public class AppContextProxy : IAppContext
{
public string BaseDirectory => AppContext.BaseDirectory;
}

@ -8,5 +8,6 @@ public class CommonAutofacModule : Module
{
builder.RegisterType<DefaultEnvironment>().As<IEnvironment>();
builder.RegisterType<FileUtilities>().As<IFileUtilities>();
builder.RegisterType<AppContextProxy>().As<IAppContext>();
}
}

@ -0,0 +1,6 @@
namespace Common;
public interface IAppContext
{
string BaseDirectory { get; }
}

@ -0,0 +1,66 @@
using System.IO.Abstractions.TestingHelpers;
using AutoFixture.NUnit3;
using Common;
using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
using TestLibrary.AutoFixture;
using TrashLib;
namespace Recyclarr.Tests;
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class ConfigurationFinderTest
{
[Test, AutoMockData]
public void Return_path_next_to_executable_if_present(
[Frozen] IAppContext appContext,
[Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs,
[Frozen] IAppPaths paths,
ConfigurationFinder sut)
{
paths.DefaultConfigFilename.Returns("recyclarr.yml");
paths.ConfigPath.Returns(@"app\data");
appContext.BaseDirectory.Returns(@"base\path");
fs.AddFile(@"base\path\recyclarr.yml", new MockFileData(""));
var path = sut.FindConfigPath();
path.Should().EndWith(@"base\path\recyclarr.yml");
}
[Test, AutoMockData]
public void Return_app_data_dir_location_if_base_directory_location_not_present(
[Frozen] IAppContext appContext,
[Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs,
[Frozen] IAppPaths paths,
ConfigurationFinder sut)
{
paths.ConfigPath.Returns(@"app\data\recyclarr.yml");
appContext.BaseDirectory.Returns(@"base\path");
var path = sut.FindConfigPath();
path.Should().EndWith(@"app\data\recyclarr.yml");
}
[Test, AutoMockData]
public void Return_base_directory_location_if_both_files_are_present(
[Frozen] IAppContext appContext,
[Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs,
[Frozen] IAppPaths paths,
ConfigurationFinder sut)
{
paths.DefaultConfigFilename.Returns("recyclarr.yml");
paths.ConfigPath.Returns(@"app\data");
appContext.BaseDirectory.Returns(@"base\path");
fs.AddFile(@"base\path\recyclarr.yml", new MockFileData(""));
fs.AddFile(@"app\data\recyclarr.yml", new MockFileData(""));
var path = sut.FindConfigPath();
path.Should().EndWith(@"base\path\recyclarr.yml");
}
}

@ -3,7 +3,7 @@ using TrashLib;
namespace Recyclarr;
internal class AppPaths : IAppPaths
public class AppPaths : IAppPaths
{
private readonly IFileSystem _fs;
@ -12,10 +12,12 @@ internal class AppPaths : IAppPaths
_fs = fs;
}
public string DefaultConfigFilename => "recyclarr.yml";
public void SetAppDataPath(string path) => AppDataPath = path;
public string AppDataPath { get; private set; } = "";
public string ConfigPath => _fs.Path.Combine(AppContext.BaseDirectory, "recyclarr.yml");
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");

@ -5,7 +5,6 @@ using Newtonsoft.Json;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using TrashLib;
using TrashLib.Config.Settings;
using TrashLib.Extensions;
using TrashLib.Repo;
@ -19,7 +18,7 @@ internal class ServiceInitializer : IServiceInitializer
private readonly ISettingsPersister _settingsPersister;
private readonly ISettingsProvider _settingsProvider;
private readonly IRepoUpdater _repoUpdater;
private readonly IAppPaths _paths;
private readonly IConfigurationFinder _configFinder;
public ServiceInitializer(
ILogger log,
@ -27,14 +26,14 @@ internal class ServiceInitializer : IServiceInitializer
ISettingsPersister settingsPersister,
ISettingsProvider settingsProvider,
IRepoUpdater repoUpdater,
IAppPaths paths)
IConfigurationFinder configFinder)
{
_log = log;
_loggingLevelSwitch = loggingLevelSwitch;
_settingsPersister = settingsPersister;
_settingsProvider = settingsProvider;
_repoUpdater = repoUpdater;
_paths = paths;
_configFinder = configFinder;
}
public void Initialize(ServiceCommand cmd)
@ -50,7 +49,7 @@ internal class ServiceInitializer : IServiceInitializer
if (!cmd.Config.Any())
{
cmd.Config = new[] {_paths.ConfigPath};
cmd.Config = new[] {_configFinder.FindConfigPath()};
}
}

@ -45,6 +45,7 @@ public static class CompositionRoot
builder.RegisterType<ObjectFactory>().As<IObjectFactory>();
builder.RegisterType<AppPaths>().As<IAppPaths>().SingleInstance();
builder.RegisterType<ConfigurationFinder>().As<IConfigurationFinder>();
builder.RegisterGeneric(typeof(ConfigurationLoader<>))
.WithProperty(new AutowiringParameter())

@ -28,9 +28,9 @@ public class ConfigurationLoader<T> : IConfigurationLoader<T>
_deserializer = yamlFactory.CreateDeserializer();
}
public IEnumerable<T> Load(string propertyName, string configSection)
public IEnumerable<T> Load(string file, string configSection)
{
using var stream = _fileSystem.File.OpenText(propertyName);
using var stream = _fileSystem.File.OpenText(file);
return LoadFromStream(stream, configSection);
}

@ -5,7 +5,7 @@ namespace Recyclarr.Config;
public interface IConfigurationLoader<out T>
where T : IServiceConfiguration
{
IEnumerable<T> Load(string propertyName, string configSection);
IEnumerable<T> Load(string file, string configSection);
IEnumerable<T> LoadFromStream(TextReader stream, string configSection);
IEnumerable<T> LoadMany(IEnumerable<string> configFiles, string configSection);
}

@ -0,0 +1,40 @@
using System.IO.Abstractions;
using Common;
using Serilog;
using TrashLib;
namespace Recyclarr;
public class ConfigurationFinder : IConfigurationFinder
{
private readonly ILogger _log;
private readonly IAppPaths _paths;
private readonly IAppContext _appContext;
private readonly IFileSystem _fs;
public ConfigurationFinder(ILogger log, IAppPaths paths, IAppContext appContext, IFileSystem fs)
{
_log = log;
_paths = paths;
_appContext = appContext;
_fs = fs;
}
public string FindConfigPath()
{
var newPath = _paths.ConfigPath;
var oldPath = _fs.Path.Join(_appContext.BaseDirectory, _paths.DefaultConfigFilename);
if (!_fs.File.Exists(oldPath))
{
return newPath;
}
_log.Warning(
"`recyclarr.yml` file located adjacent to the executable is DEPRECATED. Please move it to the " +
"following location, as support for this old location will be removed in a future release: " +
"{NewLocation}", newPath);
return oldPath;
}
}

@ -9,4 +9,5 @@ public interface IAppPaths
string LogDirectory { get; }
string RepoDirectory { get; }
string CacheDirectory { get; }
string DefaultConfigFilename { get; }
}

@ -0,0 +1,6 @@
namespace Recyclarr;
public interface IConfigurationFinder
{
string FindConfigPath();
}
Loading…
Cancel
Save