223 lines
7.6 KiB
Rust
223 lines
7.6 KiB
Rust
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<Uuid>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct LibraryItemSqliteRepository {
|
|
pub database_connection: Arc<Mutex<Connection>>,
|
|
}
|
|
|
|
impl LibraryItemSqliteRepository {
|
|
pub fn new(database_connection: Arc<Mutex<Connection>>) -> 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<Vec<LibraryItemEntity>, 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::<Vec<LibraryItemEntity>>()
|
|
}
|
|
|
|
fn map_to_entity(row: &Row) -> Result<LibraryItemEntity, Error> {
|
|
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<LibraryItemEntity>) -> 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<rusqlite::types::Value> = 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<Option<LibraryItemEntity>, Error> {
|
|
let database_connection = self.get_connection();
|
|
|
|
/*
|
|
Given a LibraryItem item_1:
|
|
LibraryItemEntity {
|
|
id: <id1>,
|
|
name: "item_1",
|
|
type: 0,
|
|
parent_item_id: <id2>
|
|
}
|
|
|
|
Its parent item is item_2:
|
|
LibraryItemEntity {
|
|
id: <id2>,
|
|
name: "item_2",
|
|
type: 1,
|
|
parent_item_id: <id3>
|
|
}
|
|
|
|
item_2 has a parent which is item_3:
|
|
LibraryItemEntity {
|
|
id: <id3>,
|
|
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 |
|
|
| ----- | -------- | ---- | -------------- |
|
|
| <id1> | 'item_1' | 0 | <id2> |
|
|
| <id2> | 'item_2' | 1 | <id3> |
|
|
| <id3> | '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::<Vec<LibraryItemRawEntity>>()?;
|
|
|
|
// Build a map of id to LibraryItemEntity for quick lookup
|
|
let mut entity_map: std::collections::HashMap<Uuid, LibraryItemEntity> = 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<Option<LibraryItemEntity>, 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<LibraryItemRawEntity, Error> {
|
|
let id_text: String = row.get(0)?;
|
|
let type_as_number: i8 = row.get(2)?;
|
|
let parent_item_id_text: Option<String> = 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)
|
|
}
|
|
}
|