2021-09-27 12:42:46 +00:00
|
|
|
using System;
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
using System.Data;
|
|
|
|
using System.Data.SQLite;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
2022-06-22 01:38:42 +00:00
|
|
|
using System.Threading;
|
2021-09-27 12:42:46 +00:00
|
|
|
using System.Threading.Tasks;
|
|
|
|
using Wabbajack.Common;
|
|
|
|
using Wabbajack.DTOs.Streams;
|
2022-06-22 01:38:42 +00:00
|
|
|
using Wabbajack.DTOs.Vfs;
|
2021-09-27 12:42:46 +00:00
|
|
|
using Wabbajack.Hashing.xxHash64;
|
|
|
|
using Wabbajack.Paths;
|
|
|
|
using Wabbajack.Paths.IO;
|
2022-06-22 01:38:42 +00:00
|
|
|
using Wabbajack.VFS.Interfaces;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
namespace Wabbajack.VFS;
|
|
|
|
|
2022-06-22 01:38:42 +00:00
|
|
|
public class VFSDiskCache : IVfsCache
|
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 AbsolutePath _path;
|
|
|
|
|
2022-06-22 01:38:42 +00:00
|
|
|
public VFSDiskCache(AbsolutePath path)
|
2021-09-27 12:42:46 +00:00
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
_path = path;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
if (!_path.Parent.DirectoryExists())
|
|
|
|
_path.Parent.CreateDirectory();
|
|
|
|
|
|
|
|
_connectionString = string.Intern($"URI=file:{path};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 VFSCache (
|
2021-09-27 12:42:46 +00:00
|
|
|
Hash BIGINT PRIMARY KEY,
|
|
|
|
Contents BLOB)
|
|
|
|
WITHOUT ROWID";
|
2021-10-23 16:51:17 +00:00
|
|
|
cmd.ExecuteNonQuery();
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
2022-07-11 20:55:54 +00:00
|
|
|
public async Task<IndexedVirtualFile?> Get(Hash hash, IStreamFactory sfn, CancellationToken token)
|
2021-10-23 16:51:17 +00:00
|
|
|
{
|
2022-06-08 03:48:13 +00:00
|
|
|
if (hash == default)
|
|
|
|
throw new ArgumentException("Cannot cache default hashes");
|
2022-06-22 01:38:42 +00:00
|
|
|
|
|
|
|
await using var cmd = new SQLiteCommand(_conn);
|
2021-10-23 16:51:17 +00:00
|
|
|
cmd.CommandText = @"SELECT Contents FROM VFSCache WHERE Hash = @hash";
|
|
|
|
cmd.Parameters.AddWithValue("@hash", (long) hash);
|
|
|
|
|
2022-08-22 15:34:19 +00:00
|
|
|
await using var rdr = await cmd.ExecuteReaderAsync(token);
|
|
|
|
while (await rdr.ReadAsync(token))
|
2021-09-27 12:42:46 +00:00
|
|
|
{
|
2022-06-22 01:38:42 +00:00
|
|
|
var data = IndexedVirtualFileExtensions.Read(rdr.GetStream(0));
|
|
|
|
return data;
|
2021-09-27 12:42:46 +00:00
|
|
|
}
|
|
|
|
|
2022-06-22 01:38:42 +00:00
|
|
|
return null;
|
2021-10-23 16:51:17 +00:00
|
|
|
}
|
2022-06-22 01:38:42 +00:00
|
|
|
|
|
|
|
public async Task Put(IndexedVirtualFile ivf, CancellationToken token)
|
2021-10-23 16:51:17 +00:00
|
|
|
{
|
|
|
|
await using var ms = new MemoryStream();
|
|
|
|
// Top level path gets renamed when read, we don't want the absolute path
|
|
|
|
// here else the reader will blow up when it tries to convert the value
|
|
|
|
ivf.Name = (RelativePath) "not/applicable";
|
|
|
|
ivf.Write(ms);
|
|
|
|
ms.Position = 0;
|
2022-06-22 01:38:42 +00:00
|
|
|
await InsertIntoVFSCache(ivf.Hash, ms);
|
2021-10-23 16:51:17 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 23:59:29 +00:00
|
|
|
public async Task Clean()
|
|
|
|
{
|
|
|
|
await using var cmd = new SQLiteCommand(_conn);
|
|
|
|
cmd.CommandText = @"VACUUM";
|
|
|
|
await cmd.PrepareAsync();
|
|
|
|
|
|
|
|
await cmd.ExecuteNonQueryAsync();
|
|
|
|
}
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
private async Task InsertIntoVFSCache(Hash hash, MemoryStream data)
|
|
|
|
{
|
|
|
|
await using var cmd = new SQLiteCommand(_conn);
|
|
|
|
cmd.CommandText = @"INSERT INTO VFSCache (Hash, Contents) VALUES (@hash, @contents)";
|
|
|
|
cmd.Parameters.AddWithValue("@hash", (long) hash);
|
|
|
|
var val = new SQLiteParameter("@contents", DbType.Binary) {Value = data.ToArray()};
|
|
|
|
cmd.Parameters.Add(val);
|
|
|
|
try
|
2021-09-27 12:42:46 +00:00
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
await cmd.ExecuteNonQueryAsync();
|
2021-09-27 12:42:46 +00:00
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
catch (SQLiteException ex)
|
2021-09-27 12:42:46 +00:00
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
if (ex.Message.StartsWith("constraint failed"))
|
|
|
|
return;
|
|
|
|
throw;
|
2021-09-27 12:42:46 +00:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
}
|