wabbajack/Wabbajack.VFS/FileHashCache.cs

132 lines
4.3 KiB
C#
Raw Permalink Normal View History

2021-09-27 12:42:46 +00:00
using System.Data.SQLite;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
namespace Wabbajack.VFS;
public class FileHashCache
2021-09-27 12:42:46 +00:00
{
2021-10-23 16:51:17 +00:00
private readonly SQLiteConnection _conn;
private readonly string _connectionString;
private readonly IResource<FileHashCache> _limiter;
private readonly AbsolutePath _location;
public FileHashCache(AbsolutePath location, IResource<FileHashCache> limiter)
2021-09-27 12:42:46 +00:00
{
2021-10-23 16:51:17 +00:00
_limiter = limiter;
_location = location;
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
if (!_location.Parent.DirectoryExists())
_location.Parent.CreateDirectory();
_connectionString =
string.Intern($"URI=file:{_location};Pooling=True;Max Pool Size=100; Journal Mode=Memory;");
_conn = new SQLiteConnection(_connectionString);
_conn.Open();
using var cmd = new SQLiteCommand(_conn);
cmd.CommandText = @"CREATE TABLE IF NOT EXISTS HashCache (
2021-09-27 12:42:46 +00:00
Path TEXT PRIMARY KEY,
LastModified BIGINT,
Hash BIGINT)
WITHOUT ROWID";
2021-10-23 16:51:17 +00:00
cmd.ExecuteNonQuery();
}
2021-09-27 12:42:46 +00:00
2022-10-01 05:21:58 +00:00
private async Task<(AbsolutePath Path, long LastModified, Hash Hash)> Get(AbsolutePath path)
2021-10-23 16:51:17 +00:00
{
using var cmd = new SQLiteCommand(_conn);
cmd.CommandText = "SELECT LastModified, Hash FROM HashCache WHERE Path = @path";
2022-08-22 15:34:19 +00:00
cmd.Parameters.AddWithValue("@path", path.ToString().ToLowerInvariant());
2022-10-01 05:21:58 +00:00
await cmd.PrepareAsync();
2021-09-27 12:42:46 +00:00
2022-10-01 05:21:58 +00:00
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) return (path, reader.GetInt64(0), Hash.FromLong(reader.GetInt64(1)));
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
return default;
}
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
public void Purge(AbsolutePath path)
{
using var cmd = new SQLiteCommand(_conn);
cmd.CommandText = "DELETE FROM HashCache WHERE Path = @path";
2022-08-22 15:34:19 +00:00
cmd.Parameters.AddWithValue("@path", path.ToString().ToLowerInvariant());
2021-10-23 16:51:17 +00:00
cmd.PrepareAsync();
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
cmd.ExecuteNonQuery();
}
2021-09-27 12:42:46 +00:00
2022-10-01 05:21:58 +00:00
private async Task Upsert(AbsolutePath path, long lastModified, Hash hash)
2021-10-23 16:51:17 +00:00
{
2022-10-01 05:21:58 +00:00
await using var cmd = new SQLiteCommand(_conn);
2021-10-23 16:51:17 +00:00
cmd.CommandText = @"INSERT INTO HashCache (Path, LastModified, Hash) VALUES (@path, @lastModified, @hash)
2021-09-27 12:42:46 +00:00
ON CONFLICT(Path) DO UPDATE SET LastModified = @lastModified, Hash = @hash";
2022-08-22 15:34:19 +00:00
cmd.Parameters.AddWithValue("@path", path.ToString().ToLowerInvariant());
2021-10-23 16:51:17 +00:00
cmd.Parameters.AddWithValue("@lastModified", lastModified);
cmd.Parameters.AddWithValue("@hash", (long) hash);
2022-10-01 05:21:58 +00:00
await cmd.PrepareAsync();
2021-09-27 12:42:46 +00:00
2022-10-01 05:21:58 +00:00
await cmd.ExecuteNonQueryAsync();
2021-10-23 16:51:17 +00:00
}
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
public void VacuumDatabase()
{
using var cmd = new SQLiteCommand(_conn);
cmd.CommandText = @"VACUUM";
cmd.PrepareAsync();
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
cmd.ExecuteNonQuery();
}
2021-09-27 12:42:46 +00:00
2022-10-01 05:21:58 +00:00
public async Task<Hash> TryGetHashCache(AbsolutePath file)
2021-10-23 16:51:17 +00:00
{
2022-10-01 05:21:58 +00:00
if (!file.FileExists()) return default;
2021-09-27 12:42:46 +00:00
2022-10-01 05:21:58 +00:00
var result = await Get(file);
if (result == default || result.Hash == default)
2022-10-01 05:21:58 +00:00
return default;
2022-10-30 13:16:12 +00:00
// Fix for strange issue where dates are messed up on some systems
if (file.LastModifiedUtc() < file.CreatedUtc())
file.Touch();
2021-09-27 12:42:46 +00:00
2021-10-23 16:51:17 +00:00
if (result.LastModified == file.LastModifiedUtc().ToFileTimeUtc())
2021-09-27 12:42:46 +00:00
{
2022-10-01 05:21:58 +00:00
return result.Hash;
2021-09-27 12:42:46 +00:00
}
2021-10-23 16:51:17 +00:00
Purge(file);
2022-10-01 05:21:58 +00:00
return default;
2021-10-23 16:51:17 +00:00
}
2021-09-27 12:42:46 +00:00
2022-10-01 05:21:58 +00:00
private async Task WriteHashCache(AbsolutePath file, Hash hash)
2021-10-23 16:51:17 +00:00
{
if (!file.FileExists()) return;
2022-10-01 05:21:58 +00:00
await Upsert(file, file.LastModifiedUtc().ToFileTimeUtc(), hash);
2021-10-23 16:51:17 +00:00
}
2022-10-01 05:21:58 +00:00
public async Task FileHashWriteCache(AbsolutePath file, Hash hash)
2021-10-23 16:51:17 +00:00
{
2022-10-01 05:21:58 +00:00
await WriteHashCache(file, hash);
2021-10-23 16:51:17 +00:00
}
public async Task<Hash> FileHashCachedAsync(AbsolutePath file, CancellationToken token)
{
2022-10-01 05:21:58 +00:00
var hash = await TryGetHashCache(file);
if (hash != default) return hash;
2021-10-23 16:51:17 +00:00
2022-01-06 04:12:40 +00:00
using var job = await _limiter.Begin($"Hashing {file.FileName}", file.Size(), token);
2021-10-23 16:51:17 +00:00
await using var fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
2022-10-01 05:21:58 +00:00
hash = await fs.HashingCopy(Stream.Null, token, job);
2021-10-23 16:51:17 +00:00
if (hash != default)
2022-10-01 05:21:58 +00:00
await WriteHashCache(file, hash);
2021-10-23 16:51:17 +00:00
return hash;
2021-09-27 12:42:46 +00:00
}
}