pinakes-core: add error context to tag and collection writes; map serde_json errors to Serialization variant
pinakes-core: distinguish task panics from cancellations in import error handling Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Icf5686f34144630ebf1935c47b3979156a6a6964
This commit is contained in:
parent
dda84d148c
commit
b89c7a5dc5
3 changed files with 65 additions and 20 deletions
|
|
@ -498,10 +498,14 @@ fn collect_import_result(
|
|||
tracing::warn!(path = %path.display(), error = %e, "failed to import file");
|
||||
results.push(Err(e));
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!(error = %e, "import task panicked");
|
||||
Err(join_err) => {
|
||||
if join_err.is_panic() {
|
||||
tracing::error!(error = %join_err, "import task panicked");
|
||||
} else {
|
||||
tracing::warn!(error = %join_err, "import task was cancelled");
|
||||
}
|
||||
results.push(Err(PinakesError::InvalidOperation(format!(
|
||||
"import task panicked: {e}"
|
||||
"import task failed: {join_err}"
|
||||
))));
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4295,6 +4295,11 @@ impl StorageBackend for PostgresBackend {
|
|||
&self,
|
||||
metadata: &crate::model::BookMetadata,
|
||||
) -> Result<()> {
|
||||
if metadata.media_id.0.is_nil() {
|
||||
return Err(PinakesError::Database(
|
||||
"upsert_book_metadata: media_id must not be nil".to_string(),
|
||||
));
|
||||
}
|
||||
let mut client = self
|
||||
.pool
|
||||
.get()
|
||||
|
|
|
|||
|
|
@ -1116,7 +1116,8 @@ impl StorageBackend for SqliteBackend {
|
|||
parent_id.map(|p| p.to_string()),
|
||||
now.to_rfc3339(),
|
||||
],
|
||||
)?;
|
||||
)
|
||||
.map_err(crate::error::db_ctx("create_tag", &name))?;
|
||||
drop(db);
|
||||
Tag {
|
||||
id,
|
||||
|
|
@ -1192,7 +1193,8 @@ impl StorageBackend for SqliteBackend {
|
|||
.lock()
|
||||
.map_err(|e| PinakesError::Database(e.to_string()))?;
|
||||
let changed = db
|
||||
.execute("DELETE FROM tags WHERE id = ?1", params![id.to_string()])?;
|
||||
.execute("DELETE FROM tags WHERE id = ?1", params![id.to_string()])
|
||||
.map_err(crate::error::db_ctx("delete_tag", id))?;
|
||||
drop(db);
|
||||
if changed == 0 {
|
||||
return Err(PinakesError::TagNotFound(id.to_string()));
|
||||
|
|
@ -1214,7 +1216,11 @@ impl StorageBackend for SqliteBackend {
|
|||
db.execute(
|
||||
"INSERT OR IGNORE INTO media_tags (media_id, tag_id) VALUES (?1, ?2)",
|
||||
params![media_id.0.to_string(), tag_id.to_string()],
|
||||
)?;
|
||||
)
|
||||
.map_err(crate::error::db_ctx(
|
||||
"tag_media",
|
||||
format!("{media_id} x {tag_id}"),
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
|
@ -1232,7 +1238,11 @@ impl StorageBackend for SqliteBackend {
|
|||
db.execute(
|
||||
"DELETE FROM media_tags WHERE media_id = ?1 AND tag_id = ?2",
|
||||
params![media_id.0.to_string(), tag_id.to_string()],
|
||||
)?;
|
||||
)
|
||||
.map_err(crate::error::db_ctx(
|
||||
"untag_media",
|
||||
format!("{media_id} x {tag_id}"),
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
|
@ -1323,7 +1333,8 @@ impl StorageBackend for SqliteBackend {
|
|||
now.to_rfc3339(),
|
||||
now.to_rfc3339(),
|
||||
],
|
||||
)?;
|
||||
)
|
||||
.map_err(crate::error::db_ctx("create_collection", &name))?;
|
||||
drop(db);
|
||||
Collection {
|
||||
id,
|
||||
|
|
@ -1406,7 +1417,8 @@ impl StorageBackend for SqliteBackend {
|
|||
let changed = db
|
||||
.execute("DELETE FROM collections WHERE id = ?1", params![
|
||||
id.to_string()
|
||||
])?;
|
||||
])
|
||||
.map_err(crate::error::db_ctx("delete_collection", id))?;
|
||||
drop(db);
|
||||
if changed == 0 {
|
||||
return Err(PinakesError::CollectionNotFound(id.to_string()));
|
||||
|
|
@ -1440,7 +1452,11 @@ impl StorageBackend for SqliteBackend {
|
|||
position,
|
||||
now.to_rfc3339(),
|
||||
],
|
||||
)?;
|
||||
)
|
||||
.map_err(crate::error::db_ctx(
|
||||
"add_to_collection",
|
||||
format!("{collection_id} <- {media_id}"),
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
|
@ -1463,7 +1479,11 @@ impl StorageBackend for SqliteBackend {
|
|||
"DELETE FROM collection_members WHERE collection_id = ?1 AND \
|
||||
media_id = ?2",
|
||||
params![collection_id.to_string(), media_id.0.to_string()],
|
||||
)?;
|
||||
)
|
||||
.map_err(crate::error::db_ctx(
|
||||
"remove_from_collection",
|
||||
format!("{collection_id} <- {media_id}"),
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
|
@ -1863,20 +1883,27 @@ impl StorageBackend for SqliteBackend {
|
|||
let db = conn
|
||||
.lock()
|
||||
.map_err(|e| PinakesError::Database(e.to_string()))?;
|
||||
let tx = db.unchecked_transaction()?;
|
||||
let ctx = format!("{} media x {} tags", media_ids.len(), tag_ids.len());
|
||||
let tx = db
|
||||
.unchecked_transaction()
|
||||
.map_err(crate::error::db_ctx("batch_tag_media", &ctx))?;
|
||||
// Prepare statement once for reuse
|
||||
let mut stmt = tx.prepare_cached(
|
||||
"INSERT OR IGNORE INTO media_tags (media_id, tag_id) VALUES (?1, ?2)",
|
||||
)?;
|
||||
)
|
||||
.map_err(crate::error::db_ctx("batch_tag_media", &ctx))?;
|
||||
let mut count = 0u64;
|
||||
for mid in &media_ids {
|
||||
for tid in &tag_ids {
|
||||
let rows = stmt.execute(params![mid, tid])?;
|
||||
let rows = stmt
|
||||
.execute(params![mid, tid])
|
||||
.map_err(crate::error::db_ctx("batch_tag_media", &ctx))?;
|
||||
count += rows as u64; // INSERT OR IGNORE: rows=1 if new, 0 if existed
|
||||
}
|
||||
}
|
||||
drop(stmt);
|
||||
tx.commit()?;
|
||||
tx.commit()
|
||||
.map_err(crate::error::db_ctx("batch_tag_media", &ctx))?;
|
||||
count
|
||||
};
|
||||
Ok(count)
|
||||
|
|
@ -2695,7 +2722,7 @@ impl StorageBackend for SqliteBackend {
|
|||
let id_str = id.0.to_string();
|
||||
let now = chrono::Utc::now();
|
||||
let role_str = serde_json::to_string(&role).map_err(|e| {
|
||||
PinakesError::Database(format!("failed to serialize role: {e}"))
|
||||
PinakesError::Serialization(format!("failed to serialize role: {e}"))
|
||||
})?;
|
||||
|
||||
tx.execute(
|
||||
|
|
@ -2714,7 +2741,7 @@ impl StorageBackend for SqliteBackend {
|
|||
let user_profile = if let Some(prof) = profile.clone() {
|
||||
let prefs_json =
|
||||
serde_json::to_string(&prof.preferences).map_err(|e| {
|
||||
PinakesError::Database(format!(
|
||||
PinakesError::Serialization(format!(
|
||||
"failed to serialize preferences: {e}"
|
||||
))
|
||||
})?;
|
||||
|
|
@ -2796,7 +2823,9 @@ impl StorageBackend for SqliteBackend {
|
|||
if let Some(ref r) = role {
|
||||
updates.push("role = ?");
|
||||
let role_str = serde_json::to_string(r).map_err(|e| {
|
||||
PinakesError::Database(format!("failed to serialize role: {e}"))
|
||||
PinakesError::Serialization(format!(
|
||||
"failed to serialize role: {e}"
|
||||
))
|
||||
})?;
|
||||
params.push(Box::new(role_str));
|
||||
}
|
||||
|
|
@ -2814,7 +2843,7 @@ impl StorageBackend for SqliteBackend {
|
|||
if let Some(prof) = profile {
|
||||
let prefs_json =
|
||||
serde_json::to_string(&prof.preferences).map_err(|e| {
|
||||
PinakesError::Database(format!(
|
||||
PinakesError::Serialization(format!(
|
||||
"failed to serialize preferences: {e}"
|
||||
))
|
||||
})?;
|
||||
|
|
@ -2966,7 +2995,9 @@ impl StorageBackend for SqliteBackend {
|
|||
PinakesError::Database(format!("failed to acquire database lock: {e}"))
|
||||
})?;
|
||||
let perm_str = serde_json::to_string(&permission).map_err(|e| {
|
||||
PinakesError::Database(format!("failed to serialize permission: {e}"))
|
||||
PinakesError::Serialization(format!(
|
||||
"failed to serialize permission: {e}"
|
||||
))
|
||||
})?;
|
||||
let now = chrono::Utc::now();
|
||||
db.execute(
|
||||
|
|
@ -5055,6 +5086,11 @@ impl StorageBackend for SqliteBackend {
|
|||
&self,
|
||||
metadata: &crate::model::BookMetadata,
|
||||
) -> Result<()> {
|
||||
if metadata.media_id.0.is_nil() {
|
||||
return Err(PinakesError::Database(
|
||||
"upsert_book_metadata: media_id must not be nil".to_string(),
|
||||
));
|
||||
}
|
||||
let conn = Arc::clone(&self.conn);
|
||||
let media_id_str = metadata.media_id.to_string();
|
||||
let isbn = metadata.isbn.clone();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue