Skip to content

Commit

Permalink
Serilog + Make more shit thread safe + unfuck search
Browse files Browse the repository at this point in the history
  • Loading branch information
Simyon264 committed Feb 24, 2024
1 parent c5de082 commit 1adefcd
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 88 deletions.
5 changes: 2 additions & 3 deletions Server/Api/ReplayController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ [FromQuery] string query
.OrderByDescending(r => r.Date ?? DateTime.MinValue)
.ToListAsync();

replays.Reverse();
var found = ReplayParser.SearchReplays(searchMode, query, replays);
return Ok(found);
}
Expand All @@ -104,15 +103,15 @@ public async Task<ActionResult> GetAllReplays()
}

/// <summary>
/// Returns the 30 most recent replays.
/// Returns the most recent replays.
/// </summary>
[HttpGet]
[Route("/replays/most-recent")]
public async Task<ActionResult> GetMostRecentReplay()
{
var replays = await _context.Replays
.OrderByDescending(r => r.Date ?? DateTime.MinValue)
.Take(30)
.Take(32)
.ToListAsync();
return Ok(replays);
}
Expand Down
10 changes: 10 additions & 0 deletions Server/Api/ReplayDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
/// leviathan-2024_02_18-08_33-round_46751.zip
/// </summary>
public DbSet<ParsedReplay> ParsedReplays { get; set; }
}

/// <summary>
/// Just a copy of ReplayDbContext, but with a different name. Used to be thread safe.
/// </summary>
public class ReplayParserDbContext : ReplayDbContext
{
public ReplayParserDbContext(DbContextOptions<ReplayDbContext> options) : base(options)
{
}
}
162 changes: 92 additions & 70 deletions Server/Program.cs
Original file line number Diff line number Diff line change
@@ -1,85 +1,107 @@
using System.Net;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.EntityFrameworkCore;
using Serilog;
using Server;
using Server.Api;

var builder = WebApplication.CreateBuilder(args);
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day, fileSizeLimitBytes: null)
.CreateLogger();

builder.WebHost.UseKestrel();

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Load in appsettings.json, and appsettings.Development.json if the environment is development.
// Additionally, load in appsettings.Secret.json if it exists.
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("appsettings.Secret.json", optional: true, reloadOnChange: true);

builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<ReplayDbContext>(options =>
try
{
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
});

ReplayParser.Context = builder.Services.BuildServiceProvider().GetService<ReplayDbContext>();

builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
var builder = WebApplication.CreateBuilder(args);

builder.Host.UseSerilog();

builder.WebHost.UseKestrel();

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Load in appsettings.json, and appsettings.Development.json if the environment is development.
// Additionally, load in appsettings.Secret.json if it exists.
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("appsettings.Secret.json", optional: true, reloadOnChange: true);

builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<ReplayDbContext>(options =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
});
});

builder.Services.AddMvc();
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "api/{controller=Home}/{action=Index}/{id?}");

// Run FetchReplays in a new thread.
var tokens = new List<CancellationTokenSource>();
var URLs = builder.Configuration.GetSection("ReplayUrls").Get<string[]>();
if (URLs == null)
{
throw new Exception("No replay URLs found in appsettings.json. Please set ReplayUrls to an array of URLs.");
}
foreach (var url in URLs)
{

builder.Services.AddDbContext<ReplayParserDbContext>(options =>
{
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
});

ReplayParser.Context = builder.Services.BuildServiceProvider().GetService<ReplayParserDbContext>(); // GOD THIS IS STUPID

Check warning on line 43 in Server/Program.cs

View workflow job for this annotation

GitHub Actions / deploy

Possible null reference assignment.

Check warning on line 43 in Server/Program.cs

View workflow job for this annotation

GitHub Actions / deploy

Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'. (https://aka.ms/AA5k895)

Check warning on line 43 in Server/Program.cs

View workflow job for this annotation

GitHub Actions / deploy

Possible null reference assignment.

builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});

builder.Services.AddMvc();
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "api/{controller=Home}/{action=Index}/{id?}");

// Run FetchReplays in a new thread.
var tokens = new List<CancellationTokenSource>();
var URLs = builder.Configuration.GetSection("ReplayUrls").Get<string[]>();
if (URLs == null)
{
throw new Exception("No replay URLs found in appsettings.json. Please set ReplayUrls to an array of URLs.");
}

var tokenSource = new CancellationTokenSource();
tokens.Add(tokenSource);
var thread = new Thread(() => ReplayParser.FetchReplays(tokenSource.Token, url));
var thread = new Thread(() => ReplayParser.FetchReplays(tokenSource.Token, URLs));

Check warning on line 82 in Server/Program.cs

View workflow job for this annotation

