using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO.Compression;
using System.Net.Http;
using System.Text;
using System.Threading;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace Shared.Engine
{
///
/// Codex AI - Module Repository
///
public static class ModuleRepository
{
private const string RepositoryFile = "module/repository.yaml";
private const string StateFile = "module/.repository_state.json";
private static readonly object SyncRoot = new object();
private static readonly HttpClient HttpClient;
private static ApplicationPartManager partManager;
private static Dictionary repositoryState;
static ModuleRepository()
{
HttpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(60)
};
if (!HttpClient.DefaultRequestHeaders.UserAgent.Any())
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LampacModuleRepository/1.0");
if (!HttpClient.DefaultRequestHeaders.Accept.Any())
HttpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github+json");
}
public static void Configuration(IMvcBuilder mvcBuilder)
{
partManager = mvcBuilder?.PartManager;
UpdateModules();
}
private static void UpdateModules()
{
if (!Monitor.TryEnter(SyncRoot))
{
Console.WriteLine("ModuleRepository: UpdateModules skipped because another update is running");
return;
}
Console.WriteLine("ModuleRepository: UpdateModules start");
try
{
var repositories = LoadConfiguration();
if (repositories.Count == 0)
{
Console.WriteLine("ModuleRepository: no repositories configured");
return;
}
Directory.CreateDirectory(Path.Combine(Environment.CurrentDirectory, "module"));
Console.WriteLine("ModuleRepository: ensured module directory exists");
var state = LoadState();
bool stateChanged = false;
var modulesToCompile = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach (var repository in repositories)
{
try
{
if (!repository.IsValid)
{
Console.WriteLine($"ModuleRepository: skipping invalid repository '{repository?.Url}'");
continue;
}
bool missingModule = repository.Folders.Any(folder => !Directory.Exists(Path.Combine(Environment.CurrentDirectory, "module", folder.ModuleName)));
string commitSha = GetLatestCommitSha(repository);
if (string.IsNullOrEmpty(commitSha))
{
Console.WriteLine($"ModuleRepository: could not determine latest commit for {repository.Url}");
continue;
}
string stateKey = repository.StateKey;
if (!missingModule && state.TryGetValue(stateKey, out string storedSha) && string.Equals(storedSha, commitSha, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"ModuleRepository: repository '{repository.Url}' is up-to-date (sha={commitSha})");
continue;
}
if (DownloadAndExtract(repository, modulesToCompile))
{
state[stateKey] = commitSha;
stateChanged = true;
}
}
catch (Exception ex)
{
Console.WriteLine($"ModuleRepository: error processing repository {repository?.Url} - {ex.Message}");
}
}
if (stateChanged)
{
SaveState(state);
Console.WriteLine("ModuleRepository: state saved");
}
}
catch (Exception ex)
{
Console.WriteLine($"module repository: {ex.Message}");
}
finally
{
Console.WriteLine("ModuleRepository: UpdateModules finished, releasing lock");
Monitor.Exit(SyncRoot);
}
}
private static List LoadConfiguration()
{
string path = Path.Combine(Environment.CurrentDirectory, RepositoryFile.Replace('/', Path.DirectorySeparatorChar));
if (!File.Exists(path))
{
Console.WriteLine($"ModuleRepository: repository config file not found at {path}");
return new List();
}
try
{
string yaml = File.ReadAllText(path);
if (string.IsNullOrWhiteSpace(yaml))
{
Console.WriteLine("ModuleRepository: repository config file is empty");
return new List();
}
var deserializer = new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
var document = deserializer.Deserialize(new StringReader(yaml));
if (document == null)
{
Console.WriteLine("ModuleRepository: repository config deserialized to null");
return new List();
}
var repos = ParseRepositories(document);
Console.WriteLine($"ModuleRepository: loaded {repos.Count} repository entries from config");
return repos;
}
catch (Exception ex)
{
Console.WriteLine($"module repository: failed to read configuration - {ex.Message}");
return new List();
}
}
private static List ParseRepositories(object document)
{
var list = new List();
if (document is IList