initial commit

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I4a6b498153eccd5407510dd541b7f4816a6a6964
This commit is contained in:
raf 2026-01-30 22:05:46 +03:00
commit 6a73d11c4b
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
124 changed files with 34856 additions and 0 deletions

View file

@ -0,0 +1,26 @@
use crate::error::{PinakesError, Result};
mod sqlite_migrations {
use refinery::embed_migrations;
embed_migrations!("../../migrations/sqlite");
}
mod postgres_migrations {
use refinery::embed_migrations;
embed_migrations!("../../migrations/postgres");
}
pub fn run_sqlite_migrations(conn: &mut rusqlite::Connection) -> Result<()> {
sqlite_migrations::migrations::runner()
.run(conn)
.map_err(|e| PinakesError::Migration(e.to_string()))?;
Ok(())
}
pub async fn run_postgres_migrations(client: &mut tokio_postgres::Client) -> Result<()> {
postgres_migrations::migrations::runner()
.run_async(client)
.await
.map_err(|e| PinakesError::Migration(e.to_string()))?;
Ok(())
}

View file

@ -0,0 +1,209 @@
pub mod migrations;
pub mod postgres;
pub mod sqlite;
use std::path::PathBuf;
use std::sync::Arc;
use uuid::Uuid;
use crate::error::Result;
use crate::model::*;
use crate::search::{SearchRequest, SearchResults};
/// Statistics about the database.
#[derive(Debug, Clone, Default)]
pub struct DatabaseStats {
pub media_count: u64,
pub tag_count: u64,
pub collection_count: u64,
pub audit_count: u64,
pub database_size_bytes: u64,
pub backend_name: String,
}
#[async_trait::async_trait]
pub trait StorageBackend: Send + Sync + 'static {
// Migrations
async fn run_migrations(&self) -> Result<()>;
// Root directories
async fn add_root_dir(&self, path: PathBuf) -> Result<()>;
async fn list_root_dirs(&self) -> Result<Vec<PathBuf>>;
async fn remove_root_dir(&self, path: &std::path::Path) -> Result<()>;
// Media CRUD
async fn insert_media(&self, item: &MediaItem) -> Result<()>;
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>>;
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<()>;
async fn delete_all_media(&self) -> Result<u64>;
// Tags
async fn create_tag(&self, name: &str, parent_id: Option<Uuid>) -> Result<Tag>;
async fn get_tag(&self, id: Uuid) -> Result<Tag>;
async fn list_tags(&self) -> Result<Vec<Tag>>;
async fn delete_tag(&self, id: Uuid) -> Result<()>;
async fn tag_media(&self, media_id: MediaId, tag_id: Uuid) -> Result<()>;
async fn untag_media(&self, media_id: MediaId, tag_id: Uuid) -> Result<()>;
async fn get_media_tags(&self, media_id: MediaId) -> Result<Vec<Tag>>;
async fn get_tag_descendants(&self, tag_id: Uuid) -> Result<Vec<Tag>>;
// Collections
async fn create_collection(
&self,
name: &str,
kind: CollectionKind,
description: Option<&str>,
filter_query: Option<&str>,
) -> Result<Collection>;
async fn get_collection(&self, id: Uuid) -> Result<Collection>;
async fn list_collections(&self) -> Result<Vec<Collection>>;
async fn delete_collection(&self, id: Uuid) -> Result<()>;
async fn add_to_collection(
&self,
collection_id: Uuid,
media_id: MediaId,
position: i32,
) -> Result<()>;
async fn remove_from_collection(&self, collection_id: Uuid, media_id: MediaId) -> Result<()>;
async fn get_collection_members(&self, collection_id: Uuid) -> Result<Vec<MediaItem>>;
// Search
async fn search(&self, request: &SearchRequest) -> Result<SearchResults>;
// Audit
async fn record_audit(&self, entry: &AuditEntry) -> Result<()>;
async fn list_audit_entries(
&self,
media_id: Option<MediaId>,
pagination: &Pagination,
) -> Result<Vec<AuditEntry>>;
// Custom fields
async fn set_custom_field(
&self,
media_id: MediaId,
name: &str,
field: &CustomField,
) -> Result<()>;
async fn get_custom_fields(
&self,
media_id: MediaId,
) -> Result<std::collections::HashMap<String, CustomField>>;
async fn delete_custom_field(&self, media_id: MediaId, name: &str) -> Result<()>;
// Batch operations (transactional where supported)
async fn batch_delete_media(&self, ids: &[MediaId]) -> Result<u64> {
let mut count = 0u64;
for id in ids {
self.delete_media(*id).await?;
count += 1;
}
Ok(count)
}
async fn batch_tag_media(&self, media_ids: &[MediaId], tag_ids: &[Uuid]) -> Result<u64> {
let mut count = 0u64;
for media_id in media_ids {
for tag_id in tag_ids {
self.tag_media(*media_id, *tag_id).await?;
count += 1;
}
}
Ok(count)
}
// Integrity
async fn list_media_paths(&self) -> Result<Vec<(MediaId, std::path::PathBuf, ContentHash)>>;
// Batch metadata update
async fn batch_update_media(
&self,
ids: &[MediaId],
title: Option<&str>,
artist: Option<&str>,
album: Option<&str>,
genre: Option<&str>,
year: Option<i32>,
description: Option<&str>,
) -> Result<u64> {
let mut count = 0u64;
for id in ids {
let mut item = self.get_media(*id).await?;
if let Some(v) = title {
item.title = Some(v.to_string());
}
if let Some(v) = artist {
item.artist = Some(v.to_string());
}
if let Some(v) = album {
item.album = Some(v.to_string());
}
if let Some(v) = genre {
item.genre = Some(v.to_string());
}
if let Some(v) = &year {
item.year = Some(*v);
}
if let Some(v) = description {
item.description = Some(v.to_string());
}
item.updated_at = chrono::Utc::now();
self.update_media(&item).await?;
count += 1;
}
Ok(count)
}
// Saved searches
async fn save_search(
&self,
id: uuid::Uuid,
name: &str,
query: &str,
sort_order: Option<&str>,
) -> Result<()>;
async fn list_saved_searches(&self) -> Result<Vec<crate::model::SavedSearch>>;
async fn delete_saved_search(&self, id: uuid::Uuid) -> Result<()>;
// Duplicates
async fn find_duplicates(&self) -> Result<Vec<Vec<MediaItem>>>;
// Database management
async fn database_stats(&self) -> Result<DatabaseStats>;
async fn vacuum(&self) -> Result<()>;
async fn clear_all_data(&self) -> Result<()>;
// Thumbnail helpers
/// List all media IDs, optionally filtering to those missing thumbnails.
async fn list_media_ids_for_thumbnails(
&self,
only_missing: bool,
) -> Result<Vec<crate::model::MediaId>>;
// Library statistics
async fn library_statistics(&self) -> Result<LibraryStatistics>;
}
/// Comprehensive library statistics.
#[derive(Debug, Clone, Default)]
pub struct LibraryStatistics {
pub total_media: u64,
pub total_size_bytes: u64,
pub avg_file_size_bytes: u64,
pub media_by_type: Vec<(String, u64)>,
pub storage_by_type: Vec<(String, u64)>,
pub newest_item: Option<String>,
pub oldest_item: Option<String>,
pub top_tags: Vec<(String, u64)>,
pub top_collections: Vec<(String, u64)>,
pub total_tags: u64,
pub total_collections: u64,
pub total_duplicates: u64,
}
pub type DynStorageBackend = Arc<dyn StorageBackend>;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff