Skip to content

Commit 0c6dee4

Browse files
committed
Merge remote-tracking branch 'origin/master' into EpicManifestParsing
2 parents 4b36e14 + bd318f7 commit 0c6dee4

9 files changed

Lines changed: 219 additions & 174 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using DeveLanCacheUI_Backend.Db;
2+
using DeveLanCacheUI_Backend.Db.DbModels;
3+
using Microsoft.Data.Sqlite;
4+
using Microsoft.EntityFrameworkCore;
5+
6+
namespace DeveLanCacheUI_Backend.Tests.DbContextTests
7+
{
8+
[TestClass]
9+
public class DbContextDateTimeTests
10+
{
11+
[TestMethod]
12+
public async Task ConvertsDateTimeToUtcCorrectly()
13+
{
14+
// Create a new SQLite connection string for in-memory database
15+
var connectionString = "Data Source=:memory:";
16+
using var connection = new SqliteConnection(connectionString);
17+
await connection.OpenAsync();
18+
19+
var options = new DbContextOptionsBuilder<DeveLanCacheUIDbContext>()
20+
.UseSqlite(connection)
21+
.Options;
22+
23+
var dateTimeInLocalTimeZone = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Local);
24+
var expectedUtcDateTime = dateTimeInLocalTimeZone.ToUniversalTime();
25+
26+
// First context: Create database and insert data
27+
using (var context = new DeveLanCacheUIDbContext(options))
28+
{
29+
// Ensure the database is created
30+
await context.Database.EnsureCreatedAsync();
31+
32+
var testDownloadEvent = new DbDownloadEvent
33+
{
34+
Id = 1,
35+
CreatedAt = dateTimeInLocalTimeZone,
36+
ClientIp = "",
37+
CacheIdentifier = "",
38+
LastUpdatedAt = dateTimeInLocalTimeZone
39+
};
40+
41+
context.DownloadEvents.Add(testDownloadEvent);
42+
await context.SaveChangesAsync();
43+
}
44+
45+
// Second context: Verify the data was persisted and converted correctly
46+
using (var context = new DeveLanCacheUIDbContext(options))
47+
{
48+
var savedEvent = await context.DownloadEvents.FindAsync(1);
49+
Assert.IsNotNull(savedEvent, "Saved event should not be null.");
50+
51+
Assert.AreEqual(DateTimeKind.Utc, savedEvent.CreatedAt.Kind, "CreatedAt should be in UTC.");
52+
Assert.AreEqual(expectedUtcDateTime, savedEvent.CreatedAt, "CreatedAt should be converted to UTC.");
53+
Assert.AreEqual(expectedUtcDateTime, savedEvent.LastUpdatedAt, "LastUpdatedAt should be converted to UTC.");
54+
}
55+
}
56+
}
57+
}

DeveLanCacheUI_Backend.Tests/LogReading/LanCacheLogLineParserTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,5 +90,29 @@ public void SuperSplit_ParseCorrectly3()
9090
Assert.AreEqual("ctldl.windowsupdate.com", splitted[13]);
9191
Assert.AreEqual("-", splitted[14]);
9292
}
93+
94+
[TestMethod]
95+
public void SplittedToLanCacheLogEntryRaw_ParseCorrectly_AndAlsoCalculateFieldsWorks_ForDateTimeWithTimeZone()
96+
{
97+
var logEntry = LanCacheLogLineParser.LogLineToLanCacheLogEntryRaw("[steam] 10.88.10.1 / - - - [28/Jun/2023:20:14:49 +0800] \"GET /depot/434174/chunk/9437c354e87778aeafe94a65ee042432440d4037 HTTP/1.1\" 200 392304 \"-\" \"Valve/Steam HTTP Client 1.0\" \"HIT\" \"cache1-ams1.steamcontent.com\" \"-\"");
98+
99+
Assert.AreEqual("steam", logEntry.CacheIdentifier);
100+
Assert.AreEqual("10.88.10.1", logEntry.RemoteAddress);
101+
Assert.AreEqual("-", logEntry.ForwardedFor);
102+
Assert.AreEqual("-", logEntry.RemoteUser);
103+
Assert.AreEqual("28/Jun/2023:20:14:49 +0800", logEntry.TimeLocal);
104+
Assert.AreEqual("GET /depot/434174/chunk/9437c354e87778aeafe94a65ee042432440d4037 HTTP/1.1", logEntry.Request);
105+
Assert.AreEqual("200", logEntry.Status);
106+
Assert.AreEqual("392304", logEntry.BodyBytesSent);
107+
Assert.AreEqual("-", logEntry.Referer);
108+
Assert.AreEqual("Valve/Steam HTTP Client 1.0", logEntry.UserAgent);
109+
Assert.AreEqual("HIT", logEntry.UpstreamCacheStatus);
110+
Assert.AreEqual("cache1-ams1.steamcontent.com", logEntry.Host);
111+
Assert.AreEqual("-", logEntry.HttpRange);
112+
113+
logEntry.CalculateFields();
114+
115+
Assert.AreEqual(new DateTime(2023, 6, 28, 12, 14, 49, DateTimeKind.Utc), logEntry.DateTime.ToUniversalTime());
116+
}
93117
}
94118
}

