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;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using Wabbajack.Common;
|
|
|
|
using Wabbajack.DTOs.Streams;
|
|
|
|
using Wabbajack.Hashing.xxHash64;
|
|
|
|
using Wabbajack.Paths;
|
|
|
|
using Wabbajack.Paths.IO;
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
namespace Wabbajack.VFS;
|
|
|
|
|
|
|
|
public class VFSCache
|
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;
|
|
|
|
|
|
|
|
public VFSCache(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
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
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())
|
2021-09-27 12:42:46 +00:00
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
var data = IndexedVirtualFile.Read(rdr.GetStream(0));
|
|
|
|
found = ConvertFromIndexedFile(context, data, path, parent, extractedFile);
|
|
|
|
found.Name = path;
|
|
|
|
found.Hash = hash;
|
|
|
|
return true;
|
2021-09-27 12:42:46 +00:00
|
|
|
}
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
found = default;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static VirtualFile ConvertFromIndexedFile(Context context, IndexedVirtualFile file, IPath path,
|
|
|
|
VirtualFile vparent, IStreamFactory extractedFile)
|
|
|
|
{
|
|
|
|
var vself = new VirtualFile
|
2021-09-27 12:42:46 +00:00
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
Context = context,
|
|
|
|
Name = path,
|
|
|
|
Parent = vparent,
|
|
|
|
Size = file.Size,
|
|
|
|
LastModified = extractedFile.LastModifiedUtc.AsUnixTime(),
|
|
|
|
LastAnalyzed = DateTime.Now.AsUnixTime(),
|
|
|
|
Hash = file.Hash,
|
|
|
|
ImageState = file.ImageState
|
|
|
|
};
|
2021-09-27 12:42:46 +00:00
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
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
|
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
|
|
|
}
|
|
|
|
}
|