Skip to content

Commit

Permalink
Add support for defining settings folder
Browse files Browse the repository at this point in the history
  • Loading branch information
coenm committed Sep 23, 2024
1 parent 78d5450 commit 1e41388
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 43 deletions.
15 changes: 15 additions & 0 deletions README.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ The repositories shown in RepoM are filtered using the search box in RepoM. But

When RepoM starts for the first time, a configuration file wil be created. Most of the properties can be adjusted using the UI but, at this moment, one property must be altered manually. Read more over [here](docs/_old/Settings.md).

## Multi configuration

By default, RepoM stores all configuration files in `%ADPPDATA%/RepoM`. As a user you can alter this location to support multi configurations which might be useful for different working environments. Also, for development or debug purposes, this might be very useful.

To change the app settings location, you can

- alter the `appsettings.json` file located in the same directory where the RepoM executable lives

snippet: appsettings-appsettingspath

- start RepoM using the commandline argument `--App:AppSettingsPath <absolute or relative path here>`.
- use environment variable `REPOM__APP_APPSETTINGSPATH`.

If none is set, the default will be used.

## Plugins

RepoM uses plugins to extend functionality. At this moment, when a plugin is available in the installed directory, it will be found and can be enabled or disabled. This is done in the hamburger menu of RepoM. Enabling or disabling requires a restart of RepoM.
Expand Down
3 changes: 1 addition & 2 deletions src/RepoM.Api/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ namespace RepoM.Api;

using Microsoft.Extensions.Logging;
using RepoM.Api.Common;
using RepoM.Api.IO;
using RepoM.Api.Plugins;
using SimpleInjector;
using System.Collections.Generic;
Expand Down Expand Up @@ -87,7 +86,7 @@ await container
.RegisterPackagesAsync(
assemblies,
filename => new FileBasedPackageConfiguration(
DefaultAppDataPathProvider.Instance,
_appDataProvider,
_fileSystem,
_loggerFactory.CreateLogger<FileBasedPackageConfiguration>(),
filename))
Expand Down
6 changes: 6 additions & 0 deletions src/RepoM.Api/IO/AppDataPathConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace RepoM.Api.IO;

public record struct AppDataPathConfig
{
public string? AppSettingsPath { get; init; }
}
22 changes: 22 additions & 0 deletions src/RepoM.Api/IO/AppDataPathProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace RepoM.Api.IO;

using System;
using System.IO;
using RepoM.Core.Plugin.Common;

public sealed class AppDataPathProvider : IAppDataPathProvider
{
public AppDataPathProvider(AppDataPathConfig config)
{
ArgumentNullException.ThrowIfNull(config);
if (!string.IsNullOrWhiteSpace(config.AppSettingsPath))
{
AppDataPath = Path.GetFullPath(config.AppSettingsPath);
return;
}

AppDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RepoM");
}

public string AppDataPath { get; }
}
18 changes: 0 additions & 18 deletions src/RepoM.Api/IO/DefaultAppDataPathProvider.cs

This file was deleted.

50 changes: 38 additions & 12 deletions src/RepoM.App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace RepoM.App;
using Container = SimpleInjector.Container;
using RepoM.App.Services.HotKey;
using RepoM.Api;
using RepoM.App.Configuration;

/// <summary>
/// Interaction logic for App.xaml
Expand Down Expand Up @@ -60,7 +61,7 @@ protected override async void OnStartup(StartupEventArgs e)
typeof(FrameworkElement),
new FrameworkPropertyMetadata(System.Windows.Markup.XmlLanguage.GetLanguage(System.Globalization.CultureInfo.CurrentCulture.IetfLanguageTag)));

Application.Current.Resources.MergedDictionaries[0] = ResourceDictionaryTranslationService.ResourceDictionary;
Current.Resources.MergedDictionaries[0] = ResourceDictionaryTranslationService.ResourceDictionary;
_notifyIcon = FindResource("NotifyIcon") as TaskbarIcon;

var fileSystem = new FileSystem();
Expand All @@ -69,22 +70,32 @@ protected override async void OnStartup(StartupEventArgs e)
IHmacService hmacService = new HmacSha256Service();
IPluginFinder pluginFinder = new PluginFinder(fileSystem, hmacService);

IConfiguration config = SetupConfiguration();
IConfiguration appDataPathConfiguration = SetupConfigurationX(e.Args);