DeveLanCacheUI_Backend/Db/DeveLanCacheUIDbContext.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace DeveLanCacheUI_Backend.Db
1+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
2+
3+
namespace DeveLanCacheUI_Backend.Db
24
{
35
public class DeveLanCacheUIDbContext : DbContext
46
{
@@ -14,7 +16,7 @@ public DeveLanCacheUIDbContext(DbContextOptions options) : base(options)
1416
{
1517

1618
}
17-
19+
1820
protected override void OnModelCreating(ModelBuilder modelBuilder)
1921
{
2022
base.OnModelCreating(modelBuilder);
@@ -59,5 +61,23 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
5961
}
6062
});
6163
}
64+
65+
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
66+
{
67+
ArgumentNullException.ThrowIfNull(configurationBuilder);
68+
69+
configurationBuilder.Properties<DateTime>().HaveConversion<DateTimeAsUtcValueConverter>();
70+
configurationBuilder.Properties<DateTime?>().HaveConversion<NullableDateTimeAsUtcValueConverter>();
71+
}
6272
}
73+
74+
public class NullableDateTimeAsUtcValueConverter() : ValueConverter<DateTime?, DateTime?>(
75+
v => !v.HasValue ? v : ToUtc(v.Value), v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v)
76+
{
77+
private static DateTime? ToUtc(DateTime v) => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime();
78+
}
79+
80+
public class DateTimeAsUtcValueConverter() : ValueConverter<DateTime, DateTime>(
81+
v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(), v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
82+
6383
}

DeveLanCacheUI_Backend/LogReading/Models/LanCacheLogEntryRaw.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class LanCacheLogEntryRaw
4242

4343
public void CalculateFields()
4444
{
45+
// Will automatically get converted to UTC when storing in Database
4546
DateTime = DateTime.ParseExact(TimeLocal, "dd/MMM/yyyy:HH:mm:ss zzz", CultureInfo.InvariantCulture);
4647
BodyBytesSentLong = long.Parse(BodyBytesSent);
4748

DeveLanCacheUI_Backend/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ public static void Main(string[] args)
8787
}
8888
else
8989
{
90-
builder.Services.AddHostedService<SteamDepotEnricherHostedService>();
9190
builder.Services.AddHostedService<SteamDepotDownloaderHostedService>();
91+
builder.Services.AddSingleton<SteamDepotEnricherHostedService>();
9292
builder.Services.AddSingleton<ISteamAppObtainerService, OriginalSteamAppObtainerService>();
9393
}
9494

DeveLanCacheUI_Backend/Services/OriginalDepotEnricher/SteamDepotDownloaderHostedService.cs

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@ public class SteamDepotDownloaderHostedService : BackgroundService
77
public IServiceProvider Services { get; }
88

99
private readonly DeveLanCacheConfiguration _deveLanCacheConfiguration;
10+
private readonly SteamDepotEnricherHostedService _steamDepotEnricherHostedService;
1011
private readonly ILogger<SteamDepotDownloaderHostedService> _logger;
1112
private readonly HttpClient _httpClient;
1213

1314
private const string DeveLanCacheUISteamDepotFinderLatestUrl = "https://api.github.com/repos/devedse/DeveLanCacheUI_SteamDepotFinder_Runner/releases/latest";
1415

