diff --git a/AutoClientCenter/AutoClientCenter.csproj b/AutoClientCenter/AutoClientCenter.csproj
new file mode 100644
index 0000000..d5e2255
--- /dev/null
+++ b/AutoClientCenter/AutoClientCenter.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net7.0
+ enable
+ enable
+ 80f55fc5-9f8f-4bb4-89bc-314ed7379c5f
+ Linux
+
+
+
+
+
+
+
+
diff --git a/AutoClientCenter/AutoClientCenter.csproj.user b/AutoClientCenter/AutoClientCenter.csproj.user
new file mode 100644
index 0000000..dd2d54c
--- /dev/null
+++ b/AutoClientCenter/AutoClientCenter.csproj.user
@@ -0,0 +1,6 @@
+
+
+
+ Container (Dockerfile)
+
+
\ No newline at end of file
diff --git a/AutoClientCenter/AutoClientCenter.http b/AutoClientCenter/AutoClientCenter.http
new file mode 100644
index 0000000..251d650
--- /dev/null
+++ b/AutoClientCenter/AutoClientCenter.http
@@ -0,0 +1,6 @@
+@AutoClientCenter_HostAddress = http://localhost:5185
+
+GET {{AutoClientCenter_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/AutoClientCenter/Controllers/ConfigController.cs b/AutoClientCenter/Controllers/ConfigController.cs
new file mode 100644
index 0000000..e259ae5
--- /dev/null
+++ b/AutoClientCenter/Controllers/ConfigController.cs
@@ -0,0 +1,28 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutoClientCenter.Controllers
+{
+ [ApiController]
+ [Route("[controller]")]
+ public class ConfigController : ControllerBase
+ {
+ private readonly ITaskService taskService;
+
+ public ConfigController(ITaskService taskService)
+ {
+ this.taskService = taskService;
+ }
+
+ [HttpGet("Stats")]
+ public AcStats Get()
+ {
+ return taskService.GetStats();
+ }
+
+ [HttpPost("Set")]
+ public void Post([FromBody] AcTasks tasks)
+ {
+ taskService.SetConfig(tasks);
+ }
+ }
+}
diff --git a/AutoClientCenter/Controllers/TasksController.cs b/AutoClientCenter/Controllers/TasksController.cs
new file mode 100644
index 0000000..9b5e184
--- /dev/null
+++ b/AutoClientCenter/Controllers/TasksController.cs
@@ -0,0 +1,35 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutoClientCenter.Controllers
+{
+ [ApiController]
+ [Route("[controller]")]
+ public class TasksController : ControllerBase
+ {
+ private readonly ITaskService taskService;
+ private static readonly object processLock = new object();
+
+ public TasksController(ITaskService taskService)
+ {
+ this.taskService = taskService;
+ }
+
+ [HttpGet]
+ public AcTasks Get()
+ {
+ return taskService.GetTasks();
+ }
+
+ [HttpPost("Results")]
+ public void Post([FromBody] AcTaskStep[] taskSteps)
+ {
+ Task.Run(() =>
+ {
+ lock (processLock)
+ {
+ taskService.ProcessResults(taskSteps);
+ }
+ });
+ }
+ }
+}
diff --git a/AutoClientCenter/Dockerfile b/AutoClientCenter/Dockerfile
new file mode 100644
index 0000000..4f8996e
--- /dev/null
+++ b/AutoClientCenter/Dockerfile
@@ -0,0 +1,25 @@
+#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+USER app
+WORKDIR /app
+EXPOSE 8080
+EXPOSE 8081
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["AutoClientCenter/AutoClientCenter.csproj", "AutoClientCenter/"]
+RUN dotnet restore "./AutoClientCenter/AutoClientCenter.csproj"
+COPY . .
+WORKDIR "/src/AutoClientCenter"
+RUN dotnet build "./AutoClientCenter.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./AutoClientCenter.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "AutoClientCenter.dll"]
\ No newline at end of file
diff --git a/AutoClientCenter/Model.cs b/AutoClientCenter/Model.cs
new file mode 100644
index 0000000..e37ef61
--- /dev/null
+++ b/AutoClientCenter/Model.cs
@@ -0,0 +1,63 @@
+namespace AutoClientCenter
+{
+ public class AcTasks
+ {
+ public TimeSpan StartTaskEvery { get; set; } = TimeSpan.FromHours(6);
+ public AcTask[] Tasks { get; set; } = Array.Empty();
+ }
+
+ public class AcTask
+ {
+ public int ChanceWeight { get; set; }
+ public AcTaskStep[] Steps { get; set; } = Array.Empty();
+ }
+
+ public class AcTaskStep
+ {
+ public string Id { get; set; } = string.Empty;
+ public AcUploadStep? UploadStep { get; set; }
+ public AcStoreStep? StoreStep { get; set; }
+ public AcDownloadStep? DownloadStep { get; set; }
+ public string? ResultErrorMsg { get; set; }
+ }
+
+ public class AcUploadStep
+ {
+ public long SizeInBytes { get; set; }
+ public string? ResultCid { get; set; }
+ }
+
+ public class AcStoreStep
+ {
+ public int ContractDurationMinutes { get; set; }
+ public int ContractExpiryMinutes { get; set; }
+ public int NumHosts { get; set; }
+ public int HostTolerance { get; set; }
+ public int Price { get; set; }
+ public int RequiredCollateral { get; set; }
+ public string? ResultPurchaseId { get; set; }
+ public string? ResultCid { get; set; }
+ }
+
+ public class AcDownloadStep
+ {
+ public string[] Cids { get; set; } = Array.Empty();
+ public long[] ResultDownloadTimeSeconds { get; set; } = Array.Empty();
+ }
+
+ public class AcStats
+ {
+ public int NumberOfAutoClients { get; set; } todo send client peerId
+
+ public DateTime ServiceStartUtc { get; set; } = DateTime.MinValue;
+ public int TotalUploads { get; set; }
+ public int TotalUploadsFailed { get; set; }
+ public int TotalDownloads { get; set; }
+ public long[] DownloadTimesSeconds { get; set; } = Array.Empty();
+ public int TotalDownloadsFailed { get; set; }
+ public int TotalContractsStarted { get; set; }
+ public int TotalContractsCompleted { get; set; }
+ public int TotalContractsExpired { get; set; }
+ public int TotalContractsFailed { get; set; }
+ }
+}
diff --git a/AutoClientCenter/Program.cs b/AutoClientCenter/Program.cs
new file mode 100644
index 0000000..c2aeec7
--- /dev/null
+++ b/AutoClientCenter/Program.cs
@@ -0,0 +1,33 @@
+
+namespace AutoClientCenter
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ builder.Services.AddSingleton(new TaskService());
+ builder.Services.AddControllers();
+ builder.Services.AddEndpointsApiExplorer();
+ builder.Services.AddSwaggerGen();
+
+ var app = builder.Build();
+
+ if (app.Environment.IsDevelopment())
+ {
+ app.UseSwagger();
+ app.UseSwaggerUI();
+ }
+
+ app.UseHttpsRedirection();
+
+ app.UseAuthorization();
+
+
+ app.MapControllers();
+
+ app.Run();
+ }
+ }
+}
diff --git a/AutoClientCenter/Properties/launchSettings.json b/AutoClientCenter/Properties/launchSettings.json
new file mode 100644
index 0000000..4f58636
--- /dev/null
+++ b/AutoClientCenter/Properties/launchSettings.json
@@ -0,0 +1,52 @@
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5185"
+ },
+ "https": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "https://localhost:7077;http://localhost:5185"
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Container (Dockerfile)": {
+ "commandName": "Docker",
+ "launchBrowser": true,
+ "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
+ "environmentVariables": {
+ "ASPNETCORE_HTTPS_PORTS": "8081",
+ "ASPNETCORE_HTTP_PORTS": "8080"
+ },
+ "publishAllPorts": true,
+ "useSSL": true
+ }
+ },
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:50475",
+ "sslPort": 44395
+ }
+ }
+}
\ No newline at end of file
diff --git a/AutoClientCenter/TaskService.cs b/AutoClientCenter/TaskService.cs
new file mode 100644
index 0000000..59aa490
--- /dev/null
+++ b/AutoClientCenter/TaskService.cs
@@ -0,0 +1,56 @@
+namespace AutoClientCenter
+{
+ public interface ITaskService
+ {
+ AcStats GetStats();
+ AcTasks GetTasks();
+ void ProcessResults(AcTaskStep[] taskSteps);
+ void SetConfig(AcTasks tasks);
+ }
+
+ public class TaskService : ITaskService
+ {
+ private readonly List downloadTimes = new List();
+ private readonly AcStats stats = new AcStats
+ {
+ ServiceStartUtc = DateTime.UtcNow,
+ };
+
+ private AcTasks tasks = new AcTasks
+ {
+ StartTaskEvery = TimeSpan.FromHours(8),
+ Tasks = Array.Empty()
+ };
+
+ public AcStats GetStats()
+ {
+ return stats;
+ }
+
+ public AcTasks GetTasks()
+ {
+ return tasks;
+ }
+
+ public void SetConfig(AcTasks newTasks)
+ {
+ if (newTasks.StartTaskEvery < TimeSpan.FromMinutes(10)) return;
+ foreach (var task in newTasks.Tasks)
+ {
+ if (task.ChanceWeight < 1) return;
+ foreach (var step in task.Steps)
+ {
+ if (string.IsNullOrWhiteSpace(step.Id)) return;
+ if (step.UploadStep == null && step.StoreStep == null && step.DownloadStep == null) return;
+ }
+ }
+
+ tasks = newTasks;
+ }
+
+ public void ProcessResults(AcTaskStep[] taskSteps)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/AutoClientCenter/appsettings.Development.json b/AutoClientCenter/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/AutoClientCenter/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/AutoClientCenter/appsettings.json b/AutoClientCenter/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/AutoClientCenter/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs
index 5e84883..3ba8ee5 100644
--- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs
+++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs
@@ -7,7 +7,7 @@ namespace CodexPlugin
{
public class CodexContainerRecipe : ContainerRecipeFactory
{
- private const string DefaultDockerImage = "codexstorage/nim-codex:sha-64b82de-dist-tests";
+ private const string DefaultDockerImage = "codexstorage/nim-codex:sha-1e2ad95-dist-tests";
public const string ApiPortTag = "codex_api_port";
public const string ListenPortTag = "codex_listen_port";
diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln
index 89a0f79..adbea6d 100644
--- a/cs-codex-dist-testing.sln
+++ b/cs-codex-dist-testing.sln
@@ -74,7 +74,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OverwatchTranscript", "Fram
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranscriptAnalysis", "Tools\TranscriptAnalysis\TranscriptAnalysis.csproj", "{C0EEBD32-23CB-45EC-A863-79FB948508C8}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarketInsights", "Tools\MarketInsights\MarketInsights.csproj", "{004614DF-1C65-45E3-882D-59AE44282573}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarketInsights", "Tools\MarketInsights\MarketInsights.csproj", "{004614DF-1C65-45E3-882D-59AE44282573}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoClientCenter", "AutoClientCenter\AutoClientCenter.csproj", "{023B6C60-406B-47A1-9ED8-12688759D2CC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -202,6 +204,10 @@ Global
{004614DF-1C65-45E3-882D-59AE44282573}.Debug|Any CPU.Build.0 = Debug|Any CPU
{004614DF-1C65-45E3-882D-59AE44282573}.Release|Any CPU.ActiveCfg = Release|Any CPU
{004614DF-1C65-45E3-882D-59AE44282573}.Release|Any CPU.Build.0 = Release|Any CPU
+ {023B6C60-406B-47A1-9ED8-12688759D2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {023B6C60-406B-47A1-9ED8-12688759D2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {023B6C60-406B-47A1-9ED8-12688759D2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {023B6C60-406B-47A1-9ED8-12688759D2CC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -237,6 +243,7 @@ Global
{870DDFBE-D7ED-4196-9681-13CA947BDEA6} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7}
{C0EEBD32-23CB-45EC-A863-79FB948508C8} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
{004614DF-1C65-45E3-882D-59AE44282573} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
+ {023B6C60-406B-47A1-9ED8-12688759D2CC} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C}