Improved DHT publication. Adjust batching, added validation per key
Build and Publish / Build and Publish Docker Image (push) Successful in 4m9s
Build and Publish / Build and Publish Docker Image (push) Successful in 4m9s
This commit is contained in:
Generated
+2
-2
@@ -1718,7 +1718,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "federation-net"
|
name = "federation-net"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://gt.hexor.cy/ab/frid.git#aaaad780f2bc3f4ba42bd3eef6aca871a2ff68da"
|
source = "git+https://gt.hexor.cy/ab/frid.git#512a818a6a52ec713678e9a4e1cf0f50bb1e34ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake3",
|
"blake3",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
@@ -3622,7 +3622,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "music-dht"
|
name = "music-dht"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://gt.hexor.cy/ab/frid.git#aaaad780f2bc3f4ba42bd3eef6aca871a2ff68da"
|
source = "git+https://gt.hexor.cy/ab/frid.git#512a818a6a52ec713678e9a4e1cf0f50bb1e34ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"blake3",
|
"blake3",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "furumusic"
|
name = "furumusic"
|
||||||
version = "0.6.7-fd"
|
version = "0.7.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
||||||
|
|
||||||
|
|||||||
+69
-44
@@ -165,52 +165,21 @@ impl MusicDhtStorage for PostgresFederationStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn store_dht_record(&self, key: DhtKey, record: StoredRecord) -> music_dht::Result<bool> {
|
async fn store_dht_record(&self, key: DhtKey, record: StoredRecord) -> music_dht::Result<bool> {
|
||||||
let existing = sqlx::query(
|
let mut conn = self.pool.acquire().await.map_err(db_error)?;
|
||||||
"SELECT revision, deleted, expires_at_ms
|
store_record_in_conn(&mut conn, &key, &record).await
|
||||||
FROM furumusic__federation_dht_record
|
}
|
||||||
WHERE dht_key = $1 AND item_id = $2 AND owner_peer_id = $3",
|
|
||||||
)
|
|
||||||
.bind(key.as_bytes().as_slice())
|
|
||||||
.bind(record.item.id.as_bytes().as_slice())
|
|
||||||
.bind(record.item.owner.to_string())
|
|
||||||
.fetch_optional(&self.pool)
|
|
||||||
.await
|
|
||||||
.map_err(db_error)?
|
|
||||||
.map(|row| {
|
|
||||||
(
|
|
||||||
row.get::<i64, _>(0) as u64,
|
|
||||||
row.get::<bool, _>(1),
|
|
||||||
row.get::<i64, _>(2) as u64,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
match decide_store(existing, &record) {
|
async fn store_dht_records(
|
||||||
StoreDecision::Ignore => return Ok(false),
|
&self,
|
||||||
StoreDecision::Write | StoreDecision::RefreshExpiry(_) => {}
|
entries: Vec<(DhtKey, StoredRecord)>,
|
||||||
|
) -> music_dht::Result<Vec<bool>> {
|
||||||
|
let mut tx = self.pool.begin().await.map_err(db_error)?;
|
||||||
|
let mut stored = Vec::with_capacity(entries.len());
|
||||||
|
for (key, record) in &entries {
|
||||||
|
stored.push(store_record_in_conn(&mut tx, key, record).await?);
|
||||||
}
|
}
|
||||||
|
tx.commit().await.map_err(db_error)?;
|
||||||
let payload = postcard::to_stdvec(&record).map_err(db_error)?;
|
Ok(stored)
|
||||||
sqlx::query(
|
|
||||||
"INSERT INTO furumusic__federation_dht_record
|
|
||||||
(dht_key, item_id, owner_peer_id, payload, revision, deleted, expires_at_ms)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
||||||
ON CONFLICT (dht_key, item_id, owner_peer_id) DO UPDATE SET
|
|
||||||
payload = EXCLUDED.payload,
|
|
||||||
revision = EXCLUDED.revision,
|
|
||||||
deleted = EXCLUDED.deleted,
|
|
||||||
expires_at_ms = EXCLUDED.expires_at_ms",
|
|
||||||
)
|
|
||||||
.bind(key.as_bytes().as_slice())
|
|
||||||
.bind(record.item.id.as_bytes().as_slice())
|
|
||||||
.bind(record.item.owner.to_string())
|
|
||||||
.bind(payload)
|
|
||||||
.bind(record.item.revision as i64)
|
|
||||||
.bind(record.item.deleted)
|
|
||||||
.bind(record.expires_at_ms as i64)
|
|
||||||
.execute(&self.pool)
|
|
||||||
.await
|
|
||||||
.map_err(db_error)?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dht_records_by_key(
|
async fn dht_records_by_key(
|
||||||
@@ -319,6 +288,62 @@ fn secret_from_bytes(bytes: Vec<u8>) -> music_dht::Result<SecretKey> {
|
|||||||
Ok(SecretKey::from_bytes(&bytes))
|
Ok(SecretKey::from_bytes(&bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies one validated record following the revision/tombstone rules.
|
||||||
|
/// Returns `true` if the record was written or refreshed. Runs against a
|
||||||
|
/// pooled connection or an open transaction.
|
||||||
|
async fn store_record_in_conn(
|
||||||
|
conn: &mut sqlx::PgConnection,
|
||||||
|
key: &DhtKey,
|
||||||
|
record: &StoredRecord,
|
||||||
|
) -> music_dht::Result<bool> {
|
||||||
|
let existing = sqlx::query(
|
||||||
|
"SELECT revision, deleted, expires_at_ms
|
||||||
|
FROM furumusic__federation_dht_record
|
||||||
|
WHERE dht_key = $1 AND item_id = $2 AND owner_peer_id = $3",
|
||||||
|
)
|
||||||
|
.bind(key.as_bytes().as_slice())
|
||||||
|
.bind(record.item.id.as_bytes().as_slice())
|
||||||
|
.bind(record.item.owner.to_string())
|
||||||
|
.fetch_optional(&mut *conn)
|
||||||
|
.await
|
||||||
|
.map_err(db_error)?
|
||||||
|
.map(|row| {
|
||||||
|
(
|
||||||
|
row.get::<i64, _>(0) as u64,
|
||||||
|
row.get::<bool, _>(1),
|
||||||
|
row.get::<i64, _>(2) as u64,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
match decide_store(existing, record) {
|
||||||
|
StoreDecision::Ignore => return Ok(false),
|
||||||
|
StoreDecision::Write | StoreDecision::RefreshExpiry(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = postcard::to_stdvec(record).map_err(db_error)?;
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO furumusic__federation_dht_record
|
||||||
|
(dht_key, item_id, owner_peer_id, payload, revision, deleted, expires_at_ms)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
|
ON CONFLICT (dht_key, item_id, owner_peer_id) DO UPDATE SET
|
||||||
|
payload = EXCLUDED.payload,
|
||||||
|
revision = EXCLUDED.revision,
|
||||||
|
deleted = EXCLUDED.deleted,
|
||||||
|
expires_at_ms = EXCLUDED.expires_at_ms",
|
||||||
|
)
|
||||||
|
.bind(key.as_bytes().as_slice())
|
||||||
|
.bind(record.item.id.as_bytes().as_slice())
|
||||||
|
.bind(record.item.owner.to_string())
|
||||||
|
.bind(payload)
|
||||||
|
.bind(record.item.revision as i64)
|
||||||
|
.bind(record.item.deleted)
|
||||||
|
.bind(record.expires_at_ms as i64)
|
||||||
|
.execute(&mut *conn)
|
||||||
|
.await
|
||||||
|
.map_err(db_error)?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
fn db_error(err: impl std::fmt::Display) -> MusicDhtError {
|
fn db_error(err: impl std::fmt::Display) -> MusicDhtError {
|
||||||
MusicDhtError::Database(err.to_string())
|
MusicDhtError::Database(err.to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user