Add File validation (#344)
* Add file validation * Revert "Added checks.dat build script (#343)" This reverts commit 39228f88e705b58858d162256a5b5e10fe99148c. * Update to use pwsh * Wrap code in using
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
$scriptDir = $PSScriptRoot
|
||||
$assetsPath = Join-Path $scriptDir 'Assets'
|
||||
$outputFile = Join-Path $assetsPath 'checks.dat'
|
||||
|
||||
$files = Get-ChildItem -Path $assetsPath -Recurse -File |
|
||||
Where-Object { $_.FullName -notmatch [regex]::Escape((Join-Path $assetsPath 'images')) } |
|
||||
Sort-Object FullName
|
||||
|
||||
$hashes = foreach ($file in $files) {
|
||||
$bytes = [System.IO.File]::ReadAllBytes($file.FullName)
|
||||
$md5 = [System.Security.Cryptography.MD5]::Create()
|
||||
$hashBytes = $md5.ComputeHash($bytes)
|
||||
$md5.Dispose()
|
||||
|
||||
$hashString = [BitConverter]::ToString($hashBytes) -replace '-', ''
|
||||
|
||||
$relativePath = $file.FullName.Substring($assetsPath.Length + 1) -replace '\\', '/'
|
||||
|
||||
[PSCustomObject]@{
|
||||
Path = $relativePath
|
||||
Hash = $hashString
|
||||
}
|
||||
}
|
||||
|
||||
$jsonString = $hashes | ConvertTo-Json -Depth 10
|
||||
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($jsonString)
|
||||
$base64String = [Convert]::ToBase64String($bytes)
|
||||
|
||||
Set-Content -Path $outputFile -Value $base64String -Encoding ASCII
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\Build.props"/>
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuildHashFile" AfterTargets="Build">
|
||||
<Exec Command="pwsh -NoProfile -ExecutionPolicy Bypass -File "$(ProjectDir)PostBuild.ps1"" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath=""/>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SPTarkov.DI.Annotations;
|
||||
using SPTarkov.Server.Core.DI;
|
||||
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
||||
@@ -17,14 +19,17 @@ public class DatabaseImporter(
|
||||
LocalisationService _localisationService,
|
||||
DatabaseServer _databaseServer,
|
||||
ImageRouter _imageRouter,
|
||||
ImporterUtil _importerUtil
|
||||
ImporterUtil _importerUtil,
|
||||
JsonUtil _jsonUtil
|
||||
) : IOnLoad
|
||||
{
|
||||
private const string _sptDataPath = "./Assets/";
|
||||
protected ISptLogger<DatabaseImporter> _logger = logger;
|
||||
protected Dictionary<string, string> databaseHashes = [];
|
||||
|
||||
public async Task OnLoad()
|
||||
{
|
||||
await LoadHashes();
|
||||
await HydrateDatabase(_sptDataPath);
|
||||
|
||||
var imageFilePath = $"{_sptDataPath}images/";
|
||||
@@ -62,6 +67,41 @@ public class DatabaseImporter(
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async Task LoadHashes()
|
||||
{
|
||||
var checksFilePath = System.IO.Path.Combine(_sptDataPath, "checks.dat");
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(checksFilePath))
|
||||
{
|
||||
await using FileStream fs = File.OpenRead(checksFilePath);
|
||||
|
||||
using var reader = new StreamReader(fs, Encoding.ASCII);
|
||||
string base64Content = await reader.ReadToEndAsync();
|
||||
|
||||
byte[] jsonBytes = Convert.FromBase64String(base64Content);
|
||||
|
||||
await using var ms = new MemoryStream(jsonBytes);
|
||||
|
||||
var FileHashes = await _jsonUtil.DeserializeFromMemoryStreamAsync<List<FileHash>>(ms) ?? [];
|
||||
|
||||
foreach(var hash in FileHashes)
|
||||
{
|
||||
databaseHashes.Add(hash.Path, hash.Hash);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("validation_error_exception", checksFilePath));
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("validation_error_exception", checksFilePath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all json files in database folder and map into a json object
|
||||
* @param filepath path to database folder
|
||||
@@ -73,7 +113,8 @@ public class DatabaseImporter(
|
||||
timer.Start();
|
||||
|
||||
var dataToImport = await _importerUtil.LoadRecursiveAsync<DatabaseTables>(
|
||||
$"{filePath}database/"
|
||||
$"{filePath}database/",
|
||||
VerifyDatabase
|
||||
);
|
||||
|
||||
// TODO: Fix loading of traders, so their full path is not included as the key
|
||||
@@ -92,8 +133,45 @@ public class DatabaseImporter(
|
||||
|
||||
dataToImport.Traders = tempTraders;
|
||||
|
||||
_logger.Info( _localisationService.GetText("importing_database_finish"));
|
||||
_logger.Info(_localisationService.GetText("importing_database_finish"));
|
||||
_logger.Debug($"Database import took {timer.ElapsedMilliseconds}ms");
|
||||
_databaseServer.SetTables(dataToImport);
|
||||
}
|
||||
|
||||
protected async Task VerifyDatabase(string fileName)
|
||||
{
|
||||
var relativePath = fileName.StartsWith(_sptDataPath, StringComparison.OrdinalIgnoreCase)
|
||||
? fileName.Substring(_sptDataPath.Length)
|
||||
: fileName;
|
||||
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
await using (var stream = File.OpenRead(fileName))
|
||||
{
|
||||
var hashBytes = await md5.ComputeHashAsync(stream);
|
||||
var hashString = Convert.ToHexString(hashBytes);
|
||||
|
||||
bool hashKeyExists = databaseHashes.ContainsKey(relativePath);
|
||||
|
||||
if (hashKeyExists)
|
||||
{
|
||||
if (databaseHashes[relativePath] != hashString)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("validation_error_file", fileName));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("validation_error_file", fileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FileHash
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string Hash { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ public class ImporterUtil(ISptLogger<ImporterUtil> _logger, FileUtil _fileUtil,
|
||||
|
||||
public async Task<T> LoadRecursiveAsync<T>(
|
||||
string filePath,
|
||||
Action<string>? onReadCallback = null,
|
||||
Action<string, object>? onObjectDeserialized = null
|
||||
Func<string, Task>? onReadCallback = null,
|
||||
Func<string, object, Task>? onObjectDeserialized = null
|
||||
)
|
||||
{
|
||||
var result = await LoadRecursiveAsync(filePath, typeof(T), onReadCallback, onObjectDeserialized);
|
||||
@@ -35,8 +35,8 @@ public class ImporterUtil(ISptLogger<ImporterUtil> _logger, FileUtil _fileUtil,
|
||||
protected async Task<object> LoadRecursiveAsync(
|
||||
string filePath,
|
||||
Type loadedType,
|
||||
Action<string>? onReadCallback = null,
|
||||
Action<string, object>? onObjectDeserialized = null
|
||||
Func<string, Task>? onReadCallback = null,
|
||||
Func<string, object, Task>? onObjectDeserialized = null
|
||||
)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
@@ -66,7 +66,7 @@ public class ImporterUtil(ISptLogger<ImporterUtil> _logger, FileUtil _fileUtil,
|
||||
continue;
|
||||
}
|
||||
|
||||
tasks.Add(ProcessDirectoryAsync(directory, loadedType, result, dictionaryLock));
|
||||
tasks.Add(ProcessDirectoryAsync(directory, loadedType, result, onReadCallback, onObjectDeserialized, dictionaryLock));
|
||||
}
|
||||
|
||||
// Wait for all tasks to finish
|
||||
@@ -78,15 +78,18 @@ public class ImporterUtil(ISptLogger<ImporterUtil> _logger, FileUtil _fileUtil,
|
||||
private async Task ProcessFileAsync(
|
||||
string file,
|
||||
Type loadedType,
|
||||
Action<string>? onReadCallback,
|
||||
Action<string, object>? onObjectDeserialized,
|
||||
Func<string, Task>? onReadCallback,
|
||||
Func<string, object, Task>? onObjectDeserialized,
|
||||
object result,
|
||||
Lock dictionaryLock
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
onReadCallback?.Invoke(file);
|
||||
if (onReadCallback != null)
|
||||
{
|
||||
await onReadCallback(file);
|
||||
}
|
||||
|
||||
// Get the set method to update the object
|
||||
var setMethod = GetSetMethod(
|
||||
@@ -98,7 +101,10 @@ public class ImporterUtil(ISptLogger<ImporterUtil> _logger, FileUtil _fileUtil,
|
||||
|
||||
var fileDeserialized = await DeserializeFileAsync(file, propertyType);
|
||||
|
||||
onObjectDeserialized?.Invoke(file, fileDeserialized);
|
||||
if (onObjectDeserialized != null)
|
||||
{
|
||||
await onObjectDeserialized(file, fileDeserialized);
|
||||
}
|
||||
|
||||
lock (dictionaryLock)
|
||||
{
|
||||
@@ -120,6 +126,8 @@ public class ImporterUtil(ISptLogger<ImporterUtil> _logger, FileUtil _fileUtil,
|
||||
string directory,
|
||||
Type loadedType,
|
||||
object result,
|
||||
Func<string, Task>? onReadCallback,
|
||||
Func<string, object, Task>? onObjectDeserialized,
|
||||
Lock dictionaryLock
|
||||
)
|
||||
{
|
||||
@@ -132,7 +140,7 @@ public class ImporterUtil(ISptLogger<ImporterUtil> _logger, FileUtil _fileUtil,
|
||||
out var isDictionary
|
||||
);
|
||||
|
||||
var loadedData = await LoadRecursiveAsync($"{directory}/", matchedProperty);
|
||||
var loadedData = await LoadRecursiveAsync($"{directory}/", matchedProperty, onReadCallback, onObjectDeserialized);
|
||||
|
||||
lock (dictionaryLock)
|
||||
{
|
||||
|
||||
@@ -154,6 +154,16 @@ public class JsonUtil
|
||||
return await JsonSerializer.DeserializeAsync(fs, type, jsonSerializerOptionsNoIndent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert JSON into an object from a MemoryStream asynchronously
|
||||
/// </summary>
|
||||
/// <param name="fs">The memory stream to deserialize</param>
|
||||
/// <returns>T</returns>
|
||||
public async Task<T?> DeserializeFromMemoryStreamAsync<T>(MemoryStream ms)
|
||||
{
|
||||
return await JsonSerializer.DeserializeAsync<T>(ms, jsonSerializerOptionsNoIndent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert an object into JSON
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user