GUI plugins #9
3 changed files with 65 additions and 20 deletions
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
commit
592a9bcc47
|
|
@ -498,10 +498,14 @@ fn collect_import_result(
|
||||||
tracing::warn!(path = %path.display(), error = %e, "failed to import file");
|
tracing::warn!(path = %path.display(), error = %e, "failed to import file");
|
||||||
results.push(Err(e));
|
results.push(Err(e));
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(join_err) => {
|
||||||
tracing::error!(error = %e, "import task panicked");
|
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!(
|
results.push(Err(PinakesError::InvalidOperation(format!(
|
||||||
"import task panicked: {e}"
|
"import task failed: {join_err}"
|
||||||
))));
|
))));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4295,6 +4295,11 @@ impl StorageBackend for PostgresBackend {
|
||||||
&self,
|
&self,
|
||||||
metadata: &crate::model::BookMetadata,
|
metadata: &crate::model::BookMetadata,
|
||||||
) -> Result<()> {
|
) -> 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
|
let mut client = self
|
||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
|
|
|
||||||
|
|
@ -1116,7 +1116,8 @@ impl StorageBackend for SqliteBackend {
|
||||||
parent_id.map(|p| p.to_string()),
|
parent_id.map(|p| p.to_string()),
|
||||||
now.to_rfc3339(),
|
now.to_rfc3339(),
|
||||||
],
|
],
|
||||||
)?;
|
)
|
||||||
|
.map_err(crate::error::db_ctx("create_tag", &name))?;
|
||||||
drop(db);
|
drop(db);
|
||||||
Tag {
|
Tag {
|
||||||
id,
|
id,
|
||||||
|
|
@ -1192,7 +1193,8 @@ impl StorageBackend for SqliteBackend {
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|e| PinakesError::Database(e.to_string()))?;
|
.map_err(|e| PinakesError::Database(e.to_string()))?;
|
||||||
let changed = db
|
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);
|
drop(db);
|
||||||
if changed == 0 {
|
if changed == 0 {
|
||||||
return Err(PinakesError::TagNotFound(id.to_string()));
|
return Err(PinakesError::TagNotFound(id.to_string()));
|
||||||
|
|
@ -1214,7 +1216,11 @@ impl StorageBackend for SqliteBackend {
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT OR IGNORE INTO media_tags (media_id, tag_id) VALUES (?1, ?2)",
|
"INSERT OR IGNORE INTO media_tags (media_id, tag_id) VALUES (?1, ?2)",
|
||||||
params![media_id.0.to_string(), tag_id.to_string()],
|
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(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
@ -1232,7 +1238,11 @@ impl StorageBackend for SqliteBackend {
|
||||||
db.execute(
|
db.execute(
|
||||||
"DELETE FROM media_tags WHERE media_id = ?1 AND tag_id = ?2",
|
"DELETE FROM media_tags WHERE media_id = ?1 AND tag_id = ?2",
|
||||||
params![media_id.0.to_string(), tag_id.to_string()],
|
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(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
@ -1323,7 +1333,8 @@ impl StorageBackend for SqliteBackend {
|
||||||
now.to_rfc3339(),
|
now.to_rfc3339(),
|
||||||
now.to_rfc3339(),
|
now.to_rfc3339(),
|
||||||
],
|
],
|
||||||
)?;
|
)
|
||||||
|
.map_err(crate::error::db_ctx("create_collection", &name))?;
|
||||||
drop(db);
|
drop(db);
|
||||||
Collection {
|
Collection {
|
||||||
id,
|
id,
|
||||||
|
|
@ -1406,7 +1417,8 @@ impl StorageBackend for SqliteBackend {
|
||||||
let changed = db
|
let changed = db
|
||||||
.execute("DELETE FROM collections WHERE id = ?1", params![
|
.execute("DELETE FROM collections WHERE id = ?1", params![
|
||||||
id.to_string()
|
id.to_string()
|
||||||
])?;
|
])
|
||||||
|
.map_err(crate::error::db_ctx("delete_collection", id))?;
|
||||||
drop(db);
|
drop(db);
|
||||||
if changed == 0 {
|
if changed == 0 {
|
||||||
return Err(PinakesError::CollectionNotFound(id.to_string()));
|
return Err(PinakesError::CollectionNotFound(id.to_string()));
|
||||||
|
|
@ -1440,7 +1452,11 @@ impl StorageBackend for SqliteBackend {
|
||||||
position,
|
position,
|
||||||
now.to_rfc3339(),
|
now.to_rfc3339(),
|
||||||
],
|
],
|
||||||
)?;
|
)
|
||||||
|
.map_err(crate::error::db_ctx(
|
||||||
|
"add_to_collection",
|
||||||
|
format!("{collection_id} <- {media_id}"),
|
||||||
|
))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
@ -1463,7 +1479,11 @@ impl StorageBackend for SqliteBackend {
|
||||||
"DELETE FROM collection_members WHERE collection_id = ?1 AND \
|
"DELETE FROM collection_members WHERE collection_id = ?1 AND \
|
||||||
media_id = ?2",
|
media_id = ?2",
|
||||||
params![collection_id.to_string(), media_id.0.to_string()],
|
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(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
@ -1863,20 +1883,27 @@ impl StorageBackend for SqliteBackend {
|
||||||
let db = conn
|
let db = conn
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|e| PinakesError::Database(e.to_string()))?;
|
.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
|
// Prepare statement once for reuse
|
||||||
let mut stmt = tx.prepare_cached(
|
let mut stmt = tx.prepare_cached(
|
||||||
"INSERT OR IGNORE INTO media_tags (media_id, tag_id) VALUES (?1, ?2)",
|
"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;
|
let mut count = 0u64;
|
||||||
for mid in &media_ids {
|
for mid in &media_ids {
|
||||||
for tid in &tag_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
|
count += rows as u64; // INSERT OR IGNORE: rows=1 if new, 0 if existed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drop(stmt);
|
drop(stmt);
|
||||||
tx.commit()?;
|
tx.commit()
|
||||||
|
.map_err(crate::error::db_ctx("batch_tag_media", &ctx))?;
|
||||||
count
|
count
|
||||||
};
|
};
|
||||||
Ok(count)
|
Ok(count)
|
||||||
|
|
@ -2695,7 +2722,7 @@ impl StorageBackend for SqliteBackend {
|
||||||
let id_str = id.0.to_string();
|
let id_str = id.0.to_string();
|
||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
let role_str = serde_json::to_string(&role).map_err(|e| {
|
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(
|
tx.execute(
|
||||||
|
|
@ -2714,7 +2741,7 @@ impl StorageBackend for SqliteBackend {
|
||||||
let user_profile = if let Some(prof) = profile.clone() {
|
let user_profile = if let Some(prof) = profile.clone() {
|
||||||
let prefs_json =
|
let prefs_json =
|
||||||
serde_json::to_string(&prof.preferences).map_err(|e| {
|
serde_json::to_string(&prof.preferences).map_err(|e| {
|
||||||
PinakesError::Database(format!(
|
PinakesError::Serialization(format!(
|
||||||
"failed to serialize preferences: {e}"
|
"failed to serialize preferences: {e}"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -2796,7 +2823,9 @@ impl StorageBackend for SqliteBackend {
|
||||||
if let Some(ref r) = role {
|
if let Some(ref r) = role {
|
||||||
updates.push("role = ?");
|
updates.push("role = ?");
|
||||||
let role_str = serde_json::to_string(r).map_err(|e| {
|
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));
|
params.push(Box::new(role_str));
|
||||||
}
|
}
|
||||||
|
|
@ -2814,7 +2843,7 @@ impl StorageBackend for SqliteBackend {
|
||||||
if let Some(prof) = profile {
|
if let Some(prof) = profile {
|
||||||
let prefs_json =
|
let prefs_json =
|
||||||
serde_json::to_string(&prof.preferences).map_err(|e| {
|
serde_json::to_string(&prof.preferences).map_err(|e| {
|
||||||
PinakesError::Database(format!(
|
PinakesError::Serialization(format!(
|
||||||
"failed to serialize preferences: {e}"
|
"failed to serialize preferences: {e}"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -2966,7 +2995,9 @@ impl StorageBackend for SqliteBackend {
|
||||||
PinakesError::Database(format!("failed to acquire database lock: {e}"))
|
PinakesError::Database(format!("failed to acquire database lock: {e}"))
|
||||||
})?;
|
})?;
|
||||||
let perm_str = serde_json::to_string(&permission).map_err(|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();
|
let now = chrono::Utc::now();
|
||||||
db.execute(
|
db.execute(
|
||||||
|
|
@ -5055,6 +5086,11 @@ impl StorageBackend for SqliteBackend {
|
||||||
&self,
|
&self,
|
||||||
metadata: &crate::model::BookMetadata,
|
metadata: &crate::model::BookMetadata,
|
||||||
) -> Result<()> {
|
) -> 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 conn = Arc::clone(&self.conn);
|
||||||
let media_id_str = metadata.media_id.to_string();
|
let media_id_str = metadata.media_id.to_string();
|
||||||
let isbn = metadata.isbn.clone();
|
let isbn = metadata.isbn.clone();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue