-
Notifications
You must be signed in to change notification settings - Fork 0
feat: stabilize local dev paths with ContentRoot-based resolution #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 40 commits
9d3fe04
af3fc33
912efcb
a29a83e
91d7c4f
65a8708
6bea872
3c1680d
1843263
bd585e9
74cc0c1
95c5442
848dc83
72f6193
0267720
f06d6f3
0992ba3
11f115b
6fa1224
7f7ef90
dac829e
1bd22d0
4c0414e
5152d4f
0466221
19c8e2f
2001330
8da1706
6c73796
8628f3a
1d696bd
4946f8b
4c793b6
f854f7f
a52919f
a0d2aee
65bf53c
4cb9208
60aa2a9
8a4aff4
11484c9
ed9a68a
8154071
4224d0c
b10f984
22b2bd9
7874dd2
e69e56e
dc22ba0
c2b11cf
a276850
587a40e
0e7ae98
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| using Microsoft.AspNetCore.Hosting; | ||
| using Microsoft.Extensions.Configuration; | ||
|
|
||
| namespace Motely.API; | ||
|
|
||
| /// <summary> | ||
| /// Centralized path resolver for Motely directories. | ||
| /// Uses ASP.NET Core ContentRootPath as the base, with optional config overrides. | ||
| /// IMPORTANT: Must call Initialize(IWebHostEnvironment, IConfiguration?) at application startup | ||
| /// before accessing any path properties. Accessing paths before initialization will throw InvalidOperationException. | ||
| /// </summary> | ||
| public static class MotelyPaths | ||
| { | ||
| private static volatile string _contentRoot = Directory.GetCurrentDirectory(); | ||
| private static string? _jamlFiltersOverride; | ||
| private static string? _seedSourcesOverride; | ||
| private static string? _searchResultsOverride; | ||
| private static volatile bool _isInitialized = false; | ||
|
|
||
| /// <summary> | ||
| /// Gets the content root path (typically the repo root). | ||
| /// </summary> | ||
| public static string ContentRoot | ||
| { | ||
| get | ||
| { | ||
| EnsureInitialized(); | ||
| return _contentRoot; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the directory for JAML filter files. | ||
| /// Defaults to ContentRoot/JamlFilters, can be overridden via config. | ||
| /// </summary> | ||
| public static string JamlFiltersDir | ||
| { | ||
| get | ||
| { | ||
| EnsureInitialized(); | ||
| return ResolvePath(_jamlFiltersOverride, "JamlFilters"); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the directory for seed source files (txt, csv, db). | ||
| /// Defaults to ContentRoot/SeedSources, can be overridden via config. | ||
| /// </summary> | ||
| public static string SeedSourcesDir | ||
| { | ||
| get | ||
| { | ||
| EnsureInitialized(); | ||
| return ResolvePath(_seedSourcesOverride, "SeedSources"); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the directory for search result databases and metadata. | ||
| /// Defaults to ContentRoot/SearchResults, can be overridden via config. | ||
| /// </summary> | ||
| public static string SearchResultsDir | ||
| { | ||
| get | ||
| { | ||
| EnsureInitialized(); | ||
| return ResolvePath(_searchResultsOverride, "SearchResults"); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes MotelyPaths with the web host environment and configuration. | ||
| /// Should be called once at application startup. | ||
| /// </summary> | ||
| public static void Initialize(IWebHostEnvironment env, IConfiguration? config = null) | ||
| { | ||
| _contentRoot = env.ContentRootPath; | ||
|
|
||
| if (config != null) | ||
| { | ||
| _jamlFiltersOverride = config["Motely:Paths:JamlFiltersDir"]; | ||
| _seedSourcesOverride = config["Motely:Paths:SeedSourcesDir"]; | ||
| _searchResultsOverride = config["Motely:Paths:SearchResultsDir"]; | ||
| } | ||
|
||
|
|
||
| // Ensure directories exist (using ResolvePath directly to avoid EnsureInitialized check) | ||
| Directory.CreateDirectory(ResolvePath(_jamlFiltersOverride, "JamlFilters")); | ||
| Directory.CreateDirectory(ResolvePath(_seedSourcesOverride, "SeedSources")); | ||
| Directory.CreateDirectory(ResolvePath(_searchResultsOverride, "SearchResults")); | ||
|
|
||
| // Mark as initialized after all setup is complete | ||
| _isInitialized = true; | ||
joirunner marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
Comment on lines
+78
to
+113
|
||
|
|
||
| /// <summary> | ||
| /// Ensures that Initialize has been called before accessing path properties. | ||
| /// Thread-safe: uses volatile _isInitialized field for proper memory ordering. | ||
| /// Initialization should complete before any concurrent path access begins. | ||
| /// </summary> | ||
| private static void EnsureInitialized() | ||
| { | ||
| if (!_isInitialized) | ||
| { | ||
| throw new InvalidOperationException( | ||
| "MotelyPaths.Initialize must be called before accessing path properties. " + | ||
| "Call MotelyPaths.Initialize(IWebHostEnvironment, IConfiguration?) at application startup."); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Resolves a path: if override is provided and is absolute, use it; | ||
| /// if override is relative, combine with ContentRoot; | ||
| /// otherwise use default relative to ContentRoot. | ||
| /// </summary> | ||
| private static string ResolvePath(string? overridePath, string defaultSubDir) | ||
| { | ||
| if (!string.IsNullOrWhiteSpace(overridePath)) | ||
| { | ||
| // If override is an absolute path, use it as-is | ||
| if (Path.IsPathRooted(overridePath)) | ||
| { | ||
| return overridePath; | ||
| } | ||
| // If override is relative, combine with ContentRoot | ||
| return Path.Combine(_contentRoot, overridePath); | ||
| } | ||
|
|
||
| // Default: combine ContentRoot with default subdirectory | ||
| return Path.Combine(_contentRoot, defaultSubDir); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.