Skip to content

Commit

Permalink
dsstats.tourneys
Browse files Browse the repository at this point in the history
  • Loading branch information
ipax77 committed Jul 28, 2024
1 parent b6bddca commit c9f4394
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 2 deletions.
8 changes: 6 additions & 2 deletions src/dsstats.decode/DecodeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,12 @@ public async Task Decode()
error = "failed decoding replays.";
continue;
}

File.Move(result.ReplayPath, Path.Combine(decodeSettings.Value.ReplayFolders.Done, Path.GetFileName(result.ReplayPath)));
var destination = Path.Combine(decodeSettings.Value.ReplayFolders.Done,
Path.GetFileNameWithoutExtension(result.ReplayPath)[..36] +
"_" +
replayDto.ReplayHash +
Path.GetExtension(result.ReplayPath));
File.Move(result.ReplayPath, destination);
var groupId = GetGroupIdFromFilename(result.ReplayPath);
var ihReplay = new IhReplay() { Replay = replayDto, Metadata = metaData };
replays.AddOrUpdate(groupId, [ihReplay], (k, v) => { v.Add(ihReplay); return v; });
Expand Down
213 changes: 213 additions & 0 deletions src/dsstats.tourneys/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
using System.Security.Cryptography;
using System.Text.Json;
using AutoMapper;
using dsstats.db8;
using dsstats.db8.AutoMapper;
using dsstats.db8services;
using dsstats.shared;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace dsstats.tourneys;

class Program
{
public static readonly string tourneysDir = "/data/ds/Tourneys";
public static readonly string decodeDir = "/data/ds/decode/done";

static void Main(string[] args)
{
var services = new ServiceCollection();

var jsonStrg = File.ReadAllText("/data/localserverconfig.json");
var json = JsonSerializer.Deserialize<JsonElement>(jsonStrg);
var config = json.GetProperty("ServerConfig");
var importConnectionString = config.GetProperty("ImportConnectionString").GetString() ?? "";
var mySqlConnectionString = config.GetProperty("DsstatsConnectionString").GetString();
var decodeUrl = config.GetProperty("DecodeUrl").GetString() ?? "";

services.AddOptions<DbImportOptions>()
.Configure(x =>
{
x.ImportConnectionString = importConnectionString;
x.IsSqlite = false;
});

services.AddLogging(options =>
{
options.SetMinimumLevel(LogLevel.Information);
options.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Warning);
options.AddConsole();
});

services.AddDbContext<ReplayContext>(options =>
options.UseMySql(mySqlConnectionString, ServerVersion.AutoDetect(mySqlConnectionString), p =>
{
p.CommandTimeout(600);
p.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery);
})
);

services.AddAutoMapper(typeof(AutoMapperProfile));
services.AddHttpClient("decode")
.ConfigureHttpClient(options =>
{
options.BaseAddress = new Uri(decodeUrl);
});

services.AddScoped<IReplayRepository, ReplayRepository>();

var serviceProvider = services.BuildServiceProvider();

if (args.Length == 0)
{
Console.WriteLine("Need a trouney name as parameter");
return;
}
var tourneyDir = Path.Combine(tourneysDir, args[0]);
if (!Directory.Exists(tourneyDir))
{
Console.WriteLine($"tourneyDir {tourneyDir} not found.");
return;
}

AddTourneyReplays(serviceProvider, tourneyDir).Wait();

Console.WriteLine("job done.");
}

static async Task AddTourneyReplays(ServiceProvider serviceProvider, string tourneyDir)
{
var replays = Directory.GetFiles(tourneyDir, "*.SC2Replay", SearchOption.AllDirectories)
.ToHashSet();
var existingJsons = replays.Where(x => File.Exists(Path.ChangeExtension(x, "json"))).ToList();
replays.ExceptWith(existingJsons);

if (replays.Count == 0)
{
Console.Write("not new tourney replays found.");
return;
}
Guid guid = Guid.NewGuid();
var fileHashes = await SaveReplays(serviceProvider, replays, guid);
await Task.Delay(30000);

SetReplayHashes(guid, fileHashes);
var stateChanges = await SaveTourneyInfo(serviceProvider, tourneyDir, fileHashes);
Console.WriteLine($"replay events generated: {stateChanges}");
}

