wabbajack/Wabbajack.VFS/VFSCache.cs
2021-10-23 10:51:17 -06:00

124 lines
3.8 KiB
C#

using System;
using System.Collections.Immutable;
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.DTOs.Streams;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
namespace Wabbajack.VFS;
public class VFSCache
{
private readonly SQLiteConnection _conn;
private readonly string _connectionString;
private readonly AbsolutePath _path;
public VFSCache(AbsolutePath path)
{
_path = path;
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 (
Hash BIGINT PRIMARY KEY,
Contents BLOB)
WITHOUT ROWID";
cmd.ExecuteNonQuery();
}
public bool TryGetFromCache(Context context, VirtualFile parent, IPath path, IStreamFactory extractedFile,
Hash hash, out VirtualFile found)
{
using var cmd = new SQLiteCommand(_conn);
cmd.CommandText = @"SELECT Contents FROM VFSCache WHERE Hash = @hash";
cmd.Parameters.AddWithValue("@hash", (long) hash);
using var rdr = cmd.ExecuteReader();
while (rdr.Read())
{
var data = IndexedVirtualFile.Read(rdr.GetStream(0));
found = ConvertFromIndexedFile(context, data, path, parent, extractedFile);
found.Name = path;
found.Hash = hash;
return true;
}
found = default;
return false;
}
private static VirtualFile ConvertFromIndexedFile(Context context, IndexedVirtualFile file, IPath path,
VirtualFile vparent, IStreamFactory extractedFile)
{
var vself = new VirtualFile
{
Context = context,
Name = path,
Parent = vparent,
Size = file.Size,
LastModified = extractedFile.LastModifiedUtc.AsUnixTime(),
LastAnalyzed = DateTime.Now.AsUnixTime(),
Hash = file.Hash,
ImageState = file.ImageState
};
vself.FillFullPath();
vself.Children = file.Children.Select(f => ConvertFromIndexedFile(context, f, f.Name, vself, extractedFile))
.ToImmutableList();
return vself;
}
public async Task WriteToCache(VirtualFile self)
{
await using var ms = new MemoryStream();
var ivf = self.ToIndexedVirtualFile();
// 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;
await InsertIntoVFSCache(self.Hash, ms);
}
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
{
await cmd.ExecuteNonQueryAsync();
}
catch (SQLiteException ex)
{
if (ex.Message.StartsWith("constraint failed"))
return;
throw;
}
}
public void VacuumDatabase()
{
using var cmd = new SQLiteCommand(_conn);
cmd.CommandText = @"VACUUM";
cmd.PrepareAsync();
cmd.ExecuteNonQuery();
}
}