var appConfig = new Config();
appDataPathConfiguration.Bind("App", appConfig);
var appDataPathConfig = new AppDataPathConfig
{
AppSettingsPath = appConfig.AppSettingsPath,
};
var appDataProvider = new AppDataPathProvider(appDataPathConfig);

IConfiguration config = SetupConfiguration(appDataProvider);
ILoggerFactory loggerFactory = CreateLoggerFactory(config);
ILogger logger = loggerFactory.CreateLogger(nameof(App));
logger.LogInformation("Started");
Bootstrapper.RegisterLogging(loggerFactory);
Bootstrapper.RegisterServices(fileSystem);
await Bootstrapper.RegisterPlugins(pluginFinder, fileSystem, loggerFactory).ConfigureAwait(true);
Bootstrapper.RegisterServices(fileSystem, appDataProvider);
await Bootstrapper.RegisterPlugins(pluginFinder, fileSystem, loggerFactory, appDataProvider).ConfigureAwait(true);

var ensureStartup = new EnsureStartup(fileSystem, appDataProvider);
await ensureStartup.EnsureFilesAsync().ConfigureAwait(true);

#if DEBUG
Bootstrapper.Container.Verify(SimpleInjector.VerificationOption.VerifyAndDiagnose);
#else
Bootstrapper.Container.Options.EnableAutoVerification = false;
#endif

EnsureStartup ensureStartup = Bootstrapper.Container.GetInstance<EnsureStartup>();
await ensureStartup.EnsureFilesAsync().ConfigureAwait(true);

UseRepositoryMonitor(Bootstrapper.Container);

Expand All @@ -104,7 +115,24 @@ protected override async void OnStartup(StartupEventArgs e)
logger.LogError(exception, "Could not start all modules.");
}
}


private static IConfiguration SetupConfigurationX(string[] args)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
//.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false);

#if DEBUG
builder = builder.AddJsonFile("appsettings.Debug.json", optional: true, reloadOnChange: false);
#endif

builder = builder
.AddEnvironmentVariables("REPOM_APP_")
.AddCommandLine(args);

return builder.Build();
}

protected override void OnExit(ExitEventArgs e)
{
_windowSizeService?.Unregister();
Expand All @@ -113,19 +141,17 @@ protected override void OnExit(ExitEventArgs e)

_hotKeyService?.Unregister();

// #pragma warning disable CA1416 // Validate platform compatibility
_notifyIcon?.Dispose();
// #pragma warning restore CA1416 // Validate platform compatibility

ReleaseAndDisposeMutex();

base.OnExit(e);
}

