mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-07-31 09:17:11 -04:00
Add get_prune_table_queries centralized logic (#2268)
This commit is contained in:
committed by
Leendert de Borst
parent
e40b924f51
commit
3f71b90c1f
@@ -4,7 +4,7 @@ import { browser } from 'wxt/browser';
|
||||
import { TRASH_RETENTION_DAYS } from '@/utils/constants/vault';
|
||||
import { devLog } from '@/utils/DevLogger';
|
||||
|
||||
import init, { getSyncableTableNames, mergeVaults, pruneVault } from './dist/core/rust/aliasvault_core.js';
|
||||
import init, { getPruneTableQueries, getSyncableTableNames, mergeVaults, pruneVault } from './dist/core/rust/aliasvault_core.js';
|
||||
|
||||
/**
|
||||
* Record type for JSON data passed to/from Rust.
|
||||
@@ -268,22 +268,11 @@ export class VaultMergeService {
|
||||
const db = this.loadDatabase(SQL, vaultBase64);
|
||||
|
||||
try {
|
||||
/*
|
||||
* Read only the columns the Rust pruner inspects. Blob columns are reduced
|
||||
* to a 1-byte presence marker to avoid serializing megabytes of binary data
|
||||
* to JSON on every sync.
|
||||
*/
|
||||
const tableQueries: Record<string, string> = {
|
||||
Items: 'SELECT Id, IsDeleted, DeletedAt, LogoId FROM Items',
|
||||
FieldValues: 'SELECT ItemId, IsDeleted FROM FieldValues',
|
||||
Attachments: 'SELECT Id, ItemId, IsDeleted, substr(Blob, 1, 1) AS Blob FROM Attachments',
|
||||
TotpCodes: 'SELECT ItemId, IsDeleted FROM TotpCodes',
|
||||
Passkeys: 'SELECT ItemId, IsDeleted FROM Passkeys',
|
||||
Logos: 'SELECT Id, IsDeleted, substr(FileData, 1, 1) AS FileData FROM Logos',
|
||||
};
|
||||
// Get the per-table SELECT queries clients should run to build `PruneInput`.
|
||||
const tableQueries = getPruneTableQueries() as { name: string; query: string }[];
|
||||
|
||||
// Read tables as JSON
|
||||
const tables: TableData[] = Object.entries(tableQueries).map(([name, query]) => ({
|
||||
const tables: TableData[] = tableQueries.map(({ name, query }) => ({
|
||||
name,
|
||||
records: this.readQueryAsJson(db, query),
|
||||
}));
|
||||
|
||||
@@ -220,23 +220,15 @@ object VaultMergeService {
|
||||
val db = SQLiteDatabase.openDatabase(dbFile.absolutePath, null, SQLiteDatabase.OPEN_READWRITE)
|
||||
|
||||
try {
|
||||
// Read only the columns the Rust pruner inspects. Blob columns are reduced
|
||||
// to a 1-byte presence marker to avoid serializing large binary data to JSON.
|
||||
val pruneTableQueries = listOf(
|
||||
"Items" to "SELECT Id, IsDeleted, DeletedAt, LogoId FROM Items",
|
||||
"FieldValues" to "SELECT ItemId, IsDeleted FROM FieldValues",
|
||||
"Attachments" to "SELECT Id, ItemId, IsDeleted, substr(Blob, 1, 1) AS Blob FROM Attachments",
|
||||
"TotpCodes" to "SELECT ItemId, IsDeleted FROM TotpCodes",
|
||||
"Passkeys" to "SELECT ItemId, IsDeleted FROM Passkeys",
|
||||
"Logos" to "SELECT Id, IsDeleted, substr(FileData, 1, 1) AS FileData FROM Logos",
|
||||
)
|
||||
// Get the per-table SELECT queries clients should run to build `PruneInput`.
|
||||
val pruneTableQueries = uniffi.aliasvault_core.getPruneTableQueries()
|
||||
val tables = JSONArray()
|
||||
|
||||
for ((tableName, query) in pruneTableQueries) {
|
||||
for (tableQuery in pruneTableQueries) {
|
||||
tables.put(
|
||||
JSONObject().apply {
|
||||
put("name", tableName)
|
||||
put("records", readQuery(db, tableName, query))
|
||||
put("name", tableQuery.name)
|
||||
put("records", readQuery(db, tableQuery.name, tableQuery.query))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,22 +154,14 @@ public class VaultMergeService {
|
||||
let db = try openDatabase(from: decrypted)
|
||||
defer { sqlite3_close(db) }
|
||||
|
||||
// Read only the columns the Rust pruner inspects. Blob columns are reduced
|
||||
// to a 1-byte presence marker to avoid serializing large binary data to JSON.
|
||||
let pruneTableQueries: [(name: String, sql: String)] = [
|
||||
("Items", "SELECT Id, IsDeleted, DeletedAt, LogoId FROM Items"),
|
||||
("FieldValues", "SELECT ItemId, IsDeleted FROM FieldValues"),
|
||||
("Attachments", "SELECT Id, ItemId, IsDeleted, substr(Blob, 1, 1) AS Blob FROM Attachments"),
|
||||
("TotpCodes", "SELECT ItemId, IsDeleted FROM TotpCodes"),
|
||||
("Passkeys", "SELECT ItemId, IsDeleted FROM Passkeys"),
|
||||
("Logos", "SELECT Id, IsDeleted, substr(FileData, 1, 1) AS FileData FROM Logos")
|
||||
]
|
||||
// Get the per-table SELECT queries clients should run to build `PruneInput`.
|
||||
let pruneTableQueries = getPruneTableQueries()
|
||||
var tables: [[String: Any]] = []
|
||||
|
||||
for entry in pruneTableQueries {
|
||||
tables.append([
|
||||
"name": entry.name,
|
||||
"records": try readQuery(from: db, tableName: entry.name, sql: entry.sql)
|
||||
"records": try readQuery(from: db, tableName: entry.name, sql: entry.query)
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -1055,27 +1055,20 @@ public sealed class DbService : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads only the columns the Rust pruner inspects. Blob columns are reduced to a
|
||||
/// 1-byte presence marker to avoid serializing large binary data to JSON.
|
||||
/// Get the per-table SELECT queries clients should run to build `PruneInput`.
|
||||
/// </summary>
|
||||
/// <param name="connection">The SQLite connection to read from.</param>
|
||||
/// <returns>List of TableData objects containing the trimmed table records.</returns>
|
||||
private async Task<List<TableData>> ReadPruneTablesAsJsonAsync(SqliteConnection connection)
|
||||
{
|
||||
var tableQueries = new (string Name, string Query)[]
|
||||
{
|
||||
("Items", "SELECT Id, IsDeleted, DeletedAt, LogoId FROM Items"),
|
||||
("FieldValues", "SELECT ItemId, IsDeleted FROM FieldValues"),
|
||||
("Attachments", "SELECT Id, ItemId, IsDeleted, substr(Blob, 1, 1) AS Blob FROM Attachments"),
|
||||
("TotpCodes", "SELECT ItemId, IsDeleted FROM TotpCodes"),
|
||||
("Passkeys", "SELECT ItemId, IsDeleted FROM Passkeys"),
|
||||
("Logos", "SELECT Id, IsDeleted, substr(FileData, 1, 1) AS FileData FROM Logos"),
|
||||
};
|
||||
var tableQueries = await _rustCore.GetPruneTableQueriesAsync();
|
||||
|
||||
var tables = new List<TableData>();
|
||||
|
||||
foreach (var (tableName, query) in tableQueries)
|
||||
foreach (var tableQuery in tableQueries)
|
||||
{
|
||||
var tableName = tableQuery.Name;
|
||||
var query = tableQuery.Query;
|
||||
var tableData = new TableData { Name = tableName };
|
||||
|
||||
// Check if table exists in the database.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="PruneTableQuery.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Client.Services.JsInterop.RustCore;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// A per-table SELECT query for building prune input, defined in the Rust core.
|
||||
/// </summary>
|
||||
public class PruneTableQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the table name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SELECT query reading only the columns the pruner inspects.
|
||||
/// </summary>
|
||||
[JsonPropertyName("query")]
|
||||
public string Query { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -124,6 +124,28 @@ public class RustCoreService : IAsyncDisposable
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the per-table SELECT queries used to build prune input.
|
||||
/// </summary>
|
||||
/// <returns>List of table queries.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if WASM module is unavailable.</exception>
|
||||
public async Task<List<PruneTableQuery>> GetPruneTableQueriesAsync()
|
||||
{
|
||||
// Wait for WASM to be available with retries, as it may still be loading.
|
||||
if (!await WaitForAvailabilityAsync())
|
||||
{
|
||||
throw new InvalidOperationException("Rust WASM module is not available.");
|
||||
}
|
||||
|
||||
var result = await jsRuntime.InvokeAsync<List<PruneTableQuery>>("rustCoreGetPruneTableQueries");
|
||||
if (result == null || result.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get prune table queries from Rust WASM.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prune expired items from trash.
|
||||
/// Items that have been in trash (DeletedAt set) for longer than retentionDays
|
||||
|
||||
@@ -169,6 +169,24 @@ window.rustCoreGetSyncableTableNames = async function() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the per-table SELECT queries used to build prune input.
|
||||
* Blob columns are reduced to a 1-byte presence marker.
|
||||
* @returns {Promise<Array<{name: string, query: string}>>} Array of table queries.
|
||||
*/
|
||||
window.rustCoreGetPruneTableQueries = async function() {
|
||||
if (!await initRustCore()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
return wasmModule.getPruneTableQueries();
|
||||
} catch (error) {
|
||||
console.error('[RustCore] Get prune table queries failed:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract domain from URL.
|
||||
* @param {string} url - The URL to extract domain from.
|
||||
|
||||
@@ -151,6 +151,17 @@ pub extern "C" fn get_syncable_table_names_ffi() -> *mut c_char {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the per-table SELECT queries used to build prune input, as a JSON array
|
||||
/// of `{ "name": ..., "query": ... }` objects. Blob columns are reduced to a
|
||||
/// 1-byte presence marker to avoid serializing large binary data to JSON.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn get_prune_table_queries_ffi() -> *mut c_char {
|
||||
match serde_json::to_string(&crate::vault_pruner::get_prune_table_queries()) {
|
||||
Ok(json) => string_to_c_char(json),
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a string that was allocated by Rust.
|
||||
///
|
||||
/// # Safety
|
||||
|
||||
@@ -72,6 +72,14 @@ pub fn prune_vault_json(input_json: String) -> Result<String, VaultError> {
|
||||
crate::vault_pruner::prune_vault_json(&input_json)
|
||||
}
|
||||
|
||||
/// Get the per-table SELECT queries used to build prune input.
|
||||
/// Blob columns are reduced to a 1-byte presence marker to avoid
|
||||
/// serializing large binary data to JSON.
|
||||
#[uniffi::export]
|
||||
pub fn get_prune_table_queries() -> Vec<crate::vault_pruner::PruneTableQuery> {
|
||||
crate::vault_pruner::get_prune_table_queries()
|
||||
}
|
||||
|
||||
/// Filter credentials for autofill based on current URL/app and page title.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -304,7 +312,8 @@ mod tests {
|
||||
fn test_prune_vault_json() {
|
||||
let input = r#"{
|
||||
"tables": [{"name": "Items", "records": []}],
|
||||
"retention_days": 30
|
||||
"retention_days": 30,
|
||||
"current_time": "2024-01-15T10:30:00.000Z"
|
||||
}"#;
|
||||
|
||||
let result = prune_vault_json(input.to_string());
|
||||
|
||||
@@ -88,6 +88,38 @@ pub struct PruneOutput {
|
||||
pub stats: PruneStats,
|
||||
}
|
||||
|
||||
/// A per-table SELECT query for building `PruneInput`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
|
||||
pub struct PruneTableQuery {
|
||||
/// Table name
|
||||
pub name: String,
|
||||
/// SELECT query reading only the columns the pruner inspects
|
||||
pub query: String,
|
||||
}
|
||||
|
||||
/// Get the per-table SELECT queries clients should run to build `PruneInput`.
|
||||
///
|
||||
/// Only the columns the pruner inspects are selected; blob columns are reduced
|
||||
/// to a 1-byte presence marker (via `substr`) to avoid serializing large binary
|
||||
/// data to JSON on every prune.
|
||||
pub fn get_prune_table_queries() -> Vec<PruneTableQuery> {
|
||||
[
|
||||
("Items", "SELECT Id, IsDeleted, DeletedAt, LogoId FROM Items"),
|
||||
("FieldValues", "SELECT ItemId, IsDeleted FROM FieldValues"),
|
||||
("Attachments", "SELECT Id, ItemId, IsDeleted, substr(Blob, 1, 1) AS Blob FROM Attachments"),
|
||||
("TotpCodes", "SELECT ItemId, IsDeleted FROM TotpCodes"),
|
||||
("Passkeys", "SELECT ItemId, IsDeleted FROM Passkeys"),
|
||||
("Logos", "SELECT Id, IsDeleted, substr(FileData, 1, 1) AS FileData FROM Logos"),
|
||||
]
|
||||
.iter()
|
||||
.map(|(name, query)| PruneTableQuery {
|
||||
name: (*name).to_string(),
|
||||
query: (*query).to_string(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Main entry point: prune expired items from trash.
|
||||
///
|
||||
/// This function finds all Items with DeletedAt set that are older than
|
||||
|
||||
@@ -99,6 +99,16 @@ pub fn prune_vault_json_js(input_json: &str) -> Result<String, JsValue> {
|
||||
.map_err(|e| JsValue::from_str(&format!("Prune failed: {}", e)))
|
||||
}
|
||||
|
||||
/// Get the per-table SELECT queries used to build prune input.
|
||||
///
|
||||
/// Returns an array of `{ name, query }` objects. Blob columns are reduced to a
|
||||
/// 1-byte presence marker to avoid serializing large binary data to JSON.
|
||||
#[wasm_bindgen(js_name = getPruneTableQueries)]
|
||||
pub fn get_prune_table_queries_js() -> Result<JsValue, JsValue> {
|
||||
serde_wasm_bindgen::to_value(&crate::vault_pruner::get_prune_table_queries())
|
||||
.map_err(|e| JsValue::from_str(&format!("Failed to serialize output: {}", e)))
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// Credential Matcher WASM Bindings
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user