|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Extensions.Configuration.KeyPerFile;
/// <summary>
/// A <see cref="ConfigurationProvider"/> that uses a directory's files as configuration key/values.
/// </summary>
public class KeyPerFileConfigurationProvider : ConfigurationProvider, IDisposable
{
private readonly IDisposable? _changeTokenRegistration;
KeyPerFileConfigurationSource Source { get; set; }
/// <summary>
/// Initializes a new instance.
/// </summary>
/// <param name="source">The settings.</param>
public KeyPerFileConfigurationProvider(KeyPerFileConfigurationSource source)
{
Source = source ?? throw new ArgumentNullException(nameof(source));
if (Source.ReloadOnChange && Source.FileProvider != null)
{
_changeTokenRegistration = ChangeToken.OnChange(
() => Source.FileProvider.Watch("*"),
() =>
{
Thread.Sleep(Source.ReloadDelay);
Load(reload: true);
});
}
}
private string NormalizeKey(string key)
=> key.Replace(Source.SectionDelimiter, ConfigurationPath.KeyDelimiter);
private static string TrimNewLine(string value)
=> value.EndsWith(Environment.NewLine, StringComparison.Ordinal)
? value.Substring(0, value.Length - Environment.NewLine.Length)
: value;
/// <summary>
/// Loads the configuration values.
/// </summary>
public override void Load()
{
Load(reload: false);
}
private void Load(bool reload)
{
var data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
if (Source.FileProvider == null)
{
if (Source.Optional || reload) // Always optional on reload
{
Data = data;
OnReload();
return;
}
throw new DirectoryNotFoundException("A non-null file provider for the directory is required when this source is not optional.");
}
var directory = Source.FileProvider.GetDirectoryContents("/");
if (!directory.Exists)
{
if (Source.Optional || reload) // Always optional on reload
{
Data = data;
OnReload();
return;
}
throw new DirectoryNotFoundException("The root directory for the FileProvider doesn't exist and is not optional.");
}
else
{
foreach (var file in directory)
{
if (file.IsDirectory)
{
continue;
}
using var stream = file.CreateReadStream();
using var streamReader = new StreamReader(stream);
if (Source.IgnoreCondition == null || !Source.IgnoreCondition(file.Name))
{
data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd()));
}
}
}
Data = data;
OnReload();
}
private string GetDirectoryName()
=> Source.FileProvider?.GetFileInfo("/")?.PhysicalPath ?? "<Unknown>";
/// <summary>
/// Generates a string representing this provider name and relevant details.
/// </summary>
/// <returns>The configuration name.</returns>
public override string ToString()
=> $"{GetType().Name} for files in '{GetDirectoryName()}' ({(Source.Optional ? "Optional" : "Required")})";
/// <inheritdoc />
public void Dispose()
{
_changeTokenRegistration?.Dispose();
}
}
|