feat: add file cache mapping diagnostic
This commit is contained in:
43
scripts/test-verify-file-cache-mapping.ps1
Normal file
43
scripts/test-verify-file-cache-mapping.ps1
Normal file
@@ -0,0 +1,43 @@
|
||||
param()
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path
|
||||
$script = Join-Path $repo "scripts\verify-file-cache-mapping.ps1"
|
||||
$tempDir = Join-Path $repo ("runs\file-cache-mapping-test-" + [guid]::NewGuid().ToString("N"))
|
||||
$archiveList = Join-Path $tempDir "archive-list.txt"
|
||||
|
||||
function Assert-True([bool]$Condition, [string]$Message) {
|
||||
if (-not $Condition) { throw $Message }
|
||||
}
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
||||
@(
|
||||
"C:\redacted\importal\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\redacted-report.docx",
|
||||
"C:\redacted\importal\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\redacted-sheet.xlsx",
|
||||
"C:\redacted\other\ignored.txt"
|
||||
) | Set-Content -LiteralPath $archiveList -Encoding UTF8
|
||||
|
||||
$output = & powershell -NoProfile -ExecutionPolicy Bypass -File $script -ArchiveListPath $archiveList -SkipDb
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "verify-file-cache-mapping.ps1 failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
$json = $output | ConvertFrom-Json
|
||||
|
||||
Assert-True ($json.ok -eq $true) "ok should be true"
|
||||
Assert-True ($json.cache_checked -eq $true) "cache should be checked"
|
||||
Assert-True ($json.cache_candidate_count -eq 2) "cache candidate count should be 2"
|
||||
Assert-True ($json.cache_extension_counts.".docx" -eq 1) "docx extension count should be 1"
|
||||
Assert-True ($json.cache_extension_counts.".xlsx" -eq 1) "xlsx extension count should be 1"
|
||||
Assert-True ($json.hash_dir_length_counts."32" -eq 2) "hash dir length count should be 2"
|
||||
Assert-True ($json.raw_paths_printed -eq $false) "raw paths must not be printed"
|
||||
Assert-True ($json.file_contents_read -eq $false) "file contents must not be read"
|
||||
Assert-True ($json.file_paths_returned -eq $false) "file paths must not be returned"
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $tempDir) {
|
||||
Remove-Item -LiteralPath $tempDir -Recurse -Force
|
||||
}
|
||||
}
|
||||
552
scripts/verify-file-cache-mapping.ps1
Normal file
552
scripts/verify-file-cache-mapping.ps1
Normal file
@@ -0,0 +1,552 @@
|
||||
param(
|
||||
[string]$ArchiveListPath = "",
|
||||
[string]$CacheRoot = "",
|
||||
[string]$SQLiteDllPath = $env:ISPHERE_MSGLIB_SQLITE_DLL,
|
||||
[string]$DbPath = $env:ISPHERE_MSGLIB_DB,
|
||||
[string]$Password = "123",
|
||||
[switch]$SkipDb
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path
|
||||
|
||||
function Add-Count([hashtable]$Map, [string]$Key, [int]$Increment = 1) {
|
||||
if ([string]::IsNullOrWhiteSpace($Key)) { return }
|
||||
if ($Map.ContainsKey($Key)) {
|
||||
$Map[$Key] = [int]$Map[$Key] + $Increment
|
||||
}
|
||||
else {
|
||||
$Map[$Key] = $Increment
|
||||
}
|
||||
}
|
||||
|
||||
function Get-Sha256Text([string]$Value) {
|
||||
$sha = [System.Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Value)
|
||||
$hash = $sha.ComputeHash($bytes)
|
||||
return (($hash | ForEach-Object { $_.ToString("x2") }) -join "")
|
||||
}
|
||||
finally {
|
||||
$sha.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Normalize-FileName([string]$Value) {
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) { return "" }
|
||||
$leaf = ($Value -split '[\\/]')[-1]
|
||||
return $leaf.Trim().ToLowerInvariant()
|
||||
}
|
||||
|
||||
function Normalize-Extension([string]$Value) {
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) { return "" }
|
||||
$extension = $Value.Trim().ToLowerInvariant()
|
||||
if ($extension -match '^\.[a-z0-9]{1,8}$') { return $extension }
|
||||
return ".other"
|
||||
}
|
||||
|
||||
function New-EmptyCacheSummary {
|
||||
return [ordered]@{
|
||||
checked = $false
|
||||
candidate_count = 0
|
||||
extension_counts = @{}
|
||||
hash_dir_length_counts = @{}
|
||||
hash_dir_value_hash_counts = @{}
|
||||
filename_hash_counts = @{}
|
||||
filename_size_hash_counts = @{}
|
||||
has_size_data = $false
|
||||
}
|
||||
}
|
||||
|
||||
function Add-CacheCandidate($Summary, [string]$Name, [Nullable[Int64]]$Size, [string]$HashDir) {
|
||||
$normalizedName = Normalize-FileName $Name
|
||||
if ($normalizedName -eq "") { return }
|
||||
$extension = Normalize-Extension ([System.IO.Path]::GetExtension($normalizedName))
|
||||
if ($extension -ne "") {
|
||||
Add-Count $Summary.extension_counts $extension
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HashDir)) {
|
||||
Add-Count $Summary.hash_dir_length_counts ([string]$HashDir.Length)
|
||||
Add-Count $Summary.hash_dir_value_hash_counts (Get-Sha256Text $HashDir.ToLowerInvariant())
|
||||
}
|
||||
Add-Count $Summary.filename_hash_counts (Get-Sha256Text $normalizedName)
|
||||
if ($null -ne $Size -and $Size.Value -ge 0) {
|
||||
$Summary["has_size_data"] = $true
|
||||
Add-Count $Summary.filename_size_hash_counts (Get-Sha256Text ($normalizedName + "|" + $Size.Value))
|
||||
}
|
||||
$Summary["candidate_count"] = [int]$Summary["candidate_count"] + 1
|
||||
}
|
||||
|
||||
function Get-HashDirFromPathText([string]$Value) {
|
||||
$match = [regex]::Match($Value, '(?i)(?:^|[\\/])importal[\\/]([0-9a-f]{32,})(?:[\\/])')
|
||||
if ($match.Success) {
|
||||
return $match.Groups[1].Value.ToLowerInvariant()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function Read-CacheSummaryFromArchiveList([string]$Path) {
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "ArchiveListPath not found"
|
||||
}
|
||||
$summary = New-EmptyCacheSummary
|
||||
$summary["checked"] = $true
|
||||
foreach ($line in Get-Content -LiteralPath $Path) {
|
||||
$text = [string]$line
|
||||
$hashDir = Get-HashDirFromPathText $text
|
||||
if ($hashDir -eq "") { continue }
|
||||
Add-CacheCandidate $summary $text $null $hashDir
|
||||
}
|
||||
return $summary
|
||||
}
|
||||
|
||||
function Read-CacheSummaryFromRoot([string]$Path) {
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "CacheRoot not found"
|
||||
}
|
||||
$summary = New-EmptyCacheSummary
|
||||
$summary["checked"] = $true
|
||||
foreach ($file in Get-ChildItem -LiteralPath $Path -File -Recurse) {
|
||||
$hashDir = Get-HashDirFromPathText $file.FullName
|
||||
if ($hashDir -eq "") { continue }
|
||||
Add-CacheCandidate $summary $file.Name ([Int64]$file.Length) $hashDir
|
||||
}
|
||||
return $summary
|
||||
}
|
||||
|
||||
function Convert-CountObjectToHashtable($Value) {
|
||||
$map = @{}
|
||||
if ($null -eq $Value) { return $map }
|
||||
if ($Value -is [System.Collections.IDictionary]) {
|
||||
foreach ($key in $Value.Keys) {
|
||||
$map[[string]$key] = [int]$Value[$key]
|
||||
}
|
||||
return $map
|
||||
}
|
||||
foreach ($property in $Value.PSObject.Properties) {
|
||||
$map[$property.Name] = [int]$property.Value
|
||||
}
|
||||
return $map
|
||||
}
|
||||
|
||||
function Count-Intersections([hashtable]$Left, [hashtable]$Right) {
|
||||
$count = 0
|
||||
foreach ($key in $Left.Keys) {
|
||||
if ($Right.ContainsKey($key)) {
|
||||
$count += [Math]::Min([int]$Left[$key], [int]$Right[$key])
|
||||
}
|
||||
}
|
||||
return $count
|
||||
}
|
||||
|
||||
function Count-UniqueIntersections([hashtable]$Left, [hashtable]$Right) {
|
||||
$count = 0
|
||||
foreach ($key in $Left.Keys) {
|
||||
if ($Right.ContainsKey($key) -and [int]$Left[$key] -eq 1 -and [int]$Right[$key] -eq 1) {
|
||||
$count++
|
||||
}
|
||||
}
|
||||
return $count
|
||||
}
|
||||
|
||||
function Invoke-ReceiveFileRecordProbe([string]$SQLiteDll, [string]$Database, [string]$DbPassword) {
|
||||
if (-not (Test-Path -LiteralPath $SQLiteDll)) {
|
||||
throw "SQLite DLL not found"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $Database)) {
|
||||
throw "MsgLib DB not found"
|
||||
}
|
||||
|
||||
$tempDir = Join-Path $repo ("runs\file-cache-mapping-db-probe-" + [guid]::NewGuid().ToString("N"))
|
||||
$source = Join-Path $tempDir "ReceiveFileRecordProbe.cs"
|
||||
$exe = Join-Path $tempDir "ReceiveFileRecordProbe.exe"
|
||||
$sqliteCopy = Join-Path $tempDir "System.Data.SQLite.dll"
|
||||
$csc = "$env:WINDIR\Microsoft.NET\Framework\v4.0.30319\csc.exe"
|
||||
if (-not (Test-Path -LiteralPath $csc)) {
|
||||
throw "32-bit .NET Framework csc.exe v4.0.30319 not found"
|
||||
}
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
||||
Copy-Item -LiteralPath $SQLiteDll -Destination $sqliteCopy -Force
|
||||
@'
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Web.Script.Serialization;
|
||||
|
||||
internal static class ReceiveFileRecordProbe
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
string sqliteDllPath = args[0];
|
||||
string dbPath = args[1];
|
||||
string password = args.Length > 2 ? args[2] : "123";
|
||||
AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Name.StartsWith("System.Data.SQLite", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Assembly.LoadFrom(sqliteDllPath);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
Assembly sqliteAssembly = Assembly.LoadFrom(sqliteDllPath);
|
||||
Type factoryType = sqliteAssembly.GetType("System.Data.SQLite.SQLiteFactory", true);
|
||||
FieldInfo instanceField = factoryType.GetField("Instance", BindingFlags.Public | BindingFlags.Static);
|
||||
DbProviderFactory factory = (DbProviderFactory)instanceField.GetValue(null);
|
||||
|
||||
var result = NewResult();
|
||||
using (DbConnection connection = factory.CreateConnection())
|
||||
{
|
||||
connection.ConnectionString = "Data Source=" + dbPath + ";Version=3;Read Only=True;Password=" + password + ";";
|
||||
connection.Open();
|
||||
ExecuteNonQuery(connection, "PRAGMA query_only=ON");
|
||||
if (!TableExists(connection, "TD_ReceiveFileRecord"))
|
||||
{
|
||||
PrintJson(result);
|
||||
return 0;
|
||||
}
|
||||
result["table_available"] = true;
|
||||
HashSet<string> columns = ReadColumns(connection, "TD_ReceiveFileRecord");
|
||||
string msgColumn = ExistingColumn(columns, "ReceiveMsgGuid");
|
||||
string nameColumn = ExistingColumn(columns, "FileName");
|
||||
string sizeColumn = ExistingColumn(columns, "FileSize", "Size");
|
||||
string sourceColumn = ExistingColumn(columns, "SourceID", "FileGuid", "FileId");
|
||||
if (nameColumn == "")
|
||||
{
|
||||
PrintJson(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
string sql = "select " +
|
||||
SelectColumn(msgColumn, "receive_msg_guid") + ", " +
|
||||
SelectColumn(nameColumn, "file_name") + ", " +
|
||||
SelectColumn(sizeColumn, "size_bytes") + ", " +
|
||||
SelectColumn(sourceColumn, "source_id") +
|
||||
" from " + QuoteIdent("TD_ReceiveFileRecord") + " limit 100000";
|
||||
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = sql;
|
||||
using (DbDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
result["record_count"] = (int)result["record_count"] + 1;
|
||||
string receiveMsgGuid = ReaderString(reader, "receive_msg_guid");
|
||||
string fileName = SafeFileName(ReaderString(reader, "file_name"));
|
||||
long size = ReaderInt64(reader, "size_bytes");
|
||||
string sourceId = ReaderString(reader, "source_id").Trim().ToLowerInvariant();
|
||||
|
||||
if (receiveMsgGuid.Trim() != "") result["receive_msg_guid_present_count"] = (int)result["receive_msg_guid_present_count"] + 1;
|
||||
if (fileName.Trim() != "") result["filename_present_count"] = (int)result["filename_present_count"] + 1;
|
||||
if (size > 0) result["size_present_count"] = (int)result["size_present_count"] + 1;
|
||||
if (sourceId != "") result["source_id_present_count"] = (int)result["source_id_present_count"] + 1;
|
||||
|
||||
string normalizedName = fileName.Trim().ToLowerInvariant();
|
||||
if (normalizedName != "")
|
||||
{
|
||||
AddCount((Dictionary<string, object>)result["extension_counts"], SafeExtension(Path.GetExtension(normalizedName)));
|
||||
AddCount((Dictionary<string, object>)result["filename_hash_counts"], Sha256(normalizedName));
|
||||
if (size > 0) AddCount((Dictionary<string, object>)result["filename_size_hash_counts"], Sha256(normalizedName + "|" + size.ToString()));
|
||||
}
|
||||
if (sourceId != "")
|
||||
{
|
||||
AddCount((Dictionary<string, object>)result["source_id_hash_counts"], Sha256(sourceId));
|
||||
if (IsHexLike(sourceId)) result["source_id_hash_shape_count"] = (int)result["source_id_hash_shape_count"] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PrintJson(result);
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine(ex.GetType().Name + ": " + ex.Message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> NewResult()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "table_available", false },
|
||||
{ "record_count", 0 },
|
||||
{ "filename_present_count", 0 },
|
||||
{ "size_present_count", 0 },
|
||||
{ "source_id_present_count", 0 },
|
||||
{ "receive_msg_guid_present_count", 0 },
|
||||
{ "source_id_hash_shape_count", 0 },
|
||||
{ "extension_counts", new Dictionary<string, object>() },
|
||||
{ "filename_hash_counts", new Dictionary<string, object>() },
|
||||
{ "filename_size_hash_counts", new Dictionary<string, object>() },
|
||||
{ "source_id_hash_counts", new Dictionary<string, object>() }
|
||||
};
|
||||
}
|
||||
|
||||
private static void PrintJson(Dictionary<string, object> value)
|
||||
{
|
||||
Console.WriteLine(new JavaScriptSerializer().Serialize(value));
|
||||
}
|
||||
|
||||
private static void AddCount(Dictionary<string, object> map, string key)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key)) return;
|
||||
if (map.ContainsKey(key)) map[key] = (int)map[key] + 1;
|
||||
else map[key] = 1;
|
||||
}
|
||||
|
||||
private static bool TableExists(DbConnection connection, string table)
|
||||
{
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "select count(*) from sqlite_master where type='table' and name=@name";
|
||||
DbParameter parameter = command.CreateParameter();
|
||||
parameter.ParameterName = "@name";
|
||||
parameter.Value = table;
|
||||
command.Parameters.Add(parameter);
|
||||
return Convert.ToInt32(command.ExecuteScalar()) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<string> ReadColumns(DbConnection connection, string table)
|
||||
{
|
||||
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "pragma table_info(" + QuoteIdent(table) + ")";
|
||||
using (DbDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read()) columns.Add(Convert.ToString(reader["name"]));
|
||||
}
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
private static string ExistingColumn(HashSet<string> columns, params string[] names)
|
||||
{
|
||||
foreach (string name in names)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(name) && columns.Contains(name)) return name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static string SelectColumn(string column, string alias)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(column)) return "'' as " + QuoteIdent(alias);
|
||||
return QuoteIdent(column) + " as " + QuoteIdent(alias);
|
||||
}
|
||||
|
||||
private static string QuoteIdent(string value)
|
||||
{
|
||||
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
|
||||
private static string ReaderString(DbDataReader reader, string column)
|
||||
{
|
||||
object value = reader[column];
|
||||
if (value == null || value == DBNull.Value) return "";
|
||||
return Convert.ToString(value);
|
||||
}
|
||||
|
||||
private static long ReaderInt64(DbDataReader reader, string column)
|
||||
{
|
||||
object value = reader[column];
|
||||
if (value == null || value == DBNull.Value) return 0;
|
||||
long parsed;
|
||||
if (long.TryParse(Convert.ToString(value), out parsed)) return parsed;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static string SafeFileName(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return "";
|
||||
try { return Path.GetFileName(value); }
|
||||
catch
|
||||
{
|
||||
int slash = Math.Max(value.LastIndexOf('/'), value.LastIndexOf('\\'));
|
||||
if (slash >= 0 && slash + 1 < value.Length) return value.Substring(slash + 1);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static string SafeExtension(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return "";
|
||||
string extension = value.Trim().ToLowerInvariant();
|
||||
if (extension.Length < 2 || extension.Length > 9) return ".other";
|
||||
if (extension[0] != '.') return ".other";
|
||||
for (int i = 1; i < extension.Length; i++)
|
||||
{
|
||||
char c = extension[i];
|
||||
bool ok = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||
if (!ok) return ".other";
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
private static bool IsHexLike(string value)
|
||||
{
|
||||
if (value.Length < 16) return false;
|
||||
foreach (char c in value)
|
||||
{
|
||||
bool ok = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
if (!ok) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string Sha256(string value)
|
||||
{
|
||||
using (SHA256 sha = SHA256.Create())
|
||||
{
|
||||
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(value));
|
||||
var builder = new StringBuilder(hash.Length * 2);
|
||||
foreach (byte b in hash) builder.Append(b.ToString("x2"));
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExecuteNonQuery(DbConnection connection, string sql)
|
||||
{
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = sql;
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
'@ | Set-Content -LiteralPath $source -Encoding UTF8
|
||||
|
||||
& $csc `
|
||||
/nologo `
|
||||
/target:exe `
|
||||
/platform:x86 `
|
||||
/optimize+ `
|
||||
/warn:0 `
|
||||
/out:$exe `
|
||||
/reference:System.dll `
|
||||
/reference:System.Core.dll `
|
||||
/reference:System.Data.dll `
|
||||
/reference:System.Web.Extensions.dll `
|
||||
/reference:$sqliteCopy `
|
||||
$source | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "ReceiveFileRecordProbe compilation failed"
|
||||
}
|
||||
@'
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||
<supportedRuntime version="v4.0" />
|
||||
<supportedRuntime version="v2.0.50727" />
|
||||
</startup>
|
||||
</configuration>
|
||||
'@ | Set-Content -LiteralPath ($exe + ".config") -Encoding UTF8
|
||||
|
||||
$output = & $exe $sqliteCopy $Database $DbPassword
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "ReceiveFileRecordProbe failed"
|
||||
}
|
||||
return $output | ConvertFrom-Json
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $tempDir) {
|
||||
Remove-Item -LiteralPath $tempDir -Recurse -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$cacheSummary = New-EmptyCacheSummary
|
||||
if (-not [string]::IsNullOrWhiteSpace($ArchiveListPath)) {
|
||||
$cacheSummary = Read-CacheSummaryFromArchiveList $ArchiveListPath
|
||||
}
|
||||
elseif (-not [string]::IsNullOrWhiteSpace($CacheRoot)) {
|
||||
$cacheSummary = Read-CacheSummaryFromRoot $CacheRoot
|
||||
}
|
||||
|
||||
$dbChecked = $false
|
||||
$dbProbe = [ordered]@{
|
||||
table_available = $false
|
||||
record_count = 0
|
||||
filename_present_count = 0
|
||||
size_present_count = 0
|
||||
source_id_present_count = 0
|
||||
receive_msg_guid_present_count = 0
|
||||
source_id_hash_shape_count = 0
|
||||
extension_counts = @{}
|
||||
filename_hash_counts = @{}
|
||||
filename_size_hash_counts = @{}
|
||||
source_id_hash_counts = @{}
|
||||
}
|
||||
|
||||
if (-not $SkipDb) {
|
||||
if ([string]::IsNullOrWhiteSpace($SQLiteDllPath) -or [string]::IsNullOrWhiteSpace($DbPath)) {
|
||||
throw "MsgLib DB probe requires SQLiteDllPath and DbPath, or pass -SkipDb"
|
||||
}
|
||||
$dbProbe = Invoke-ReceiveFileRecordProbe $SQLiteDllPath $DbPath $Password
|
||||
$dbChecked = $true
|
||||
}
|
||||
|
||||
$dbFilenameHashes = Convert-CountObjectToHashtable $dbProbe.filename_hash_counts
|
||||
$dbFilenameSizeHashes = Convert-CountObjectToHashtable $dbProbe.filename_size_hash_counts
|
||||
$dbSourceIDHashes = Convert-CountObjectToHashtable $dbProbe.source_id_hash_counts
|
||||
$cacheFilenameHashes = [hashtable]$cacheSummary["filename_hash_counts"]
|
||||
$cacheFilenameSizeHashes = [hashtable]$cacheSummary["filename_size_hash_counts"]
|
||||
$cacheHashDirValueHashes = [hashtable]$cacheSummary["hash_dir_value_hash_counts"]
|
||||
|
||||
$filenameOnlyMatches = Count-Intersections $dbFilenameHashes $cacheFilenameHashes
|
||||
$filenameSizeMatches = Count-Intersections $dbFilenameSizeHashes $cacheFilenameSizeHashes
|
||||
$uniqueFilenameSizeMatches = Count-UniqueIntersections $dbFilenameSizeHashes $cacheFilenameSizeHashes
|
||||
$sourceIDHashDirMatches = Count-Intersections $dbSourceIDHashes $cacheHashDirValueHashes
|
||||
|
||||
$mappingValidated = ($uniqueFilenameSizeMatches -gt 0 -and [bool]$cacheSummary["has_size_data"])
|
||||
$recommendedNextNode = "additional evidence request"
|
||||
if ($mappingValidated) {
|
||||
$recommendedNextNode = "R4 cache-file resolver candidate"
|
||||
}
|
||||
elseif ($filenameOnlyMatches -gt 0 -or $sourceIDHashDirMatches -gt 0) {
|
||||
$recommendedNextNode = "additional evidence request: collect file size or cache root metadata"
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
ok = $true
|
||||
verification = "file_cache_mapping"
|
||||
db_checked = $dbChecked
|
||||
db_table_available = [bool]$dbProbe.table_available
|
||||
db_record_count = [int]$dbProbe.record_count
|
||||
db_filename_present_count = [int]$dbProbe.filename_present_count
|
||||
db_size_present_count = [int]$dbProbe.size_present_count
|
||||
db_source_id_present_count = [int]$dbProbe.source_id_present_count
|
||||
db_receive_msg_guid_present_count = [int]$dbProbe.receive_msg_guid_present_count
|
||||
db_source_id_hash_shape_count = [int]$dbProbe.source_id_hash_shape_count
|
||||
db_extension_counts = $dbProbe.extension_counts
|
||||
cache_checked = [bool]$cacheSummary["checked"]
|
||||
cache_candidate_count = [int]$cacheSummary["candidate_count"]
|
||||
cache_extension_counts = $cacheSummary["extension_counts"]
|
||||
hash_dir_length_counts = $cacheSummary["hash_dir_length_counts"]
|
||||
filename_only_match_count = $filenameOnlyMatches
|
||||
filename_size_match_count = $filenameSizeMatches
|
||||
unique_filename_size_match_count = $uniqueFilenameSizeMatches
|
||||
source_id_hash_dir_match_count = $sourceIDHashDirMatches
|
||||
mapping_validated = $mappingValidated
|
||||
recommended_next_node = $recommendedNextNode
|
||||
raw_paths_printed = $false
|
||||
file_contents_read = $false
|
||||
file_paths_returned = $false
|
||||
raw_rows_returned = $false
|
||||
message_body_values_printed = $false
|
||||
} | ConvertTo-Json -Depth 8 -Compress
|
||||
Reference in New Issue
Block a user