static async Task<int> SaveTourneyInfo(ServiceProvider serviceProvider,
string tourneyDir,
Dictionary<string, ReplayFileInfo> replayHashes)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ReplayContext>();
var mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();

var tourney = await context.Events.FirstOrDefaultAsync(f => f.Name == Path.GetFileName(tourneyDir));

if (tourney is null)
{
logger.LogWarning("Tourney not found {dir}", tourneyDir);
return 0;
}

foreach (var replayInfo in replayHashes.Values)
{
if (string.IsNullOrEmpty(replayInfo.ReplayHash))
{
logger.LogWarning("no replay hash found: {replay}", replayInfo.TourneyPath);
continue;
}

var replay = await context.Replays.FirstOrDefaultAsync(f => f.ReplayHash == replayInfo.ReplayHash);

if (replay is null)
{
logger.LogWarning("replay not found: {hash}", replayInfo.ReplayHash);
continue;
}

// 1v1
string winnerTeam = replay.ReplayPlayers.Where(x => x.Team == replay.WinnerTeam).FirstOrDefault()?.Name ?? "";
string runnerTeam = replay.ReplayPlayers.Where(x => x.Team != replay.WinnerTeam).FirstOrDefault()?.Name ?? "";
var groupName = Path.GetFileName(Path.GetDirectoryName(replayInfo.DecodePath));
ReplayEvent replayEvent = new()
{
WinnerTeam = winnerTeam,
RunnerTeam = runnerTeam,
Round = groupName ?? "tbd",
Event = tourney
};
replay.ReplayEvent = replayEvent;
var fakeJson = JsonSerializer.Serialize(mapper.Map<ReplayDto>(replay));
File.WriteAllText(Path.ChangeExtension(replayInfo.TourneyPath, "json"), fakeJson);
}
return await context.SaveChangesAsync();
}

static async Task<Dictionary<string, ReplayFileInfo>> SaveReplays(ServiceProvider serviceProvider,
ICollection<string> replays,
Guid guid)
{
using var scope = serviceProvider.CreateScope();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
var httpClientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("decode");
Dictionary<string, ReplayFileInfo> fileHashes = [];
using var sha256 = SHA256.Create();
try
{
var formData = new MultipartFormDataContent();

foreach (var replay in replays)
{
var stream = new MemoryStream(File.ReadAllBytes(replay));
var hashBytes = sha256.ComputeHash(stream);
var hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
fileHashes[hashString] = new ReplayFileInfo() { TourneyPath = replay };
stream.Position = 0;
var fileContent = new StreamContent(stream);
formData.Add(fileContent, "files", Path.GetFileName(replay));
}

var result = await httpClient.PostAsync($"/api/v1/decode/upload/{guid}", formData);
result.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
logger.LogError("failed saving replays: {error}", ex.Message);
}
return fileHashes;
}

static void SetReplayHashes(Guid guid, Dictionary<string, ReplayFileInfo> fileHashes)
{
var replays = Directory.GetFiles(decodeDir, "*.SC2Replay", SearchOption.AllDirectories)
.Where(x => Path.GetFileName(x).StartsWith(guid.ToString()))
.ToList();
using var sha256 = SHA256.Create();
foreach (var replay in replays)
{
using var stream = new MemoryStream(File.ReadAllBytes(replay));
var hashBytes = sha256.ComputeHash(stream);
var hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
if (fileHashes.TryGetValue(hashString, out var replayFileInfo)
&& replayFileInfo is not null)
{
replayFileInfo.DecodePath = replay;
replayFileInfo.ReplayHash = Path.GetFileNameWithoutExtension(replay)[37..];
}
}
}
}

internal record ReplayFileInfo
{
public string TourneyPath { get; set; } = string.Empty;
public string DecodePath { get; set; } = string.Empty;
public string ReplayHash { get; set; } = string.Empty;
}
21 changes: 21 additions & 0 deletions src/dsstats.tourneys/dsstats.tourneys.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\dsstats.shared\dsstats.shared.csproj" />
<ProjectReference Include="..\dsstats.db8\dsstats.db8.csproj" />
<ProjectReference Include="..\dsstats.db8services\dsstats.db8services.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
</ItemGroup>

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>

0 comments on commit c9f4394

Please sign in to comment.