1516
public SteamDepotDownloaderHostedService(IServiceProvider services,
1617
DeveLanCacheConfiguration deveLanCacheConfiguration,
18+
SteamDepotEnricherHostedService steamDepotEnricherHostedService,
1719
ILogger<SteamDepotDownloaderHostedService> logger)
1820
{
1921
Services = services;
2022
_deveLanCacheConfiguration = deveLanCacheConfiguration;
23+
_steamDepotEnricherHostedService = steamDepotEnricherHostedService;
2124
_logger = logger;
2225
_httpClient = new HttpClient();
2326
_httpClient.DefaultRequestHeaders.Add("User-Agent", "request");
@@ -40,39 +43,42 @@ private async Task GoRun(CancellationToken stoppingToken)
4043

4144
var depotFileDirectory = Path.Combine(deveLanCacheUIDataDirectory, "depotdir");
4245

43-
if (!Directory.Exists(depotFileDirectory))
46+
if (Directory.Exists(depotFileDirectory))
4447
{
45-
Directory.CreateDirectory(depotFileDirectory);
48+
// This is purely cleanup since we don't use the depot directory anymore. We just directly download and process.
49+
Directory.Delete(depotFileDirectory, true);
4650
}
4751

4852
while (!stoppingToken.IsCancellationRequested)
4953
{
5054
var shouldDownload = await NewDepotFileAvailable();
5155

56+
_logger.LogInformation("New Depot File Available: {NewVersionAvailable}, LatestVersion: {LatestVersion}, DownloadUrl: {DownloadUrl}",
57+
shouldDownload.NewVersionAvailable, shouldDownload.LatestVersion, shouldDownload.DownloadUrl);
58+
5259
if (shouldDownload.NewVersionAvailable)
5360
{
54-
var createdFileName = await GoDownload(depotFileDirectory, shouldDownload);
61+
if (shouldDownload.LatestVersion == null || string.IsNullOrWhiteSpace(shouldDownload.DownloadUrl))
62+
{
63+
throw new UnreachableException($"New version available, but LatestVersion or DownloadUrl is null. This should not happen. LatestVersion: {shouldDownload.LatestVersion}, DownloadUrl: {shouldDownload.DownloadUrl}");
64+
}
65+
var downloadedBytes = await GoDownload(depotFileDirectory, shouldDownload);
5566

56-
//Remove all other csv files in DepotDir except this one
57-
var depotFiles = Directory.GetFiles(depotFileDirectory).Where(t => Path.GetExtension(t).Equals(".csv", StringComparison.OrdinalIgnoreCase) && !t.EndsWith(createdFileName, StringComparison.OrdinalIgnoreCase));
58-
foreach (var depotFile in depotFiles)
67+
if (downloadedBytes != null)
68+
{
69+
await _steamDepotEnricherHostedService.GoProcess(shouldDownload.LatestVersion, downloadedBytes, stoppingToken);
70+
}
71+
else
5972
{
60-
try
61-
{
62-
File.Delete(depotFile);
63-
}
64-
catch (Exception ex)
65-
{
66-
_logger.LogWarning("Could not delete depot file: {DepotFile}, exception: {Exception}", depotFile, ex);
67-
}
73+
_logger.LogWarning("Downloaded depot file was null, not processing further.");
6874
}
6975
}
7076

7177
await Task.Delay(TimeSpan.FromHours(1));
7278
}
7379
}
7480

75-
private async Task<string?> GoDownload(string depotFileDirectory, (bool NewVersionAvailable, Version? LatestVersion, string? DownloadUrl) shouldDownload)
81+
private async Task<byte[]?> GoDownload(string depotFileDirectory, (bool NewVersionAvailable, Version? LatestVersion, string? DownloadUrl) shouldDownload)
7682
{
7783
_logger.LogInformation("Detected that new version '{NewVersionAvailable}' of Depot File is available, so downloading: {DownloadUrl}...", shouldDownload.NewVersionAvailable, shouldDownload.DownloadUrl);
7884

@@ -84,31 +90,7 @@ private async Task GoRun(CancellationToken stoppingToken)
8490
}
8591

8692
var bytes = await downloadedCsv.Content.ReadAsByteArrayAsync();
87-
88-
var fileName = $"depot_{shouldDownload.LatestVersion}.csv";
89-
var filePath = Path.Combine(depotFileDirectory, fileName);
90-
File.WriteAllBytes(filePath, bytes);
91-
92-
await using (var scope = Services.CreateAsyncScope())
93-
{
94-
using var dbContext = scope.ServiceProvider.GetRequiredService<DeveLanCacheUIDbContext>();
95-
var foundSetting = await dbContext.Settings.FirstOrDefaultAsync();
96-
if (foundSetting == null || foundSetting.Value == null)
97-
{
98-
foundSetting = new DbSetting()
99-
{
100-
Key = DbSetting.SettingKey_DepotVersion,
101-
Value = shouldDownload.LatestVersion?.ToString()
102-
};
103-
dbContext.Settings.Add(foundSetting);
104-
}
105-
else
106-
{
107-
foundSetting.Value = shouldDownload.LatestVersion?.ToString();
108-
}
109-
await dbContext.SaveChangesAsync();
110-
}
111-
return fileName;
93+
return bytes;
11294
}
11395

11496
private async Task<(bool NewVersionAvailable, Version? LatestVersion, string? DownloadUrl)> NewDepotFileAvailable()
@@ -148,7 +130,7 @@ private async Task GoRun(CancellationToken stoppingToken)
148130
await using (var scope = Services.CreateAsyncScope())
149131
{
150132
using var dbContext = scope.ServiceProvider.GetRequiredService<DeveLanCacheUIDbContext>();
151-
var foundSetting = await dbContext.Settings.FirstOrDefaultAsync();
133+
var foundSetting = await dbContext.Settings.FirstOrDefaultAsync(t => t.Key == DbSetting.SettingKey_DepotVersion);
152134
if (foundSetting == null || foundSetting.Value == null)
153135
{
154136
_logger.LogInformation("Update of Depot File required because CurrentVersion could not be found");

0 commit comments

Comments
 (0)