pinakes: import in parallel; various UI improvements

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I1eb47cd79cd4145c56af966f6756fe1d6a6a6964
This commit is contained in:
raf 2026-02-03 10:31:20 +03:00
commit 116fe7b059
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
42 changed files with 4189 additions and 316 deletions

View file

@ -46,6 +46,8 @@ pub trait StorageBackend: Send + Sync + 'static {
async fn get_media(&self, id: MediaId) -> Result<MediaItem>;
async fn count_media(&self) -> Result<u64>;
async fn get_media_by_hash(&self, hash: &ContentHash) -> Result<Option<MediaItem>>;
/// Get a media item by its file path (used for incremental scanning)
async fn get_media_by_path(&self, path: &std::path::Path) -> Result<Option<MediaItem>>;
async fn list_media(&self, pagination: &Pagination) -> Result<Vec<MediaItem>>;
async fn update_media(&self, item: &MediaItem) -> Result<()>;
async fn delete_media(&self, id: MediaId) -> Result<()>;
@ -232,6 +234,59 @@ pub trait StorageBackend: Send + Sync + 'static {
root_path: &str,
) -> Result<()>;
/// Check if a user has access to a specific media item based on library permissions.
/// Returns the permission level if access is granted, or an error if denied.
/// Admin users (role=admin) bypass library checks and have full access.
async fn check_library_access(
&self,
user_id: crate::users::UserId,
media_id: crate::model::MediaId,
) -> Result<crate::users::LibraryPermission> {
// Default implementation: get the media item's path and check against user's library access
let media = self.get_media(media_id).await?;
let path_str = media.path.to_string_lossy().to_string();
// Get user's library permissions
let libraries = self.get_user_libraries(user_id).await?;
// If user has no library restrictions, they have no access (unless they're admin)
// This default impl requires at least one matching library permission
for lib in &libraries {
if path_str.starts_with(&lib.root_path) {
return Ok(lib.permission);
}
}
Err(crate::error::PinakesError::Authorization(format!(
"user {} has no access to media {}",
user_id, media_id
)))
}
/// Check if a user has at least read access to a media item
async fn has_media_read_access(
&self,
user_id: crate::users::UserId,
media_id: crate::model::MediaId,
) -> Result<bool> {
match self.check_library_access(user_id, media_id).await {
Ok(perm) => Ok(perm.can_read()),
Err(_) => Ok(false),
}
}
/// Check if a user has write access to a media item
async fn has_media_write_access(
&self,
user_id: crate::users::UserId,
media_id: crate::model::MediaId,
) -> Result<bool> {
match self.check_library_access(user_id, media_id).await {
Ok(perm) => Ok(perm.can_write()),
Err(_) => Ok(false),
}
}
// ===== Ratings =====
async fn rate_media(
&self,