fix(storage): reclaim transfer cache and track received files

This commit is contained in:
2026-07-22 16:03:37 +02:00
parent 627c205853
commit b46c5e7d72
46 changed files with 1229 additions and 124 deletions

View File

@@ -15,12 +15,12 @@ use uuid::Uuid;
use crate::{
access_policy::mode_from_storage,
api::{CoreEvent, ReceiverRequest, StoredTransfer},
api::{CoreEvent, ReceivedArtifact, ReceivedLocatorKind, ReceiverRequest, StoredTransfer},
transfer_state::{ReceiverRequestStatus, TransferDirection, TransferStatus},
util::now_ms,
};
const SCHEMA_VERSION: i64 = 4;
const SCHEMA_VERSION: i64 = 5;
#[derive(Debug, Clone)]
pub(crate) struct Repository {
@@ -47,6 +47,7 @@ pub(crate) struct TransferUpsert<'a> {
#[derive(Debug, Clone)]
pub(crate) struct PersistedShare {
pub(crate) transfer_id: u64,
pub(crate) local_id: String,
pub(crate) content_hash: String,
pub(crate) access_mode: String,
}
@@ -58,6 +59,15 @@ pub(crate) struct RecoveredTransfer {
pub(crate) previous_status: TransferStatus,
}
pub(crate) struct ReceivedArtifactInsert<'a> {
pub(crate) transfer_local_id: &'a str,
pub(crate) protocol_transfer_id: u64,
pub(crate) relative_path: &'a str,
pub(crate) locator_kind: ReceivedLocatorKind,
pub(crate) locator: &'a str,
pub(crate) logical_size: u64,
}
pub(crate) struct ReceiverRequestInsert<'a> {
pub(crate) id: &'a str,
pub(crate) transfer_id: u64,
@@ -169,6 +179,24 @@ impl Repository {
.execute(&self.pool)
.await?;
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS received_artifacts (
id TEXT PRIMARY KEY,
transfer_local_id TEXT NOT NULL,
protocol_transfer_id INTEGER NOT NULL,
relative_path TEXT NOT NULL,
locator_kind TEXT NOT NULL,
locator TEXT NOT NULL,
logical_size INTEGER NOT NULL,
published_at INTEGER NOT NULL,
UNIQUE(transfer_local_id, relative_path)
);
"#,
)
.execute(&self.pool)
.await?;
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS transfer_events (
@@ -330,6 +358,71 @@ impl Repository {
Ok(())
}
pub(crate) async fn transfer_local_id(&self, transfer_id: u64) -> Result<String> {
let row = sqlx::query("SELECT local_id FROM transfers WHERE transfer_id = ?1")
.bind(to_db_id(transfer_id)?)
.fetch_one(&self.pool)
.await?;
Ok(row.get(0))
}
pub(crate) async fn record_received_artifact(
&self,
artifact: ReceivedArtifactInsert<'_>,
) -> Result<()> {
sqlx::query(
r#"
INSERT INTO received_artifacts (
id, transfer_local_id, protocol_transfer_id, relative_path,
locator_kind, locator, logical_size, published_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
ON CONFLICT(transfer_local_id, relative_path) DO UPDATE SET
locator_kind = excluded.locator_kind,
locator = excluded.locator,
logical_size = excluded.logical_size,
published_at = excluded.published_at
"#,
)
.bind(Uuid::new_v4().to_string())
.bind(artifact.transfer_local_id)
.bind(to_db_id(artifact.protocol_transfer_id)?)
.bind(artifact.relative_path)
.bind(locator_kind_to_storage(&artifact.locator_kind))
.bind(artifact.locator)
.bind(to_db_id(artifact.logical_size)?)
.bind(now_ms())
.execute(&self.pool)
.await?;
Ok(())
}
pub(crate) async fn list_received_artifacts(&self) -> Result<Vec<ReceivedArtifact>> {
let rows = sqlx::query(
r#"
SELECT id, transfer_local_id, protocol_transfer_id, relative_path,
locator_kind, locator, logical_size, published_at
FROM received_artifacts
ORDER BY published_at DESC
"#,
)
.fetch_all(&self.pool)
.await?;
rows.into_iter()
.map(|row| {
Ok(ReceivedArtifact {
id: row.get("id"),
transfer_local_id: row.get("transfer_local_id"),
protocol_transfer_id: row.get::<i64, _>("protocol_transfer_id") as u64,
relative_path: row.get("relative_path"),
locator_kind: locator_kind_from_storage(&row.get::<String, _>("locator_kind"))?,
locator: row.get("locator"),
logical_size: row.get::<i64, _>("logical_size") as u64,
published_at: row.get("published_at"),
})
})
.collect()
}
pub(crate) async fn complete_share_import(&self, transfer: TransferUpsert<'_>) -> Result<()> {
self.maybe_fail_write()?;
if transfer.direction != TransferDirection::Send
@@ -508,7 +601,7 @@ impl Repository {
pub(crate) async fn list_active_shares(&self) -> Result<Vec<PersistedShare>> {
let rows = sqlx::query(
r#"
SELECT transfer_id, content_hash, access_mode
SELECT transfer_id, local_id, content_hash, access_mode
FROM transfers
WHERE direction = 'send'
AND status = 'sharing'
@@ -521,8 +614,9 @@ impl Repository {
.into_iter()
.map(|row| PersistedShare {
transfer_id: row.get::<i64, _>(0) as u64,
content_hash: row.get::<String, _>(1),
access_mode: row.get::<String, _>(2),
local_id: row.get::<String, _>(1),
content_hash: row.get::<String, _>(2),
access_mode: row.get::<String, _>(3),
})
.collect())
}
@@ -870,6 +964,23 @@ fn to_db_id(value: u64) -> Result<i64> {
i64::try_from(value).context("transfer id exceeds SQLite signed integer range")
}
fn locator_kind_to_storage(kind: &ReceivedLocatorKind) -> &'static str {
match kind {
ReceivedLocatorKind::FilesystemPath => "filesystem_path",
ReceivedLocatorKind::AndroidMediaStore => "android_media_store",
ReceivedLocatorKind::AndroidDocument => "android_document",
}
}
fn locator_kind_from_storage(value: &str) -> Result<ReceivedLocatorKind> {
match value {
"filesystem_path" => Ok(ReceivedLocatorKind::FilesystemPath),
"android_media_store" => Ok(ReceivedLocatorKind::AndroidMediaStore),
"android_document" => Ok(ReceivedLocatorKind::AndroidDocument),
_ => anyhow::bail!("unknown received artifact locator kind: {value}"),
}
}
fn row_to_transfer(row: sqlx::sqlite::SqliteRow) -> Result<StoredTransfer> {
let direction = row.get::<String, _>("direction");
let status = row.get::<String, _>("status");