use std::sync::{Arc, Mutex, MutexGuard}; use rusqlite::{Connection, Error, Row}; use rusqlite::fallible_iterator::FallibleIterator; use uuid::Uuid; use crate::video::model::LibraryItemEntity; struct LibraryItemRawEntity { pub id: Uuid, pub name: String, pub r#type: i8, pub parent_item_id: Option, } #[derive(Clone)] pub struct LibraryItemSqliteRepository { pub database_connection: Arc>, } impl LibraryItemSqliteRepository { pub fn new(database_connection: Arc>) -> Self { Self { database_connection, } } fn get_connection(&self) -> MutexGuard<'_, Connection> { self.database_connection.lock().unwrap() } pub fn create_table(&self) -> Result<(), rusqlite::Error> { let connection = self.get_connection(); connection.execute( "CREATE TABLE IF NOT EXISTS library_item ( id TEXT PRIMARY KEY, name VARCHAR NOT NULL, type INTEGER NOT NULL, parent_folder_id TEXT NULL FOREIGN KEY REFERENCES library_item (id) );", [] )?; Ok(()) } pub fn get_all_of_root_folder(&self) -> Result, Error> { let database_connection = self.get_connection(); let mut prepared_statement = database_connection.prepare( "SELECT id, name, type FROM library_item WHERE parent_folder_id IS NULL", )?; let rows = prepared_statement.query([])?; rows.map(Self::map_to_entity).collect::>() } fn map_to_entity(row: &Row) -> Result { let id_text: String = row.get(0)?; let type_as_number: i8 = row.get(2)?; Ok( LibraryItemEntity { id: Uuid::parse_str(&id_text).unwrap(), name: row.get(1)?, r#type: type_as_number, parent_folder_id: None } ) } pub fn save_all(&self, entities: Vec) -> Result<(), Error> { let database_connection = self.get_connection(); let mut query = String::from("INSERT INTO library_item (id, name, type) VALUES "); let mut params: Vec = Vec::new(); for (i, entity) in entities.iter().enumerate() { if i > 0 { query.push_str(", "); } query.push_str(&format!("(${}, ${}, ${})", i * 3 + 1, i * 3 + 2, i * 3 + 3)); params.push(entity.id.to_string().into()); params.push(entity.name.clone().into()); params.push(entity.r#type.into()); } let mut prepared_statement = database_connection .prepare(&query) .expect("Unable to prepare statement for user saving…"); prepared_statement.execute(rusqlite::params_from_iter(params))?; Ok(()) } pub fn get_by_id_with_all_hierarchy(&self, library_item_id: &Uuid) -> Result, Error> { let database_connection = self.get_connection(); /* Given a LibraryItem item_1: LibraryItemEntity { id: , name: "item_1", type: 0, parent_item_id: } Its parent item is item_2: LibraryItemEntity { id: , name: "item_2", type: 1, parent_item_id: } item_2 has a parent which is item_3: LibraryItemEntity { id: , name: "item_3", type: 0, parent_item_id: NULL } TODO: Write a SQL query to retrieve all LibraryItemEntity objects. It should filter on id with a parameter. This query should return something like: | id | name | type | parent_item_id | | ----- | -------- | ---- | -------------- | | | 'item_1' | 0 | | | | 'item_2' | 1 | | | | 'item_3' | 1 | NULL | Take example on the following query and adapt it. */ let mut prepared_statement = database_connection.prepare( "WITH RECURSIVE library_item_hierarchy AS ( -- Base case: start with the requested item SELECT id, name, type, parent_folder_id FROM library_item WHERE id = ?1 UNION ALL -- Recursive case: get all ancestors SELECT li.id, li.name, li.type, li.parent_folder_id FROM library_item li INNER JOIN library_item_hierarchy lh ON li.id = lh.parent_folder_id ) SELECT id, name, type, parent_folder_id FROM library_item_hierarchy ORDER BY id;" )?; let mut rows = prepared_statement.query([library_item_id.to_string()])?; let raw_data = rows.map(Self::map_to_raw_entity).collect::>()?; // Build a map of id to LibraryItemEntity for quick lookup let mut entity_map: std::collections::HashMap = std::collections::HashMap::new(); // First pass: create all entities without parent relationships for raw_entity in &raw_data { let entity = LibraryItemEntity { id: raw_entity.id.clone(), name: raw_entity.name.clone(), r#type: raw_entity.r#type, parent_folder_id: None, // Will be set in second pass }; entity_map.insert(raw_entity.id.clone(), entity); } // Second pass: set up parent-child relationships for raw_entity in &raw_data { if let Some(parent_id) = &raw_entity.parent_item_id { if let Some(child_entity) = entity_map.get_mut(&raw_entity.id) { if let Some(parent_entity) = entity_map.get(parent_id) { child_entity.parent_folder_id = Some(Box::new(parent_entity.clone())); } } } } // Find the requested item and return it with its full hierarchy if let Some(entity) = entity_map.get(library_item_id) { Ok(Some(entity.clone())) } else { Ok(None) } } pub fn find_by_id(&self, library_item_id: Uuid) -> Result, Error> { let database_connection = self.get_connection(); let mut prepared_statement = database_connection.prepare( "SELECT id, name, type, parent_folder_id FROM library_item WHERE id = ?1;" )?; let mut rows = prepared_statement.query([library_item_id.to_string()])?; if let Some(row) = rows.next()? { Ok(Some(Self::map_to_entity(&row)?)) } else { Ok(None) } } fn map_to_raw_entity(row: &Row) -> Result { let id_text: String = row.get(0)?; let type_as_number: i8 = row.get(2)?; let parent_item_id_text: Option = row.get(3)?; let raw_entity = LibraryItemRawEntity { id: Uuid::parse_str(&id_text).unwrap(), name: row.get(1)?, r#type: type_as_number, parent_item_id: parent_item_id_text.map(|id| Uuid::parse_str(id.as_str()).unwrap()) }; Ok(raw_entity) } }