using Microsoft.Extensions.Logging; namespace JdeScoping.ConfigManager.Services; /// /// Service for auto-discovering configuration file locations. /// Searches in prioritized order: /// 1. JDESCOPING_CONFIG_PATH environment variable /// 2. Same directory as executable /// 3. ../JdeScoping.Host/ relative to executable /// 4. User config directory (~/.jdescoping on Unix, %LOCALAPPDATA%\JdeScoping on Windows) /// public class AutoDiscoveryService : IAutoDiscoveryService { private readonly IFileSystem _fileSystem; private readonly ILogger? _logger; private const string EnvVarName = "JDESCOPING_CONFIG_PATH"; private const string AppSettingsFileName = "appsettings.json"; /// /// Initializes a new instance of the class. /// /// The file system abstraction to use for directory and file checks. /// Optional logger for recording discovery process information. public AutoDiscoveryService(IFileSystem fileSystem, ILogger? logger = null) { _fileSystem = fileSystem; _logger = logger; } /// /// Finds the configuration folder using a prioritized search strategy. /// /// Cancellation token for the async operation. /// The path to a valid configuration folder, or null if none is found. public Task FindConfigFolderAsync(CancellationToken ct = default) { // 1. Check environment variable var envPath = Environment.GetEnvironmentVariable(EnvVarName); if (!string.IsNullOrEmpty(envPath) && IsValidConfigFolder(envPath)) { _logger?.LogInformation("Found config folder from environment variable: {Path}", envPath); return Task.FromResult(envPath); } // 2. Check same directory as executable var exeDir = AppContext.BaseDirectory; if (IsValidConfigFolder(exeDir)) { _logger?.LogInformation("Found config folder in executable directory: {Path}", exeDir); return Task.FromResult(exeDir); } // 3. Check ../JdeScoping.Host/ relative to executable var hostDir = _fileSystem.Combine(exeDir, "..", "JdeScoping.Host"); if (IsValidConfigFolder(hostDir)) { _logger?.LogInformation("Found config folder in host directory: {Path}", hostDir); return Task.FromResult(hostDir); } // 4. Check project structure paths (for development) // When running from bin/Debug/net10.0, go up to find src/JdeScoping.Host var projectHostPaths = new[] { // From bin/Debug/net10.0 -> src/JdeScoping.Host _fileSystem.Combine(exeDir, "..", "..", "..", "..", "..", "JdeScoping.Host"), // Absolute fallback for development "/Users/dohertj2/Desktop/JdeScopingTool/NEW/src/JdeScoping.Host" }; foreach (var projectPath in projectHostPaths) { if (IsValidConfigFolder(projectPath)) { _logger?.LogInformation("Found config folder in project directory: {Path}", projectPath); return Task.FromResult(Path.GetFullPath(projectPath)); } } // 5. Check user config directory var userConfigDir = GetUserConfigDirectory(); if (userConfigDir != null && IsValidConfigFolder(userConfigDir)) { _logger?.LogInformation("Found config folder in user directory: {Path}", userConfigDir); return Task.FromResult(userConfigDir); } _logger?.LogWarning("Could not find config folder in any standard location"); return Task.FromResult(null); } private bool IsValidConfigFolder(string path) { if (!_fileSystem.DirectoryExists(path)) return false; var appSettingsPath = _fileSystem.Combine(path, AppSettingsFileName); return _fileSystem.FileExists(appSettingsPath); } private string? GetUserConfigDirectory() { if (OperatingSystem.IsWindows()) { var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); return _fileSystem.Combine(localAppData, "JdeScoping"); } else { var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); return _fileSystem.Combine(home, ".jdescoping"); } } }