private static IConfiguration SetupConfiguration()
private static IConfiguration SetupConfiguration(AppDataPathProvider appDataProvider)
{
const string FILENAME = "appsettings.serilog.json";
var fullFilename = Path.Combine(DefaultAppDataPathProvider.Instance.AppDataPath, FILENAME);
var fullFilename = Path.Combine(appDataProvider.AppDataPath, FILENAME);

IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
Expand Down
13 changes: 6 additions & 7 deletions src/RepoM.App/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal static class Bootstrapper
{
public static readonly Container Container = new();

public static void RegisterServices(IFileSystem fileSystem)
public static void RegisterServices(IFileSystem fileSystem, IAppDataPathProvider appDataProvider)
{
Container.RegisterInstance<ObjectCache>(MemoryCache.Default);
Container.RegisterSingleton<Window>(() => Container.GetInstance<MainWindow>());
Expand All @@ -51,7 +51,7 @@ public static void RegisterServices(IFileSystem fileSystem)
Container.Register<IRepositoryDetectorFactory, DefaultRepositoryDetectorFactory>(Lifestyle.Singleton);
Container.Register<IRepositoryObserverFactory, DefaultRepositoryObserverFactory>(Lifestyle.Singleton);
Container.Register<IGitRepositoryFinderFactory, GitRepositoryFinderFactory>(Lifestyle.Singleton);
Container.RegisterInstance<IAppDataPathProvider>(DefaultAppDataPathProvider.Instance);
Container.RegisterInstance(appDataProvider);
Container.Register<IRepositoryReader, DefaultRepositoryReader>(Lifestyle.Singleton);
Container.Register<IRepositoryWriter, DefaultRepositoryWriter>(Lifestyle.Singleton);
Container.Register<IRepositoryStore, DefaultRepositoryStore>(Lifestyle.Singleton);
Expand Down Expand Up @@ -106,27 +106,26 @@ public static void RegisterServices(IFileSystem fileSystem)
Container.Register<IRepositoryComparerFactory<SumComparerConfigurationV1>, SumRepositoryComparerFactory>(Lifestyle.Singleton);

Container.RegisterSingleton<ActionExecutor>();
Container.Register(typeof(ICommandExecutor<>), new[] { typeof(CoreBootstrapper).Assembly, }, Lifestyle.Singleton);
Container.Register(typeof(ICommandExecutor<>), [typeof(CoreBootstrapper).Assembly,], Lifestyle.Singleton);
Container.RegisterDecorator(
typeof(ICommandExecutor<>),
typeof(LoggerCommandExecutorDecorator<>),
Lifestyle.Singleton);

Container.RegisterSingleton<HotKeyService>();
Container.RegisterSingleton<WindowSizeService>();

Container.RegisterSingleton<EnsureStartup>();
}

public static async Task RegisterPlugins(
IPluginFinder pluginFinder,
IFileSystem fileSystem,
ILoggerFactory loggerFactory)
ILoggerFactory loggerFactory,
IAppDataPathProvider appDataPathProvider)
{
Container.Register<ModuleService>(Lifestyle.Singleton);
Container.RegisterInstance(pluginFinder);

var coreBootstrapper = new CoreBootstrapper(pluginFinder, fileSystem, DefaultAppDataPathProvider.Instance, loggerFactory);
var coreBootstrapper = new CoreBootstrapper(pluginFinder, fileSystem, appDataPathProvider, loggerFactory);
var baseDirectory = fileSystem.Path.Combine(AppDomain.CurrentDomain.BaseDirectory);
await coreBootstrapper.LoadAndRegisterPluginsAsync(Container, baseDirectory).ConfigureAwait(false);
}
Expand Down
9 changes: 9 additions & 0 deletions src/RepoM.App/Configuration/Config.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace RepoM.App.Configuration;

public class Config
{
/// <summary>
/// Absolute or relative path to the app settings folder.
/// </summary>
public string? AppSettingsPath { get; init; }
}
11 changes: 11 additions & 0 deletions src/RepoM.App/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"profiles": {
"RepoM.App bin config": {
"commandName": "Project",
"commandLineArgs": "--App:AppSettingsPath RepoMConfig"
},
"RepoM.App": {
"commandName": "Project"
}
}
}
8 changes: 8 additions & 0 deletions src/RepoM.App/RepoM.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
Expand Down Expand Up @@ -64,6 +65,13 @@
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.Development.json">
<CopyToOutputDirectory Condition="$(Configuration) == 'Release'">Never</CopyToOutputDirectory>
<CopyToOutputDirectory Condition="$(Configuration) == 'Debug'">PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.serilog.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
9 changes: 9 additions & 0 deletions src/RepoM.App/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// begin-snippet: appsettings-appsettingspath
{
"App": {
// Absolute or relative path to the configuration directory.
// like: "AppSettingsPath": "C:/my-config/",
"AppSettingsPath": "MyConfig"
}
}
// end-snippet
5 changes: 5 additions & 0 deletions src/RepoM.App/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"App": {
"AppSettingsPath": null
}
}
9 changes: 5 additions & 4 deletions tests/RepoM.App.Tests/BootstrapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace RepoM.App.Tests;
using FakeItEasy;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using RepoM.Core.Plugin.Common;
using SimpleInjector;
using Xunit;
using Sut = RepoM.App.Bootstrapper;
Expand All @@ -17,8 +18,8 @@ public void Container_ShouldAlwaysBeSameInstance()
// arrange

// act
var result1 = Sut.Container;
var result2 = Sut.Container;
Container result1 = Sut.Container;
Container result2 = Sut.Container;

// assert
result1.Should().NotBeNull().And.Subject.Should().BeSameAs(result2);
Expand All @@ -30,7 +31,7 @@ public void RegisterServices_ShouldNotThrow()
// arrange

// act
Action act = () => Sut.RegisterServices(A.Dummy<IFileSystem>());
Action act = () => Sut.RegisterServices(A.Dummy<IFileSystem>(), A.Dummy<IAppDataPathProvider>());

// assert
act.Should().NotThrow();
Expand All @@ -55,7 +56,7 @@ public void RegisterServices_ShouldResultInValidContainer()
// arrange

// act
Sut.RegisterServices(A.Dummy<IFileSystem>());
Sut.RegisterServices(A.Dummy<IFileSystem>(), A.Dummy<IAppDataPathProvider>());
Sut.RegisterLogging(A.Fake<ILoggerFactory>());

// assert
Expand Down

0 comments on commit 1e41388

Please sign in to comment.