diff --git a/BittorrentDriver/BittorrentDriver.csproj b/BittorrentDriver/BittorrentDriver.csproj
new file mode 100644
index 00000000..f0fc5a2c
--- /dev/null
+++ b/BittorrentDriver/BittorrentDriver.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net8.0
+ enable
+ enable
+ 4d58719c-20df-4407-bfb4-0f65a324a118
+ Linux
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BittorrentDriver/BittorrentDriver.csproj.user b/BittorrentDriver/BittorrentDriver.csproj.user
new file mode 100644
index 00000000..dd2d54cf
--- /dev/null
+++ b/BittorrentDriver/BittorrentDriver.csproj.user
@@ -0,0 +1,6 @@
+
+
+
+ Container (Dockerfile)
+
+
\ No newline at end of file
diff --git a/BittorrentDriver/BittorrentDriver.http b/BittorrentDriver/BittorrentDriver.http
new file mode 100644
index 00000000..30d7a037
--- /dev/null
+++ b/BittorrentDriver/BittorrentDriver.http
@@ -0,0 +1,6 @@
+@BittorrentDriver_HostAddress = http://localhost:5160
+
+GET {{BittorrentDriver_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/BittorrentDriver/Controllers/TorrentController.cs b/BittorrentDriver/Controllers/TorrentController.cs
new file mode 100644
index 00000000..15f847fe
--- /dev/null
+++ b/BittorrentDriver/Controllers/TorrentController.cs
@@ -0,0 +1,50 @@
+using Logging;
+using Microsoft.AspNetCore.Mvc;
+using System.Diagnostics;
+
+namespace BittorrentDriver.Controllers
+{
+ [ApiController]
+ [Route("[controller]")]
+ public class TorrentController : ControllerBase
+ {
+ private readonly ILog log = new ConsoleLog();
+ private readonly TorrentTracker tracker = new TorrentTracker();
+ private readonly Transmission transmission;
+
+ public TorrentController()
+ {
+ transmission = new Transmission(log);
+ }
+
+ [HttpPut("tracker")]
+ public string StartTracker([FromBody] int port)
+ {
+ Log("Starting tracker...");
+ return tracker.Start(port);
+ }
+
+ [HttpPost("create")]
+ public string CreateTorrent([FromBody] CreateTorrentInput input)
+ {
+ return transmission.CreateNew(input.Size, input.TrackerUrl);
+ }
+
+ [HttpPost("download")]
+ public string DownloadTorrent([FromBody] string torrentBase64)
+ {
+ return transmission.Download(torrentBase64);
+ }
+
+ private void Log(string v)
+ {
+ log.Log(v);
+ }
+ }
+
+ public class CreateTorrentInput
+ {
+ public int Size { get; set; }
+ public string TrackerUrl { get; set; } = string.Empty;
+ }
+}
diff --git a/BittorrentDriver/Dockerfile b/BittorrentDriver/Dockerfile
new file mode 100644
index 00000000..225aa141
--- /dev/null
+++ b/BittorrentDriver/Dockerfile
@@ -0,0 +1,36 @@
+#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 ["BittorrentDriver/BittorrentDriver.csproj", "BittorrentDriver/"]
+RUN dotnet restore "./BittorrentDriver/BittorrentDriver.csproj"
+COPY . .
+WORKDIR "/src/BittorrentDriver"
+RUN dotnet build "./BittorrentDriver.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./BittorrentDriver.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+ARG DEBIAN_FRONTEND=noninteractive
+
+USER root
+# Set up npm and bittorrent-tracker
+RUN apt-get update
+RUN apt-get install npm software-properties-common -y
+RUN npm install -g bittorrent-tracker
+# Set up transmission
+RUN apt-get install transmission-cli transmission-common transmission-daemon -y
+
+USER app
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "BittorrentDriver.dll"]
\ No newline at end of file
diff --git a/BittorrentDriver/Program.cs b/BittorrentDriver/Program.cs
new file mode 100644
index 00000000..8ace5eed
--- /dev/null
+++ b/BittorrentDriver/Program.cs
@@ -0,0 +1,41 @@
+
+namespace BittorrentDriver
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ var listenPort = Environment.GetEnvironmentVariable("APIPORT");
+ if (string.IsNullOrEmpty(listenPort)) listenPort = "31100";
+
+ builder.WebHost.ConfigureKestrel((context, options) =>
+ {
+ options.ListenAnyIP(Convert.ToInt32(listenPort));
+ });
+
+ 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();
+
+ Console.WriteLine("TorrentController BittorrentDriver listening on port " + listenPort);
+
+ app.Run();
+ }
+ }
+}
diff --git a/BittorrentDriver/Properties/launchSettings.json b/BittorrentDriver/Properties/launchSettings.json
new file mode 100644
index 00000000..f13ded31
--- /dev/null
+++ b/BittorrentDriver/Properties/launchSettings.json
@@ -0,0 +1,52 @@
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5160"
+ },
+ "https": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "https://localhost:7134;http://localhost:5160"
+ },
+ "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:32045",
+ "sslPort": 44353
+ }
+ }
+}
\ No newline at end of file
diff --git a/BittorrentDriver/README.md b/BittorrentDriver/README.md
new file mode 100644
index 00000000..748836e4
--- /dev/null
+++ b/BittorrentDriver/README.md
@@ -0,0 +1,4 @@
+# Run from Tools folder:
+
+docker build -t thatbenbierens/bittorrentdriver:init -f .\BittorrentDriver\Dockerfile .
+docker push thatbenbierens/bittorrentdriver:init
diff --git a/BittorrentDriver/TorrentTracker.cs b/BittorrentDriver/TorrentTracker.cs
new file mode 100644
index 00000000..b66a1b73
--- /dev/null
+++ b/BittorrentDriver/TorrentTracker.cs
@@ -0,0 +1,36 @@
+using System.Diagnostics;
+
+namespace BittorrentDriver
+{
+ public class TorrentTracker
+ {
+ private Process? process;
+
+ public string Start(int port)
+ {
+ if (process != null) throw new Exception("Already started");
+ var info = new ProcessStartInfo
+ {
+ FileName = "bittorrent-tracker",
+ Arguments = $"--port {port} &",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ };
+
+ process = Process.Start(info);
+ if (process == null) return "Failed to start";
+
+ Thread.Sleep(1000);
+
+ if (process.HasExited)
+ {
+ return
+ $"STDOUT: {process.StandardOutput.ReadToEnd()} " +
+ $"STDERR: {process.StandardError.ReadToEnd()}";
+ }
+ return "OK";
+ }
+
+ }
+}
diff --git a/BittorrentDriver/Transmission.cs b/BittorrentDriver/Transmission.cs
new file mode 100644
index 00000000..e6bcb565
--- /dev/null
+++ b/BittorrentDriver/Transmission.cs
@@ -0,0 +1,97 @@
+using FileUtils;
+using Logging;
+using System.Buffers.Text;
+using System.Diagnostics;
+using System.Text;
+using Utils;
+
+namespace BittorrentDriver
+{
+ public class Transmission
+ {
+ private readonly string dataDir;
+ private readonly ILog log;
+
+ public Transmission(ILog log)
+ {
+ dataDir = Path.Combine(Directory.GetCurrentDirectory(), "files");
+ Directory.CreateDirectory(dataDir);
+ this.log = log;
+ }
+
+ public string CreateNew(int size, string trackerUrl)
+ {
+ var file = CreateFile(size);
+
+ var outFile = Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString());
+
+ var base64 = CreateTorrentFile(file, outFile, trackerUrl);
+
+ if (File.Exists(outFile)) File.Delete(outFile);
+
+ return base64;
+ }
+
+ public string Download(string torrentBase64)
+ {
+ var torrentFile = Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString() + ".torrent");
+ File.WriteAllBytes(torrentFile, Convert.FromBase64String(torrentBase64));
+
+ var info = new ProcessStartInfo
+ {
+ FileName = "transmission-cli",
+ Arguments = torrentFile
+ };
+ RunToComplete(info);
+
+ return "OK";
+ }
+
+ private string CreateTorrentFile(TrackedFile file, string outFile, string trackerUrl)
+ {
+ try
+ {
+ var info = new ProcessStartInfo
+ {
+ FileName = "transmission-create",
+ Arguments = $"-o {outFile} -t {trackerUrl} {file.Filename}",
+ };
+
+ var process = RunToComplete(info);
+
+ log.Log(nameof(CreateTorrentFile) + " exited with: " + process.ExitCode);
+
+ if (!File.Exists(outFile)) throw new Exception("Outfile not created.");
+
+ return Convert.ToBase64String(File.ReadAllBytes(outFile));
+ }
+ catch (Exception ex)
+ {
+ log.Error("Failed to create torrent file: " + ex);
+ throw;
+ }
+ }
+
+ private Process RunToComplete(ProcessStartInfo info)
+ {
+ var process = Process.Start(info);
+ if (process == null) throw new Exception("Failed to start");
+ process.WaitForExit(TimeSpan.FromMinutes(3));
+ return process;
+ }
+
+ private TrackedFile CreateFile(int size)
+ {
+ try
+ {
+ var fileManager = new FileManager(log, dataDir);
+ return fileManager.GenerateFile(size.Bytes());
+ }
+ catch (Exception ex)
+ {
+ log.Error("Failed to create file: " + ex);
+ throw;
+ }
+ }
+ }
+}
diff --git a/BittorrentDriver/appsettings.Development.json b/BittorrentDriver/appsettings.Development.json
new file mode 100644
index 00000000..0c208ae9
--- /dev/null
+++ b/BittorrentDriver/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/BittorrentDriver/appsettings.json b/BittorrentDriver/appsettings.json
new file mode 100644
index 00000000..10f68b8c
--- /dev/null
+++ b/BittorrentDriver/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/BittorrentPlugin/BittorrentPlugin.cs b/BittorrentPlugin/BittorrentPlugin.cs
new file mode 100644
index 00000000..15d3c040
--- /dev/null
+++ b/BittorrentPlugin/BittorrentPlugin.cs
@@ -0,0 +1,79 @@
+using Core;
+using KubernetesWorkflow;
+using KubernetesWorkflow.Recipe;
+
+namespace BittorrentPlugin
+{
+ public class BittorrentPlugin : IProjectPlugin
+ {
+ private readonly IPluginTools tools;
+
+ public BittorrentPlugin(IPluginTools tools)
+ {
+ this.tools = tools;
+ }
+
+ public void Announce()
+ {
+ tools.GetLog().Log("Loaded Bittorrent plugin");
+ }
+
+ public void Decommission()
+ {
+ }
+
+ public void Run()
+ {
+ var flow = tools.CreateWorkflow();
+ var trackerPod = flow.Start(1, new TrackerContainerRecipe(), new StartupConfig()).WaitForOnline();
+ var trackerContainer = trackerPod.Containers.Single();
+
+ //var msg = flow.ExecuteCommand(trackerContainer, "apt-get", "update");
+ //msg = flow.ExecuteCommand(trackerContainer, "apt-get", "install", "npm", "-y");
+ //var msg = flow.ExecuteCommand(trackerContainer, "npm", "install", "-g", "bittorrent-tracker");
+ //msg = flow.ExecuteCommand(trackerContainer, "bittorrent-tracker", "--port", "30800", "&");
+
+ var clientPod = flow.Start(1, new BittorrentContainerRecipe(), new StartupConfig()).WaitForOnline();
+ var clientContainer = clientPod.Containers.Single();
+
+ var msg = flow.ExecuteCommand(clientContainer, "echo", "1234567890987654321",
+ ">", "/root/datafile.txt");
+
+ var trackerAddress = trackerContainer.GetAddress(tools.GetLog(), TrackerContainerRecipe.HttpPort);
+ if (trackerAddress == null) throw new Exception();
+ var trackerAddressStr = trackerAddress.ToString();
+
+ msg = flow.ExecuteCommand(clientContainer, "transmission-create",
+ "-o", "/root/outfile.torrent",
+ "-t", trackerAddressStr,
+ "/root/datafile.txt");
+
+ msg = flow.ExecuteCommand(clientContainer, "cat", "/root/outfile.torrent");
+
+ var a = 0;
+ }
+ }
+
+ public class TrackerContainerRecipe : ContainerRecipeFactory
+ {
+ public override string AppName => "bittorrenttracker";
+ public override string Image => "thatbenbierens/bittorrent-tracker:init";
+
+ public static string HttpPort = "http";
+
+ protected override void Initialize(StartupConfig config)
+ {
+ AddExposedPort(30800, HttpPort);
+ }
+ }
+
+ public class BittorrentContainerRecipe : ContainerRecipeFactory
+ {
+ public override string AppName => "bittorrentclient";
+ public override string Image => "thatbenbierens/bittorrent-client:init";
+
+ protected override void Initialize(StartupConfig config)
+ {
+ }
+ }
+}
diff --git a/BittorrentPlugin/BittorrentPlugin.csproj b/BittorrentPlugin/BittorrentPlugin.csproj
new file mode 100644
index 00000000..f8d505f9
--- /dev/null
+++ b/BittorrentPlugin/BittorrentPlugin.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/BittorrentPlugin/CoreInterfaceExtensions.cs b/BittorrentPlugin/CoreInterfaceExtensions.cs
new file mode 100644
index 00000000..c73dd77b
--- /dev/null
+++ b/BittorrentPlugin/CoreInterfaceExtensions.cs
@@ -0,0 +1,17 @@
+using Core;
+
+namespace BittorrentPlugin
+{
+ public static class CoreInterfaceExtensions
+ {
+ public static void RunThing(this CoreInterface ci)
+ {
+ Plugin(ci).Run();
+ }
+
+ private static BittorrentPlugin Plugin(CoreInterface ci)
+ {
+ return ci.GetPlugin();
+ }
+ }
+}
diff --git a/Tests/CodexTests/BasicTests/ExampleTests.cs b/Tests/CodexTests/BasicTests/ExampleTests.cs
index f9d5b89e..959a66eb 100644
--- a/Tests/CodexTests/BasicTests/ExampleTests.cs
+++ b/Tests/CodexTests/BasicTests/ExampleTests.cs
@@ -2,6 +2,7 @@
using DistTestCore;
using GethPlugin;
using MetricsPlugin;
+using BittorrentPlugin;
using NUnit.Framework;
using Utils;
@@ -66,5 +67,11 @@ namespace CodexTests.BasicTests
Assert.That(bootN, Is.EqualTo(followN));
Assert.That(discN, Is.LessThan(bootN));
}
+
+ [Test]
+ public void BittorrentPluginTest()
+ {
+ Ci.RunThing();
+ }
}
}
diff --git a/Tests/CodexTests/CodexTests.csproj b/Tests/CodexTests/CodexTests.csproj
index 4a9a3edb..ceda9c0e 100644
--- a/Tests/CodexTests/CodexTests.csproj
+++ b/Tests/CodexTests/CodexTests.csproj
@@ -13,6 +13,7 @@
+
diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln
index 6041a7c1..8dc5e8c4 100644
--- a/cs-codex-dist-testing.sln
+++ b/cs-codex-dist-testing.sln
@@ -76,6 +76,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranscriptAnalysis", "Tools
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarketInsights", "Tools\MarketInsights\MarketInsights.csproj", "{004614DF-1C65-45E3-882D-59AE44282573}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BittorrentPlugin", "BittorrentPlugin\BittorrentPlugin.csproj", "{0E2C6152-951D-433A-A150-96CFBCD1472A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BittorrentDriver", "BittorrentDriver\BittorrentDriver.csproj", "{59B53781-9E5E-4A38-89F3-52996556B593}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -202,6 +206,14 @@ 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
+ {0E2C6152-951D-433A-A150-96CFBCD1472A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E2C6152-951D-433A-A150-96CFBCD1472A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E2C6152-951D-433A-A150-96CFBCD1472A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E2C6152-951D-433A-A150-96CFBCD1472A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {59B53781-9E5E-4A38-89F3-52996556B593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {59B53781-9E5E-4A38-89F3-52996556B593}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {59B53781-9E5E-4A38-89F3-52996556B593}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {59B53781-9E5E-4A38-89F3-52996556B593}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -237,6 +249,8 @@ 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}
+ {0E2C6152-951D-433A-A150-96CFBCD1472A} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124}
+ {59B53781-9E5E-4A38-89F3-52996556B593} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C}