use crate::database::Database;
use crate::error::ConformResult;
use crate::types::MediaFile;
use parking_lot::RwLock;
use std::path::Path;
use std::sync::Arc;
pub struct MediaCatalog {
db: Database,
cache: Arc<RwLock<Vec<MediaFile>>>,
}
impl MediaCatalog {
pub fn new<P: AsRef<Path>>(db_path: P) -> ConformResult<Self> {
let db = Database::open(db_path)?;
Ok(Self {
db,
cache: Arc::new(RwLock::new(Vec::new())),
})
}
pub fn in_memory() -> ConformResult<Self> {
let db = Database::in_memory()?;
Ok(Self {
db,
cache: Arc::new(RwLock::new(Vec::new())),
})
}
pub fn add(&self, media: MediaFile) -> ConformResult<()> {
self.db.add_media_file(&media)?;
self.cache.write().push(media);
Ok(())
}
pub fn add_many(&self, media_files: Vec<MediaFile>) -> ConformResult<()> {
for media in &media_files {
self.db.add_media_file(media)?;
}
self.cache.write().extend(media_files);
Ok(())
}
pub fn find_by_filename(&self, filename: &str) -> ConformResult<Vec<MediaFile>> {
self.db.find_by_filename(filename)
}
pub fn find_by_path_pattern(&self, pattern: &str) -> ConformResult<Vec<MediaFile>> {
self.db.find_by_path_pattern(pattern)
}
pub fn find_by_md5(&self, md5: &str) -> ConformResult<Vec<MediaFile>> {
self.db.find_by_md5(md5)
}
pub fn get_all(&self) -> ConformResult<Vec<MediaFile>> {
self.db.get_all_media_files()
}
pub fn count(&self) -> ConformResult<usize> {
self.db.get_media_file_count()
}
pub fn clear(&self) -> ConformResult<()> {
self.db.clear_media_files()?;
self.cache.write().clear();
Ok(())
}
pub fn reload_cache(&self) -> ConformResult<()> {
let media_files = self.db.get_all_media_files()?;
*self.cache.write() = media_files;
Ok(())
}
pub fn search(&self, query: &SearchQuery) -> ConformResult<Vec<MediaFile>> {
let all_media = self.cache.read();
let mut results = Vec::new();
for media in all_media.iter() {
if query.matches(media) {
results.push(media.clone());
}
}
Ok(results)
}
}
#[derive(Debug, Clone, Default)]
pub struct SearchQuery {
pub filename: Option<String>,
pub min_duration: Option<f64>,
pub max_duration: Option<f64>,
pub width: Option<u32>,
pub height: Option<u32>,
}
impl SearchQuery {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_filename(mut self, filename: String) -> Self {
self.filename = Some(filename);
self
}
#[must_use]
pub fn with_duration_range(mut self, min: f64, max: f64) -> Self {
self.min_duration = Some(min);
self.max_duration = Some(max);
self
}
#[must_use]
pub fn with_resolution(mut self, width: u32, height: u32) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
#[must_use]
pub fn matches(&self, media: &MediaFile) -> bool {
if let Some(ref pattern) = self.filename {
if !media.filename.contains(pattern) {
return false;
}
}
if let Some(min_dur) = self.min_duration {
if media.duration.map_or(true, |d| d < min_dur) {
return false;
}
}
if let Some(max_dur) = self.max_duration {
if media.duration.map_or(true, |d| d > max_dur) {
return false;
}
}
if let Some(w) = self.width {
if media.width != Some(w) {
return false;
}
}
if let Some(h) = self.height {
if media.height != Some(h) {
return false;
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_catalog_creation() {
let catalog = MediaCatalog::in_memory().expect("catalog should be valid");
assert_eq!(catalog.count().expect("count should succeed"), 0);
}
#[test]
fn test_add_media() {
let catalog = MediaCatalog::in_memory().expect("catalog should be valid");
let media = MediaFile::new(PathBuf::from("/test/file.mov"));
catalog.add(media).expect("add should succeed");
assert_eq!(catalog.count().expect("count should succeed"), 1);
}
#[test]
fn test_find_by_filename() {
let catalog = MediaCatalog::in_memory().expect("catalog should be valid");
let media = MediaFile::new(PathBuf::from("/test/file.mov"));
catalog.add(media).expect("add should succeed");
let found = catalog
.find_by_filename("file.mov")
.expect("found should be valid");
assert_eq!(found.len(), 1);
}
#[test]
fn test_clear_catalog() {
let catalog = MediaCatalog::in_memory().expect("catalog should be valid");
let media1 = MediaFile::new(PathBuf::from("/test/file1.mov"));
let media2 = MediaFile::new(PathBuf::from("/test/file2.mov"));
catalog.add(media1).expect("add should succeed");
catalog.add(media2).expect("add should succeed");
assert_eq!(catalog.count().expect("count should succeed"), 2);
catalog.clear().expect("clear should succeed");
assert_eq!(catalog.count().expect("count should succeed"), 0);
}
#[test]
fn test_search_query() {
let catalog = MediaCatalog::in_memory().expect("catalog should be valid");
let mut media1 = MediaFile::new(PathBuf::from("/test/video.mov"));
media1.duration = Some(10.0);
media1.width = Some(1920);
media1.height = Some(1080);
let mut media2 = MediaFile::new(PathBuf::from("/test/audio.wav"));
media2.duration = Some(5.0);
catalog.add(media1).expect("add should succeed");
catalog.add(media2).expect("add should succeed");
let query = SearchQuery::new().with_duration_range(8.0, 12.0);
let results = catalog.search(&query).expect("results should be valid");
assert_eq!(results.len(), 1);
assert!(results[0].filename.contains("video"));
}
}