GitHub Actions / deploy

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 82 in Server/Program.cs

View workflow job for this annotation

GitHub Actions / deploy

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
thread.Start();

var token = new CancellationTokenSource();
tokens.Add(token);
var thread2 = new Thread(() => ReplayParser.ConsumeQueue(token.Token));

Check warning on line 87 in Server/Program.cs

View workflow job for this annotation

GitHub Actions / deploy

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 87 in Server/Program.cs

View workflow job for this annotation

GitHub Actions / deploy

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
thread2.Start();

app.Lifetime.ApplicationStopping.Register(() =>
{
foreach (var token in tokens)
{
token.Cancel();
}
});

app.Run();
}

var token = new CancellationTokenSource();
tokens.Add(token);
var thread2 = new Thread(() => ReplayParser.ConsumeQueue(token.Token));
thread2.Start();

app.Lifetime.ApplicationStopping.Register(() =>
catch (Exception e)
{
foreach (var token in tokens)
{
token.Cancel();
}
});

app.Run();
Log.Fatal(e, "An error occurred while starting the server.");
}
finally
{
Log.CloseAndFlush();
}
35 changes: 20 additions & 15 deletions Server/ReplayParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Text.RegularExpressions;
using HtmlAgilityPack;
using Microsoft.EntityFrameworkCore;
using Serilog;
using Server.Api;
using Shared;
using Shared.Models;
Expand Down Expand Up @@ -75,7 +76,7 @@ public static async Task ConsumeQueue(CancellationToken token)
try
{
var client = new HttpClient();
Console.WriteLine("Downloading " + replay);
Log.Information("Downloading " + replay);
var fileStream = await client.GetStreamAsync(replay, token);
Replay? parsedReplay = null;
try
Expand Down Expand Up @@ -107,11 +108,11 @@ public static async Task ConsumeQueue(CancellationToken token)
await AddReplayToDb(parsedReplay);
await AddParsedReplayToDb(replay);
Console.WriteLine("Parsed " + replay);
Log.Information("Parsed " + replay);
}
catch (Exception e)
{
Console.WriteLine(e);
Log.Error(e, "Error while parsing " + replay);
}
}, token));

Expand All @@ -127,23 +128,27 @@ public static async Task ConsumeQueue(CancellationToken token)
/// <summary>
/// Handles fetching replays from the remote storage.
/// </summary>
public static async Task FetchReplays(CancellationToken token, string storageUrl)
public static async Task FetchReplays(CancellationToken token, string[] storageUrls)
{
while (!token.IsCancellationRequested)
{
Console.WriteLine("Fetching replays...");
try
foreach (var storageUrl in storageUrls)
{
await RetrieveFilesRecursive(storageUrl, token);
}
catch (Exception e)
{
Console.WriteLine(e);
Log.Information("Fetching replays from " + storageUrl);
try
{
await RetrieveFilesRecursive(storageUrl, token);
}
catch (Exception e)
{
Log.Error(e, "Error while fetching replays from " + storageUrl);
}
}

var now = DateTime.Now;
var nextRun = now.AddMinutes(10 - now.Minute % 10).AddSeconds(-now.Second);
var delay = nextRun - now;
Log.Information("Next run in " + delay.TotalMinutes + " minutes.");
await Task.Delay(delay, token);
}
}
Expand All @@ -152,7 +157,7 @@ private static async Task RetrieveFilesRecursive(string directoryUrl, Cancellati
{
try
{
Console.WriteLine("Retrieving files from " + directoryUrl);
Log.Information("Retrieving files from " + directoryUrl);
var client = new HttpClient();
var htmlContent = await client.GetStringAsync(directoryUrl, token);
var document = new HtmlDocument();
Expand All @@ -161,7 +166,7 @@ private static async Task RetrieveFilesRecursive(string directoryUrl, Cancellati
var links = document.DocumentNode.SelectNodes("//a[@href]");
if (links == null)
{
Console.WriteLine("No links found on " + directoryUrl);
Log.Information("No links found on " + directoryUrl + ".");
return;
}

Expand Down Expand Up @@ -207,15 +212,15 @@ private static async Task RetrieveFilesRecursive(string directoryUrl, Cancellati
{
continue;
}
Console.WriteLine("Adding " + href + " to the queue.");
Log.Information("Adding " + href + " to the queue.");
Queue.Add(href);
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
Log.Error(e, "Error while retrieving files from " + directoryUrl);
// We don't care about the exception, we just want to return the files we have.
}
}
Expand Down
4 changes: 4 additions & 0 deletions Server/Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
<PackageReference Include="YamlDotNet" Version="15.1.1" />
</ItemGroup>
Expand Down

0 comments on commit 1adefcd

Please sign in to comment.