зеркало из https://github.com/Azure/TypeEdge.git
logging and dotenv refactoring
This commit is contained in:
Родитель
132abc0100
Коммит
c6ed1b190d
|
@ -2,14 +2,16 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>TypeEdge.Host</id>
|
||||
<version>0.3.6</version>
|
||||
<version>0.3.9</version>
|
||||
<authors>paloukari</authors>
|
||||
<owners>paloukari</owners>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Package Description</description>
|
||||
<dependencies>
|
||||
<group targetFramework=".NETCoreApp2.1">
|
||||
<dependency id="TypeEdge" version="0.3.6" exclude="Build,Analyzers" />
|
||||
<dependency id="TypeEdge" version="0.3.9" exclude="Build,Analyzers" />
|
||||
<dependency id="Docker.DotNet" version="3.125.2.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Serilog.Extensions.Logging" version="1.4.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Serilog.Extensions.Logging" version="1.4.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Serilog.Sinks.Console" version="2.1.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.DependencyInjection" version="2.1.1" exclude="Build,Analyzers" />
|
||||
|
@ -61,8 +63,8 @@
|
|||
</contentFiles>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
|
||||
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Docker.dll" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Docker.dll" />
|
||||
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Docker.pdb" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Docker.pdb" />
|
||||
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Core.dll" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Core.dll" />
|
||||
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Core.pdb" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Core.pdb" />
|
||||
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Hub.Amqp.dll" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Hub.Amqp.dll" />
|
||||
|
|
|
@ -116,7 +116,7 @@ namespace TypeEdge.Host.Docker
|
|||
await (await _dockerFactory.RemoveAsync(_moduleWithIdentity.Module)).ExecuteAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine($"{_moduleWithIdentity.Module.Name} not found");
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
<PackageId>TypeEdge.Proxy</PackageId>
|
||||
<Version>0.3.6</Version>
|
||||
<Version>0.3.9</Version>
|
||||
<Authors>paloukari</Authors>
|
||||
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
|
|
11
TypeEdge.sln
11
TypeEdge.sln
|
@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edg
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Storage", "IoT.Edge\edge-util\src\Microsoft.Azure.Devices.Edge.Storage\Microsoft.Azure.Devices.Edge.Storage.csproj", "{101A6115-688F-474A-AC5F-051FA6232349}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.Docker", "IoT.Edge\edge-agent\src\Microsoft.Azure.Devices.Edge.Agent.Docker\Microsoft.Azure.Devices.Edge.Agent.Docker.csproj", "{05407166-867F-4321-A3B2-0E5D56726C2B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
CodeCoverage|Any CPU = CodeCoverage|Any CPU
|
||||
|
@ -153,6 +155,14 @@ Global
|
|||
{101A6115-688F-474A-AC5F-051FA6232349}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{101A6115-688F-474A-AC5F-051FA6232349}.TemplateDevelopment|Any CPU.ActiveCfg = CodeCoverage|Any CPU
|
||||
{101A6115-688F-474A-AC5F-051FA6232349}.TemplateDevelopment|Any CPU.Build.0 = CodeCoverage|Any CPU
|
||||
{05407166-867F-4321-A3B2-0E5D56726C2B}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU
|
||||
{05407166-867F-4321-A3B2-0E5D56726C2B}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU
|
||||
{05407166-867F-4321-A3B2-0E5D56726C2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{05407166-867F-4321-A3B2-0E5D56726C2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{05407166-867F-4321-A3B2-0E5D56726C2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{05407166-867F-4321-A3B2-0E5D56726C2B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{05407166-867F-4321-A3B2-0E5D56726C2B}.TemplateDevelopment|Any CPU.ActiveCfg = CodeCoverage|Any CPU
|
||||
{05407166-867F-4321-A3B2-0E5D56726C2B}.TemplateDevelopment|Any CPU.Build.0 = CodeCoverage|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -169,6 +179,7 @@ Global
|
|||
{A7DEAB53-6DCB-4287-8DED-C0358BCB4EBB} = {ABC622C1-4D24-48CF-8B0D-CA63C27CB444}
|
||||
{ED0444A3-03A3-4B84-854F-CC6BC53DEBB8} = {ABC622C1-4D24-48CF-8B0D-CA63C27CB444}
|
||||
{101A6115-688F-474A-AC5F-051FA6232349} = {ABC622C1-4D24-48CF-8B0D-CA63C27CB444}
|
||||
{05407166-867F-4321-A3B2-0E5D56726C2B} = {ABC622C1-4D24-48CF-8B0D-CA63C27CB444}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {114E5850-267D-4B44-ACD5-91ACA382DDBC}
|
||||
|
|
|
@ -3,57 +3,30 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
||||
//copied from https://github.com/frozzare/dotnet-dotenv
|
||||
|
||||
namespace TypeEdge.DovEnv
|
||||
{
|
||||
public class Dotenv
|
||||
{
|
||||
public static readonly string DefaultPath = "./.env";
|
||||
public static readonly string DefaultPath = ".env";
|
||||
|
||||
private readonly Dictionary<string, string> _variables = new Dictionary<string, string>();
|
||||
|
||||
public static Dotenv Load(string path)
|
||||
public static Dotenv Read(Stream input)
|
||||
{
|
||||
if (String.IsNullOrEmpty(path))
|
||||
{
|
||||
path = DefaultPath;
|
||||
}
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
throw new Exception("The .env file don't exists in the current directory");
|
||||
}
|
||||
|
||||
var content = File.ReadAllText(path);
|
||||
|
||||
return new Dotenv(content);
|
||||
}
|
||||
|
||||
public static Dotenv Load(Stream input)
|
||||
{
|
||||
string dotEnvContent;
|
||||
// Ensure resources are cleaned up after the read...
|
||||
using (var reader = new StreamReader(input))
|
||||
{
|
||||
dotEnvContent = reader.ReadToEnd();
|
||||
return new Dotenv(reader.ReadToEnd());
|
||||
}
|
||||
return new Dotenv(dotEnvContent);
|
||||
}
|
||||
|
||||
public Dictionary<string, string> GetVariables()
|
||||
public Dictionary<string, string> GetData()
|
||||
{
|
||||
// Return a copy so caller cannot modify internal state.
|
||||
return CloneVariables();
|
||||
return _variables.DeepClone();
|
||||
}
|
||||
|
||||
private Dictionary<string, string> CloneVariables()
|
||||
{
|
||||
var clone = new Dictionary<string, string>(_variables.Count, _variables.Comparer);
|
||||
foreach (var item in _variables)
|
||||
{
|
||||
clone.Add(item.Key, item.Value);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
protected Dotenv(string content)
|
||||
{
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
using System.IO;
|
||||
|
||||
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace TypeEdge.DovEnv
|
||||
{
|
||||
public class DotenvConfigurationProvider : FileConfigurationProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance.
|
||||
/// </summary>
|
||||
public DotenvConfigurationProvider(DotenvConfigurationSource source) : base(source)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the dotenv variables.
|
||||
/// </summary>
|
||||
public override void Load(Stream stream)
|
||||
{
|
||||
Data = Dotenv.Load(stream).GetVariables();
|
||||
Data = Dotenv.Read(stream).GetData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using PhysicalFileProvider = TypeEdge.DovEnv.FileProvider.PhysicalFileProvider;
|
||||
|
||||
namespace TypeEdge.DovEnv
|
||||
{
|
||||
public static class DotenvExtension
|
||||
{
|
||||
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder)
|
||||
{
|
||||
return AddDotenvFile(builder, null, string.Empty, false, false);
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path)
|
||||
{
|
||||
return AddDotenvFile(builder, null, path, false, false);
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path, bool optional)
|
||||
{
|
||||
return AddDotenvFile(builder, null, path, optional, false);
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)
|
||||
{
|
||||
return AddDotenvFile(builder, null, path, optional, reloadOnChange);
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange)
|
||||
{
|
||||
// Bail if builder is null.
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
// Use the default value if value is empty.
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
path = Dotenv.DefaultPath;
|
||||
path = path.Replace("./", "");
|
||||
}
|
||||
|
||||
#if NET451
|
||||
var basePath1 = AppDomain.CurrentDomain.GetData("APP_CONTEXT_BASE_DIRECTORY") as string
|
||||
?? AppDomain.CurrentDomain.BaseDirectory
|
||||
?? string.Empty;
|
||||
#else
|
||||
var basePath1 = AppContext.BaseDirectory ?? string.Empty;
|
||||
#endif
|
||||
var basePaths = new List<string>() { basePath1 };
|
||||
|
||||
// Since we shouldn't relay on asp.net core hosting package
|
||||
// and then we can't get content root path and because of
|
||||
// https://github.com/aspnet/FileSystem/issues/232
|
||||
// we have to create two different paths that we can try to read the dotenv file from.
|
||||
if (basePath1.Contains("/bin/"))
|
||||
{
|
||||
var basePath2 = basePath1.Split(new string[] { "bin" }, StringSplitOptions.None)[0];
|
||||
basePaths.Add(basePath2.TrimEnd('/'));
|
||||
}
|
||||
|
||||
if (provider == null)
|
||||
{
|
||||
//--
|
||||
// The below is still needed for .NET Core 1.x
|
||||
//--
|
||||
var fileExists = path.StartsWith('/') && File.Exists(path);
|
||||
if (!fileExists)
|
||||
foreach (var basePath in basePaths)
|
||||
{
|
||||
var testPath = string.Join("/", new string[] { basePath, path });
|
||||
//Console.WriteLine($"DotEnv: checking for {testPath}");
|
||||
if (!File.Exists(testPath))
|
||||
continue;
|
||||
fileExists = true;
|
||||
path = testPath;
|
||||
break;
|
||||
}
|
||||
if (!fileExists && !optional)
|
||||
{
|
||||
throw new Exception($"The .env configuration file '{path}' was not found");
|
||||
}
|
||||
if (Path.IsPathRooted(path))
|
||||
{
|
||||
// Real PhysicalFileProvider has a bug that don't allow dot files:
|
||||
// https://github.com/aspnet/FileSystem/issues/232
|
||||
provider = new PhysicalFileProvider(Path.GetDirectoryName(path));
|
||||
path = Path.GetFileName(path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//--
|
||||
// For .NET Core 2.0 and above, the PhysicalFileProvider has ways to deal
|
||||
// with hidden files.
|
||||
// See the change: https://github.com/aspnet/FileSystem/pull/280/files
|
||||
// This also allowed MockFileProvider to be plugged in for unit testing.
|
||||
//--
|
||||
if (!provider.GetFileInfo(path).Exists)
|
||||
{
|
||||
throw new Exception($"The configuration file {path} could not be found.");
|
||||
}
|
||||
}
|
||||
|
||||
var source = new DotenvConfigurationSource
|
||||
{
|
||||
Path = path,
|
||||
Optional = optional,
|
||||
FileProvider = provider,
|
||||
ReloadOnChange = reloadOnChange
|
||||
};
|
||||
builder.Add(source);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace TypeEdge.DovEnv.FileProvider
|
||||
{
|
||||
public class CompositeChangeToken : IChangeToken
|
||||
{
|
||||
private static readonly Action<object> _onChangeDelegate = OnChange;
|
||||
private readonly object _callbackLock = new object();
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private bool _registeredCallbackProxy;
|
||||
private List<IDisposable> _disposables;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="CompositeChangeToken"/>.
|
||||
/// </summary>
|
||||
/// <param name="changeTokens">The list of <see cref="IChangeToken"/> to compose.</param>
|
||||
public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens)
|
||||
{
|
||||
ChangeTokens = changeTokens ?? throw new ArgumentNullException(nameof(changeTokens));
|
||||
for (var i = 0; i < ChangeTokens.Count; i++)
|
||||
{
|
||||
if (ChangeTokens[i].ActiveChangeCallbacks)
|
||||
{
|
||||
ActiveChangeCallbacks = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list of <see cref="IChangeToken"/> which compose the current <see cref="CompositeChangeToken"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IChangeToken> ChangeTokens { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
|
||||
{
|
||||
EnsureCallbacksInitialized();
|
||||
return _cancellationTokenSource.Token.Register(callback, state);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasChanged
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cancellationTokenSource != null && _cancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < ChangeTokens.Count; i++)
|
||||
{
|
||||
if (ChangeTokens[i].HasChanged)
|
||||
{
|
||||
OnChange(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ActiveChangeCallbacks { get; }
|
||||
|
||||
private void EnsureCallbacksInitialized()
|
||||
{
|
||||
if (_registeredCallbackProxy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_callbackLock)
|
||||
{
|
||||
if (_registeredCallbackProxy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_disposables = new List<IDisposable>();
|
||||
for (var i = 0; i < ChangeTokens.Count; i++)
|
||||
{
|
||||
if (ChangeTokens[i].ActiveChangeCallbacks)
|
||||
{
|
||||
var disposable = ChangeTokens[i].RegisterChangeCallback(_onChangeDelegate, this);
|
||||
_disposables.Add(disposable);
|
||||
}
|
||||
}
|
||||
_registeredCallbackProxy = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnChange(object state)
|
||||
{
|
||||
var compositeChangeTokenState = (CompositeChangeToken)state;
|
||||
if (compositeChangeTokenState._cancellationTokenSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (compositeChangeTokenState._callbackLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
compositeChangeTokenState._cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
var disposables = compositeChangeTokenState._disposables;
|
||||
Debug.Assert(disposables != null);
|
||||
for (var i = 0; i < disposables.Count; i++)
|
||||
{
|
||||
disposables[i].Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,263 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.FileProviders.Internal;
|
||||
using Microsoft.Extensions.FileProviders.Physical;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using PhysicalFilesWatcher = TypeEdge.DovEnv.FileProvider.PhysicalFilesWatcher;
|
||||
|
||||
namespace TypeEdge.DovEnv.FileProvider
|
||||
{
|
||||
public sealed class PhysicalFileProvider : IFileProvider, IDisposable
|
||||
{
|
||||
private const string PollingEnvironmentKey = "DOTNET_USE_POLLING_FILE_WATCHER";
|
||||
|
||||
private static readonly char[] _invalidFileNameChars = Path.GetInvalidFileNameChars()
|
||||
.Where(c => c != Path.DirectorySeparatorChar && c != Path.AltDirectorySeparatorChar).ToArray();
|
||||
|
||||
private static readonly char[] _invalidFilterChars = _invalidFileNameChars
|
||||
.Where(c => c != '*' && c != '|' && c != '?').ToArray();
|
||||
|
||||
private static readonly char[] _pathSeparators = new[]
|
||||
{Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar};
|
||||
|
||||
private readonly PhysicalFilesWatcher _filesWatcher;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of a PhysicalFileProvider at the given root directory.
|
||||
/// </summary>
|
||||
/// <param name="root">The root directory. This should be an absolute path.</param>
|
||||
public PhysicalFileProvider(string root)
|
||||
: this(root, CreateFileWatcher(root))
|
||||
{
|
||||
}
|
||||
|
||||
internal PhysicalFileProvider(string root, PhysicalFilesWatcher physicalFilesWatcher)
|
||||
{
|
||||
if (!Path.IsPathRooted(root))
|
||||
{
|
||||
throw new ArgumentException("The path must be absolute.", nameof(root));
|
||||
}
|
||||
var fullRoot = Path.GetFullPath(root);
|
||||
// When we do matches in GetFullPath, we want to only match full directory names.
|
||||
Root = EnsureTrailingSlash(fullRoot);
|
||||
if (!Directory.Exists(Root))
|
||||
{
|
||||
throw new DirectoryNotFoundException(Root);
|
||||
}
|
||||
|
||||
_filesWatcher = physicalFilesWatcher;
|
||||
}
|
||||
|
||||
private static PhysicalFilesWatcher CreateFileWatcher(string root)
|
||||
{
|
||||
var environmentValue = Environment.GetEnvironmentVariable(PollingEnvironmentKey);
|
||||
var pollForChanges = string.Equals(environmentValue, "1", StringComparison.Ordinal) ||
|
||||
string.Equals(environmentValue, "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
root = EnsureTrailingSlash(Path.GetFullPath(root));
|
||||
return new PhysicalFilesWatcher(root, new FileSystemWatcher(root), pollForChanges);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the provider. Change tokens may not trigger after the provider is disposed.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_filesWatcher.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The root directory for this instance.
|
||||
/// </summary>
|
||||
public string Root { get; }
|
||||
|
||||
private string GetFullPath(string path)
|
||||
{
|
||||
if (PathNavigatesAboveRoot(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string fullPath;
|
||||
try
|
||||
{
|
||||
fullPath = Path.GetFullPath(Path.Combine(Root, path));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!IsUnderneathRoot(fullPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
private bool PathNavigatesAboveRoot(string path)
|
||||
{
|
||||
var tokenizer = new StringTokenizer(path, _pathSeparators);
|
||||
var depth = 0;
|
||||
|
||||
foreach (var segment in tokenizer)
|
||||
{
|
||||
if (segment.Equals(".", StringComparison.OrdinalIgnoreCase) || segment.Equals("", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (segment.Equals("..", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
depth--;
|
||||
|
||||
if (depth == -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsUnderneathRoot(string fullPath)
|
||||
{
|
||||
return fullPath.StartsWith(Root, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string EnsureTrailingSlash(string path)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(path) &&
|
||||
path[path.Length - 1] != Path.DirectorySeparatorChar)
|
||||
{
|
||||
return path + Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static bool HasInvalidPathChars(string path)
|
||||
{
|
||||
return path.IndexOfAny(_invalidFileNameChars) != -1;
|
||||
}
|
||||
|
||||
private static bool HasInvalidFilterChars(string path)
|
||||
{
|
||||
return path.IndexOfAny(_invalidFilterChars) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locate a file at the given path by directly mapping path segments to physical directories.
|
||||
/// </summary>
|
||||
/// <param name="subpath">A path under the root directory</param>
|
||||
/// <returns>The file information. Caller must check Exists property. </returns>
|
||||
public IFileInfo GetFileInfo(string subpath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(subpath) || HasInvalidPathChars(subpath))
|
||||
{
|
||||
return new NotFoundFileInfo(subpath);
|
||||
}
|
||||
|
||||
// Relative paths starting with leading slashes are okay
|
||||
subpath = subpath.TrimStart(_pathSeparators);
|
||||
|
||||
// Absolute paths not permitted.
|
||||
if (Path.IsPathRooted(subpath))
|
||||
{
|
||||
return new NotFoundFileInfo(subpath);
|
||||
}
|
||||
|
||||
var fullPath = GetFullPath(subpath);
|
||||
if (fullPath == null)
|
||||
{
|
||||
return new NotFoundFileInfo(subpath);
|
||||
}
|
||||
|
||||
return new PhysicalFileInfo(new FileInfo(fullPath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate a directory at the given path, if any.
|
||||
/// </summary>
|
||||
/// <param name="subpath">A path under the root directory. Leading slashes are ignored.</param>
|
||||
/// <returns>
|
||||
/// Contents of the directory. Caller must check Exists property. <see cref="NotFoundDirectoryContents" /> if
|
||||
/// <paramref name="subpath" /> is absolute, if the directory does not exist, or <paramref name="subpath" /> has invalid
|
||||
/// characters.
|
||||
/// </returns>
|
||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (subpath == null || HasInvalidPathChars(subpath))
|
||||
{
|
||||
return NotFoundDirectoryContents.Singleton;
|
||||
}
|
||||
|
||||
// Relative paths starting with leading slashes are okay
|
||||
subpath = subpath.TrimStart(_pathSeparators);
|
||||
|
||||
// Absolute paths not permitted.
|
||||
if (Path.IsPathRooted(subpath))
|
||||
{
|
||||
return NotFoundDirectoryContents.Singleton;
|
||||
}
|
||||
|
||||
var fullPath = GetFullPath(subpath);
|
||||
if (fullPath == null || !Directory.Exists(fullPath))
|
||||
{
|
||||
return NotFoundDirectoryContents.Singleton;
|
||||
}
|
||||
|
||||
return new PhysicalDirectoryContents(fullPath);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
return NotFoundDirectoryContents.Singleton;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Creates a <see cref="IChangeToken" /> for the specified <paramref name="filter" />.</para>
|
||||
/// <para>Globbing patterns are interpreted by <seealso cref="Microsoft.Extensions.FileSystemGlobbing.Matcher" />.</para>
|
||||
/// </summary>
|
||||
/// <param name="filter">
|
||||
/// Filter string used to determine what files or folders to monitor. Example: **/*.cs, *.*,
|
||||
/// subFolder/**/*.cshtml.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// An <see cref="IChangeToken" /> that is notified when a file matching <paramref name="filter" /> is added,
|
||||
/// modified or deleted. Returns a <see cref="NullChangeToken" /> if <paramref name="filter" /> has invalid filter
|
||||
/// characters or if <paramref name="filter" /> is an absolute path or outside the root directory specified in the
|
||||
/// constructor <seealso cref="PhysicalFileProvider(string)" />.
|
||||
/// </returns>
|
||||
public IChangeToken Watch(string filter)
|
||||
{
|
||||
if (filter == null || HasInvalidFilterChars(filter))
|
||||
{
|
||||
return NullChangeToken.Singleton;
|
||||
}
|
||||
|
||||
// Relative paths starting with leading slashes are okay
|
||||
filter = filter.TrimStart(_pathSeparators);
|
||||
|
||||
// Absolute paths and paths traversing above root not permitted.
|
||||
if (Path.IsPathRooted(filter) || PathNavigatesAboveRoot(filter))
|
||||
{
|
||||
return NullChangeToken.Singleton;
|
||||
}
|
||||
|
||||
return _filesWatcher.CreateFileChangeToken(filter);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,333 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.FileProviders.Physical;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace TypeEdge.DovEnv.FileProvider
|
||||
{
|
||||
public class PhysicalFilesWatcher : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ChangeTokenInfo> _filePathTokenLookup =
|
||||
new ConcurrentDictionary<string, ChangeTokenInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<string, ChangeTokenInfo> _wildcardTokenLookup =
|
||||
new ConcurrentDictionary<string, ChangeTokenInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly FileSystemWatcher _fileWatcher;
|
||||
private readonly object _fileWatcherLock = new object();
|
||||
private readonly string _root;
|
||||
private readonly bool _pollForChanges;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="PhysicalFilesWatcher" /> that watches files in <paramref name="root" />.
|
||||
/// Wraps an instance of <see cref="System.IO.FileSystemWatcher" />
|
||||
/// </summary>
|
||||
/// <param name="root">Root directory for the watcher</param>
|
||||
/// <param name="fileSystemWatcher">The wrapped watcher that is watching <paramref name="root" /></param>
|
||||
/// <param name="pollForChanges">
|
||||
/// True when the watcher should use polling to trigger instances of
|
||||
/// <see cref="IChangeToken" /> created by <see cref="CreateFileChangeToken(string)" />
|
||||
/// </param>
|
||||
public PhysicalFilesWatcher(
|
||||
string root,
|
||||
FileSystemWatcher fileSystemWatcher,
|
||||
bool pollForChanges)
|
||||
{
|
||||
_root = root;
|
||||
_fileWatcher = fileSystemWatcher;
|
||||
_fileWatcher.IncludeSubdirectories = true;
|
||||
_fileWatcher.Created += OnChanged;
|
||||
_fileWatcher.Changed += OnChanged;
|
||||
_fileWatcher.Renamed += OnRenamed;
|
||||
_fileWatcher.Deleted += OnChanged;
|
||||
_fileWatcher.Error += OnError;
|
||||
|
||||
_pollForChanges = pollForChanges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Creates an instance of <see cref="IChangeToken" /> for all files and directories that match the
|
||||
/// <paramref name="filter" />
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Globbing patterns are relative to the root directory given in the constructor
|
||||
/// <seealso cref="PhysicalFilesWatcher(string, FileSystemWatcher, bool)" />. Globbing patterns
|
||||
/// are interpreted by <seealso cref="Microsoft.Extensions.FileSystemGlobbing.Matcher" />.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="filter">A globbing pattern for files and directories to watch</param>
|
||||
/// <returns>A change token for all files that match the filter</returns>
|
||||
/// <exception cref="ArgumentNullException">When <paramref name="filter" /> is null</exception>
|
||||
public IChangeToken CreateFileChangeToken(string filter)
|
||||
{
|
||||
if (filter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filter));
|
||||
}
|
||||
|
||||
filter = NormalizePath(filter);
|
||||
|
||||
var changeToken = GetOrAddChangeToken(filter);
|
||||
TryEnableFileSystemWatcher();
|
||||
|
||||
return changeToken;
|
||||
}
|
||||
|
||||
private IChangeToken GetOrAddChangeToken(string pattern)
|
||||
{
|
||||
IChangeToken changeToken;
|
||||
var isWildCard = pattern.IndexOf('*') != -1;
|
||||
if (isWildCard || IsDirectoryPath(pattern))
|
||||
{
|
||||
changeToken = GetOrAddWildcardChangeToken(pattern);
|
||||
}
|
||||
else
|
||||
{
|
||||
changeToken = GetOrAddFilePathChangeToken(pattern);
|
||||
}
|
||||
|
||||
return changeToken;
|
||||
}
|
||||
|
||||
private IChangeToken GetOrAddFilePathChangeToken(string filePath)
|
||||
{
|
||||
ChangeTokenInfo tokenInfo;
|
||||
if (!_filePathTokenLookup.TryGetValue(filePath, out tokenInfo))
|
||||
{
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationChangeToken = new CancellationChangeToken(cancellationTokenSource.Token);
|
||||
tokenInfo = new ChangeTokenInfo(cancellationTokenSource, cancellationChangeToken);
|
||||
tokenInfo = _filePathTokenLookup.GetOrAdd(filePath, tokenInfo);
|
||||
}
|
||||
|
||||
IChangeToken changeToken = tokenInfo.ChangeToken;
|
||||
if (_pollForChanges)
|
||||
{
|
||||
// The expiry of CancellationChangeToken is controlled by this type and consequently we can cache it.
|
||||
// PollingFileChangeToken on the other hand manages its own lifetime and consequently we cannot cache it.
|
||||
changeToken = new CompositeChangeToken(
|
||||
new[]
|
||||
{
|
||||
changeToken,
|
||||
new PollingFileChangeToken(new FileInfo(filePath))
|
||||
});
|
||||
}
|
||||
|
||||
return changeToken;
|
||||
}
|
||||
|
||||
private IChangeToken GetOrAddWildcardChangeToken(string pattern)
|
||||
{
|
||||
ChangeTokenInfo tokenInfo;
|
||||
if (!_wildcardTokenLookup.TryGetValue(pattern, out tokenInfo))
|
||||
{
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationChangeToken = new CancellationChangeToken(cancellationTokenSource.Token);
|
||||
var matcher = new Matcher(StringComparison.OrdinalIgnoreCase);
|
||||
matcher.AddInclude(pattern);
|
||||
tokenInfo = new ChangeTokenInfo(cancellationTokenSource, cancellationChangeToken, matcher);
|
||||
tokenInfo = _wildcardTokenLookup.GetOrAdd(pattern, tokenInfo);
|
||||
}
|
||||
|
||||
IChangeToken changeToken = tokenInfo.ChangeToken;
|
||||
if (_pollForChanges)
|
||||
{
|
||||
// The expiry of CancellationChangeToken is controlled by this type and consequently we can cache it.
|
||||
// PollingFileChangeToken on the other hand manages its own lifetime and consequently we cannot cache it.
|
||||
changeToken = new CompositeChangeToken(
|
||||
new[]
|
||||
{
|
||||
changeToken,
|
||||
new PollingWildCardChangeToken(_root, pattern)
|
||||
});
|
||||
}
|
||||
|
||||
return changeToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the file watcher
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_fileWatcher.Dispose();
|
||||
}
|
||||
|
||||
private void OnRenamed(object sender, RenamedEventArgs e)
|
||||
{
|
||||
// For a file name change or a directory's name change notify registered tokens.
|
||||
OnFileSystemEntryChange(e.OldFullPath);
|
||||
OnFileSystemEntryChange(e.FullPath);
|
||||
|
||||
if (Directory.Exists(e.FullPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
// If the renamed entity is a directory then notify tokens for every sub item.
|
||||
foreach (
|
||||
var newLocation in
|
||||
Directory.EnumerateFileSystemEntries(e.FullPath, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
// Calculated previous path of this moved item.
|
||||
var oldLocation = Path.Combine(e.OldFullPath, newLocation.Substring(e.FullPath.Length + 1));
|
||||
OnFileSystemEntryChange(oldLocation);
|
||||
OnFileSystemEntryChange(newLocation);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (
|
||||
ex is IOException ||
|
||||
ex is SecurityException ||
|
||||
ex is DirectoryNotFoundException ||
|
||||
ex is UnauthorizedAccessException)
|
||||
{
|
||||
// Swallow the exception.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
OnFileSystemEntryChange(e.FullPath);
|
||||
}
|
||||
|
||||
private void OnError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
// Notify all cache entries on error.
|
||||
foreach (var path in _filePathTokenLookup.Keys)
|
||||
{
|
||||
ReportChangeForMatchedEntries(path);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFileSystemEntryChange(string fullPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var relativePath = fullPath.Substring(_root.Length);
|
||||
ReportChangeForMatchedEntries(relativePath);
|
||||
}
|
||||
catch (Exception ex) when (
|
||||
ex is IOException ||
|
||||
ex is SecurityException ||
|
||||
ex is UnauthorizedAccessException)
|
||||
{
|
||||
// Swallow the exception.
|
||||
}
|
||||
}
|
||||
|
||||
private void ReportChangeForMatchedEntries(string path)
|
||||
{
|
||||
path = NormalizePath(path);
|
||||
|
||||
var matched = false;
|
||||
ChangeTokenInfo matchInfo;
|
||||
if (_filePathTokenLookup.TryRemove(path, out matchInfo))
|
||||
{
|
||||
CancelToken(matchInfo);
|
||||
matched = true;
|
||||
}
|
||||
|
||||
foreach (var wildCardEntry in _wildcardTokenLookup)
|
||||
{
|
||||
var matchResult = wildCardEntry.Value.Matcher.Match(path);
|
||||
if (matchResult.HasMatches &&
|
||||
_wildcardTokenLookup.TryRemove(wildCardEntry.Key, out matchInfo))
|
||||
{
|
||||
CancelToken(matchInfo);
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched)
|
||||
{
|
||||
TryDisableFileSystemWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
private void TryDisableFileSystemWatcher()
|
||||
{
|
||||
lock (_fileWatcherLock)
|
||||
{
|
||||
if (_filePathTokenLookup.IsEmpty &&
|
||||
_wildcardTokenLookup.IsEmpty &&
|
||||
_fileWatcher.EnableRaisingEvents)
|
||||
{
|
||||
// Perf: Turn off the file monitoring if no files to monitor.
|
||||
_fileWatcher.EnableRaisingEvents = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryEnableFileSystemWatcher()
|
||||
{
|
||||
lock (_fileWatcherLock)
|
||||
{
|
||||
if ((!_filePathTokenLookup.IsEmpty || !_wildcardTokenLookup.IsEmpty) &&
|
||||
!_fileWatcher.EnableRaisingEvents)
|
||||
{
|
||||
// Perf: Turn off the file monitoring if no files to monitor.
|
||||
_fileWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizePath(string filter) => filter = filter.Replace('\\', '/');
|
||||
|
||||
private static bool IsDirectoryPath(string path)
|
||||
{
|
||||
return path.Length > 0 &&
|
||||
(path[path.Length - 1] == Path.DirectorySeparatorChar ||
|
||||
path[path.Length - 1] == Path.AltDirectorySeparatorChar);
|
||||
}
|
||||
|
||||
private static void CancelToken(ChangeTokenInfo matchInfo)
|
||||
{
|
||||
if (matchInfo.TokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
matchInfo.TokenSource.Cancel();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private struct ChangeTokenInfo
|
||||
{
|
||||
public ChangeTokenInfo(
|
||||
CancellationTokenSource tokenSource,
|
||||
CancellationChangeToken changeToken)
|
||||
: this(tokenSource, changeToken, matcher: null)
|
||||
{
|
||||
}
|
||||
|
||||
public ChangeTokenInfo(
|
||||
CancellationTokenSource tokenSource,
|
||||
CancellationChangeToken changeToken,
|
||||
Matcher matcher)
|
||||
{
|
||||
TokenSource = tokenSource;
|
||||
ChangeToken = changeToken;
|
||||
Matcher = matcher;
|
||||
}
|
||||
|
||||
public CancellationTokenSource TokenSource { get; }
|
||||
|
||||
public CancellationChangeToken ChangeToken { get; }
|
||||
|
||||
public Matcher Matcher { get; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,98 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TypeEdge.DovEnv;
|
||||
|
||||
namespace TypeEdge
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static IConfigurationBuilder AddDotenv(this IConfigurationBuilder builder)
|
||||
{
|
||||
return AddDotenv(builder, null, string.Empty, false, false);
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddDotenv(this IConfigurationBuilder builder, string path)
|
||||
{
|
||||
return AddDotenv(builder, null, path, false, false);
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path, bool optional)
|
||||
{
|
||||
return AddDotenv(builder, null, path, optional, false);
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)
|
||||
{
|
||||
return AddDotenv(builder, null, path, optional, reloadOnChange);
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddDotenv(this IConfigurationBuilder configurationBuilder, IFileProvider provider, string filePath, bool optional, bool reloadOnChange)
|
||||
{
|
||||
if (configurationBuilder == null)
|
||||
throw new ArgumentNullException(nameof(configurationBuilder));
|
||||
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
filePath = Dotenv.DefaultPath;
|
||||
|
||||
var lookUpPaths = new List<string>() { AppContext.BaseDirectory, "" };
|
||||
|
||||
if (provider == null)
|
||||
{
|
||||
bool exists = false;
|
||||
foreach (var lookUpPath in lookUpPaths)
|
||||
{
|
||||
var fullPath = Path.Join(lookUpPath, filePath);
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
exists = true;
|
||||
filePath = fullPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists && !optional)
|
||||
throw new Exception($"Could not locate {filePath}");
|
||||
}
|
||||
else
|
||||
if (!provider.GetFileInfo(filePath).Exists)
|
||||
throw new Exception($"Could not locate {filePath}");
|
||||
|
||||
if (Path.IsPathRooted(filePath))
|
||||
{
|
||||
provider = new PhysicalFileProvider(Path.GetDirectoryName(filePath), Microsoft.Extensions.FileProviders.Physical.ExclusionFilters.None);
|
||||
filePath = Path.GetFileName(filePath);
|
||||
}
|
||||
|
||||
var source = new DotenvConfigurationSource
|
||||
{
|
||||
Path = filePath,
|
||||
Optional = optional,
|
||||
FileProvider = provider,
|
||||
ReloadOnChange = reloadOnChange
|
||||
};
|
||||
configurationBuilder.Add(source);
|
||||
return configurationBuilder;
|
||||
|
||||
}
|
||||
public static Dictionary<TKey, TValue> DeepClone<TKey, TValue>
|
||||
(this Dictionary<TKey, TValue> original) where TValue : ICloneable
|
||||
{
|
||||
Dictionary<TKey, TValue> ret = new Dictionary<TKey, TValue>(original.Count,
|
||||
original.Comparer);
|
||||
foreach (KeyValuePair<TKey, TValue> entry in original)
|
||||
{
|
||||
ret.Add(entry.Key, (TValue)entry.Value.Clone());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Task WhenCanceled(this CancellationToken cancellationToken)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
@ -42,4 +124,4 @@ namespace TypeEdge
|
|||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Core;
|
||||
|
||||
namespace TypeEdge
|
||||
{
|
||||
//this code has been copied from the IoT Edge Runtime
|
||||
|
||||
public static class Logger
|
||||
{
|
||||
public const string RuntimeLogLevelEnvKey = "RuntimeLogLevel";
|
||||
|
||||
static readonly Dictionary<string, LogEventLevel> LogLevelDictionary = new Dictionary<string, LogEventLevel>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{"verbose", LogEventLevel.Verbose},
|
||||
{"debug", LogEventLevel.Debug},
|
||||
{"info", LogEventLevel.Information},
|
||||
{"information", LogEventLevel.Information},
|
||||
{"warning", LogEventLevel.Warning},
|
||||
{"error", LogEventLevel.Error},
|
||||
{"fatal", LogEventLevel.Fatal}
|
||||
};
|
||||
|
||||
static LogEventLevel logLevel = LogEventLevel.Information;
|
||||
|
||||
public static void SetLogLevel(string level)
|
||||
{
|
||||
logLevel = LogLevelDictionary.TryGetValue(level.ToLower(), out LogEventLevel value) ? value : LogEventLevel.Information;
|
||||
}
|
||||
|
||||
public static LogEventLevel GetLogLevel() => logLevel;
|
||||
|
||||
static readonly Lazy<ILoggerFactory> LoggerLazy = new Lazy<ILoggerFactory>(() => GetLoggerFactory(), true);
|
||||
|
||||
public static ILoggerFactory Factory => LoggerLazy.Value;
|
||||
|
||||
static ILoggerFactory GetLoggerFactory()
|
||||
{
|
||||
var levelSwitch = new LoggingLevelSwitch();
|
||||
levelSwitch.MinimumLevel = logLevel;
|
||||
Serilog.Core.Logger loggerConfig = new LoggerConfiguration()
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext:1}] - {Message}{NewLine}{Exception}"
|
||||
)
|
||||
.CreateLogger();
|
||||
if (levelSwitch.MinimumLevel <= LogEventLevel.Debug)
|
||||
{
|
||||
// Overwrite with richer content if less then debug
|
||||
loggerConfig = new LoggerConfiguration()
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext:1}] - {Message}{NewLine}{Exception}"
|
||||
)
|
||||
.CreateLogger();
|
||||
}
|
||||
ILoggerFactory factory = new LoggerFactory()
|
||||
.AddSerilog(loggerConfig);
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ using Autofac;
|
|||
using Castle.DynamicProxy;
|
||||
using TypeEdge.Proxy;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
[assembly: InternalsVisibleTo("TypeEdge.Host")]
|
||||
[assembly: InternalsVisibleTo("TypeEdge.Proxy")]
|
||||
|
@ -35,10 +36,11 @@ namespace TypeEdge.Modules
|
|||
private readonly Dictionary<string, MethodCallback> _methodSubscriptions;
|
||||
private readonly Dictionary<string, SubscriptionCallback> _routeSubscriptions;
|
||||
private readonly Dictionary<string, SubscriptionCallback> _twinSubscriptions;
|
||||
|
||||
private string _connectionString;
|
||||
private readonly ILogger _logger;
|
||||
private ModuleClient _ioTHubModuleClient;
|
||||
private ITransportSettings[] _transportSettings;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
protected TypeModule()
|
||||
|
@ -52,6 +54,8 @@ namespace TypeEdge.Modules
|
|||
DefaultTwin = new TwinCollection();
|
||||
Routes = new List<string>();
|
||||
|
||||
_logger = Logger.Factory.CreateLogger(GetType());
|
||||
|
||||
InstantiateProperties();
|
||||
}
|
||||
|
||||
|
@ -156,10 +160,10 @@ namespace TypeEdge.Modules
|
|||
RegisterMethods();
|
||||
|
||||
// Open a connection to the Edge runtime
|
||||
_ioTHubModuleClient = ModuleClient.CreateFromConnectionString(_connectionString, _transportSettings);
|
||||
_ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(_transportSettings);
|
||||
|
||||
await _ioTHubModuleClient.OpenAsync();
|
||||
//Console.WriteLine($"{Name}:IoT Hub module client initialized.");
|
||||
_logger?.LogInformation($"IoT Hub module client initialized.");
|
||||
|
||||
// Register callback to be called when a message is received by the module
|
||||
foreach (var subscription in _routeSubscriptions)
|
||||
|
@ -167,7 +171,7 @@ namespace TypeEdge.Modules
|
|||
await _ioTHubModuleClient.SetInputMessageHandlerAsync(subscription.Key, MessageHandler,
|
||||
subscription.Value);
|
||||
|
||||
//Console.WriteLine($"{Name}:MessageHandler set for {subscription.Key}");
|
||||
_logger?.LogInformation($"MessageHandler set for {subscription.Key}");
|
||||
}
|
||||
|
||||
// Register callback to be called when a twin update is received by the module
|
||||
|
@ -176,25 +180,25 @@ namespace TypeEdge.Modules
|
|||
foreach (var subscription in _methodSubscriptions)
|
||||
{
|
||||
await _ioTHubModuleClient.SetMethodHandlerAsync(subscription.Key, MethodCallback, subscription.Value);
|
||||
//Console.WriteLine($"{Name}:MethodCallback set for{subscription.Key}");
|
||||
_logger?.LogInformation($"MethodCallback set for{subscription.Key}");
|
||||
}
|
||||
|
||||
//Console.WriteLine($"{Name}:Running RunAsync..");
|
||||
_logger?.LogInformation($"Running RunAsync..");
|
||||
return await RunAsync(cancellationToken);
|
||||
}
|
||||
|
||||
internal virtual InitializationResult _Init(IConfigurationRoot configuration, IContainer container)
|
||||
{
|
||||
//Console.WriteLine($"{Name}:InternalConfigure called");
|
||||
|
||||
_connectionString = configuration.GetValue<string>($"{Constants.EdgeHubConnectionStringKey}");
|
||||
if (string.IsNullOrEmpty(_connectionString))
|
||||
throw new ArgumentException($"Missing {Constants.EdgeHubConnectionStringKey} in configuration for {Name}");
|
||||
_logger?.LogInformation($"InternalConfigure called");
|
||||
|
||||
var connectionString = configuration.GetValue<string>($"{Constants.EdgeHubConnectionStringKey}");
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
_logger?.LogWarning($"Missing {Constants.EdgeHubConnectionStringKey} variable.");
|
||||
|
||||
// Cert verification is not yet fully functional when using Windows OS for the container
|
||||
var bypassCertVerification = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
if (!bypassCertVerification)
|
||||
InstallCert();
|
||||
//var bypassCertVerification = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
//if (!bypassCertVerification)
|
||||
// InstallCert();
|
||||
|
||||
var settings = new AmqpTransportSettings(TransportType.Amqp_Tcp_Only);
|
||||
if (true) //bypassCertVerification)
|
||||
|
@ -208,7 +212,7 @@ namespace TypeEdge.Modules
|
|||
internal async Task<PublishResult> PublishMessageAsync<T>(string outputName, T message)
|
||||
where T : IEdgeMessage
|
||||
{
|
||||
//Console.WriteLine($"{Name}:PublishMessageAsync called");
|
||||
_logger?.LogInformation($"PublishMessageAsync called");
|
||||
var edgeMessage = new Message(message.GetBytes());
|
||||
|
||||
|
||||
|
@ -219,7 +223,7 @@ namespace TypeEdge.Modules
|
|||
await _ioTHubModuleClient.SendEventAsync(outputName, edgeMessage);
|
||||
|
||||
var messageString = Encoding.UTF8.GetString(message.GetBytes());
|
||||
//Console.WriteLine($">>>>>>{Name}: message: Body: [{messageString}]");
|
||||
_logger?.LogInformation($"Message: Body: [{messageString}]");
|
||||
|
||||
return PublishResult.Ok;
|
||||
}
|
||||
|
@ -228,7 +232,7 @@ namespace TypeEdge.Modules
|
|||
Func<T, Task<MessageResult>> handler)
|
||||
where T : IEdgeMessage
|
||||
{
|
||||
//Console.WriteLine($"{Name}:SubscribeRoute called");
|
||||
_logger?.LogInformation($"SubscribeRoute called");
|
||||
|
||||
if (outRoute != "$downstream")
|
||||
Routes.Add($"FROM {outRoute} INTO {inRoute}");
|
||||
|
@ -241,7 +245,7 @@ namespace TypeEdge.Modules
|
|||
|
||||
internal void SubscribeRoute(string outName, string outRoute, string inName, string inRoute)
|
||||
{
|
||||
//Console.WriteLine($"{Name}:SubscribeRoute called");
|
||||
_logger?.LogInformation($"SubscribeRoute called");
|
||||
|
||||
if (outRoute != "$downstream")
|
||||
Routes.Add($"FROM {outRoute} INTO {inRoute}");
|
||||
|
@ -249,7 +253,7 @@ namespace TypeEdge.Modules
|
|||
|
||||
internal void SubscribeTwin<T>(string name, Func<T, Task<TwinResult>> handler) where T : TypeTwin
|
||||
{
|
||||
//Console.WriteLine($"{Name}:SubscribeTwin called");
|
||||
_logger?.LogInformation($"SubscribeTwin called");
|
||||
|
||||
_twinSubscriptions[name] = new SubscriptionCallback(name, handler, typeof(T));
|
||||
}
|
||||
|
@ -257,7 +261,7 @@ namespace TypeEdge.Modules
|
|||
internal async Task ReportTwinAsync<T>(string name, T twin)
|
||||
where T : TypeTwin
|
||||
{
|
||||
//Console.WriteLine($"{Name}:ReportTwinAsync called");
|
||||
_logger?.LogInformation($"ReportTwinAsync called");
|
||||
await _ioTHubModuleClient.UpdateReportedPropertiesAsync(twin.GetReportedProperties());
|
||||
}
|
||||
|
||||
|
@ -292,7 +296,7 @@ namespace TypeEdge.Modules
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"{Name}:ERROR:{ex}");
|
||||
_logger?.LogError(ex, "Error in GetReferenceData.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -311,7 +315,7 @@ namespace TypeEdge.Modules
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"{Name}:ERROR:{ex}");
|
||||
_logger?.LogError(ex, "Error in SetReferenceData.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -327,7 +331,7 @@ namespace TypeEdge.Modules
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"{Name}:ERROR:{ex}");
|
||||
_logger?.LogError(ex, "Error in DeleteReferenceData.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -354,7 +358,7 @@ namespace TypeEdge.Modules
|
|||
|
||||
private Task<MethodResponse> MethodCallback(MethodRequest methodRequest, object userContext)
|
||||
{
|
||||
//Console.WriteLine($"{Name}:MethodCallback called");
|
||||
_logger?.LogInformation($"MethodCallback called");
|
||||
if (!(userContext is MethodCallback callback))
|
||||
throw new InvalidOperationException($"{Name}:UserContext doesn't contain a valid SubscriptionCallback");
|
||||
|
||||
|
@ -373,8 +377,7 @@ namespace TypeEdge.Modules
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"{Name}:ERROR:{ex}");
|
||||
|
||||
_logger?.LogError(ex, "Error in MethodCallback.");
|
||||
// Acknowlege the direct method call with a 400 error message
|
||||
var result = "{\"result\":\"" + ex.Message + "\"}";
|
||||
return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(result), 400));
|
||||
|
@ -383,18 +386,17 @@ namespace TypeEdge.Modules
|
|||
|
||||
private async Task<MessageResponse> MessageHandler(Message message, object userContext)
|
||||
{
|
||||
//Console.WriteLine($"{Name}:MessageHandler called");
|
||||
_logger?.LogInformation($"MessageHandler called");
|
||||
|
||||
if (!(userContext is SubscriptionCallback callback))
|
||||
throw new InvalidOperationException("UserContext doesn't contain a valid SubscriptionCallback");
|
||||
|
||||
var messageBytes = message.GetBytes();
|
||||
var messageString = Encoding.UTF8.GetString(messageBytes);
|
||||
//Console.WriteLine($"<<<<<<{Name}:message: Body: [{messageString}]");
|
||||
|
||||
if (!(Activator.CreateInstance(callback.Type) is IEdgeMessage input))
|
||||
{
|
||||
Console.WriteLine($"{Name}:Abandoned Message");
|
||||
_logger?.LogWarning("Abandoned Message");
|
||||
return MessageResponse.Abandoned;
|
||||
}
|
||||
|
||||
|
@ -408,13 +410,13 @@ namespace TypeEdge.Modules
|
|||
|
||||
private async Task PropertyHandler(TwinCollection desiredProperties, object userContext)
|
||||
{
|
||||
//Console.WriteLine($"{Name}:PropertyHandler called");
|
||||
_logger?.LogInformation($"PropertyHandler called");
|
||||
|
||||
if (!(userContext is Dictionary<string, SubscriptionCallback> callbacks))
|
||||
throw new InvalidOperationException("UserContext doesn't contain a valid SubscriptionCallback");
|
||||
|
||||
//Console.WriteLine($"{Name}:Desired property change:");
|
||||
//Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));
|
||||
_logger?.LogInformation($"Desired property change:");
|
||||
_logger?.LogInformation(JsonConvert.SerializeObject(desiredProperties));
|
||||
|
||||
foreach (var callback in callbacks)
|
||||
if (desiredProperties.Contains($"___{callback.Key}"))
|
||||
|
@ -432,21 +434,21 @@ namespace TypeEdge.Modules
|
|||
if (string.IsNullOrWhiteSpace(certPath))
|
||||
{
|
||||
// We cannot proceed further without a proper cert file
|
||||
Console.WriteLine($"Missing path to certificate collection file: {certPath}");
|
||||
_logger?.LogWarning($"Missing path to certificate collection file: {certPath}");
|
||||
throw new InvalidOperationException("Missing path to certificate file.");
|
||||
}
|
||||
|
||||
if (!File.Exists(certPath))
|
||||
{
|
||||
// We cannot proceed further without a proper cert file
|
||||
Console.WriteLine($"Missing path to certificate collection file: {certPath}");
|
||||
_logger?.LogWarning($"Missing path to certificate collection file: {certPath}");
|
||||
throw new InvalidOperationException("Missing certificate file.");
|
||||
}
|
||||
|
||||
var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
store.Add(new X509Certificate2(X509Certificate.CreateFromCertFile(certPath)));
|
||||
//Console.WriteLine($"{Name}:Added Cert: " + certPath);
|
||||
_logger?.LogInformation($"Added Cert: " + certPath);
|
||||
store.Close();
|
||||
}
|
||||
|
||||
|
@ -480,7 +482,7 @@ namespace TypeEdge.Modules
|
|||
|
||||
private void RegisterMethods()
|
||||
{
|
||||
//Console.WriteLine($"{Name}:RegisterMethods called");
|
||||
_logger?.LogInformation($"RegisterMethods called");
|
||||
var interfaceType = GetType().GetProxyInterface();
|
||||
|
||||
if (interfaceType == null)
|
||||
|
|
|
@ -84,7 +84,7 @@ namespace TypeEdge
|
|||
.AddJsonFile($"{moduleName}Settings.json", true)
|
||||
.AddEnvironmentVariables()
|
||||
.AddCommandLine(args)
|
||||
.AddDotenvFile(fileName)
|
||||
.AddDotenv(fileName)
|
||||
.Build();
|
||||
File.Delete(fileName);
|
||||
break;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
<PackageId>TypeEdge</PackageId>
|
||||
<Version>0.3.6</Version>
|
||||
<Version>0.3.9</Version>
|
||||
<Authors>paloukari</Authors>
|
||||
<PackageOutputPath>../../TypeEdgeNuGets</PackageOutputPath>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
|
@ -27,12 +27,14 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
|
||||
<PackageReference Include="Serilog" Version="2.7.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Загрузка…
Ссылка в новой задаче