From e8ee893630b40c744e145b47f2a0c37f30e49a8b Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Thu, 10 Aug 2023 23:52:53 +0200 Subject: [PATCH 01/17] hooking db --- .vsb | 0 .vscode/settings.json | 1 + README.md | 4 ++ src/app_data.rs | 8 ++- src/database/collection.rs | 54 ++++++++++----- src/database/handler.rs | 73 +++------------------ src/database/sqls/schema.sql | 1 + src/database/sqls/test.sql | 10 +-- src/database/store.rs | 3 +- src/database/tests/get_collections.rs | 22 ++++--- src/main.rs | 12 +++- src/state/browser.rs | 94 +++++++++++++-------------- 12 files changed, 134 insertions(+), 148 deletions(-) create mode 100644 .vsb diff --git a/.vsb b/.vsb new file mode 100644 index 0000000..e69de29 diff --git a/.vscode/settings.json b/.vscode/settings.json index b066b94..914bc6a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,4 +10,5 @@ }, "prettier.tabWidth": 4, "css.lint.unknownProperties": "ignore", + "rust-analyzer.showUnlinkedFileNotification": false, } diff --git a/README.md b/README.md index 90839be..34e0682 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # Vizia Sample Browser An audio sample browser application. + +| Header 1 | Header 2 | +| --- | --- | +| Table 1 | Table 2 | \ No newline at end of file diff --git a/src/app_data.rs b/src/app_data.rs index bc09d40..12b3f36 100644 --- a/src/app_data.rs +++ b/src/app_data.rs @@ -1,9 +1,15 @@ +use std::sync::{Arc, Mutex}; + use vizia::prelude::*; -use crate::state::browser::BrowserState; +use crate::{ + database::prelude::Database, + state::browser::{BrowserState, Directory}, +}; #[derive(Lens)] pub struct AppData { + pub database: Arc>, pub browser: BrowserState, pub browser_width: f32, pub table_height: f32, diff --git a/src/database/collection.rs b/src/database/collection.rs index 5606db4..dd3cae8 100644 --- a/src/database/collection.rs +++ b/src/database/collection.rs @@ -1,3 +1,5 @@ +use std::path::{Path, PathBuf}; + use super::{Database, DatabaseConnectionHandle, DatabaseError}; use serde::{Deserialize, Serialize}; @@ -7,11 +9,17 @@ pub struct Collection { id: CollectionID, parent_collection: Option, name: String, + path: PathBuf, } impl Collection { - pub fn new(id: CollectionID, parent_collection: Option, name: String) -> Self { - Self { id, parent_collection, name } + pub fn new( + id: CollectionID, + parent_collection: Option, + name: String, + path: PathBuf, + ) -> Self { + Self { id, parent_collection, name, path } } pub fn id(&self) -> usize { @@ -25,6 +33,10 @@ impl Collection { pub fn name(&self) -> &str { self.name.as_ref() } + + pub fn path(&self) -> &PathBuf { + &self.path + } } pub trait DatabaseCollectionHandler { @@ -39,11 +51,17 @@ impl DatabaseCollectionHandler for Database { fn get_root_collection(&self) -> Result { if let Some(connection) = self.get_connection() { let mut query = connection.prepare( - "SELECT id, parent_collection, name FROM collections WHERE parent_collection IS NULL", + "SELECT id, parent_collection, name, path FROM collections WHERE parent_collection IS NULL", )?; let col: Collection = query.query_row([], |row| { - Ok(Collection { id: row.get(0)?, parent_collection: None, name: row.get(2)? }) + let path: String = row.get(3)?; + Ok(Collection::new( + row.get(0)?, + row.get(1)?, + row.get(2)?, + Path::new(&path).to_path_buf(), + )) })?; return Ok(col); @@ -55,14 +73,16 @@ impl DatabaseCollectionHandler for Database { fn get_all_collections(&self) -> Result, DatabaseError> { if let Some(connection) = self.get_connection() { let mut query = - connection.prepare("SELECT id, parent_collection, name FROM collections")?; + connection.prepare("SELECT id, parent_collection, name, path FROM collections")?; let collections = query.query_map([], |row| { - Ok(Collection { - id: row.get(0)?, - parent_collection: row.get(1)?, - name: row.get(2)?, - }) + let path: String = row.get(3)?; + Ok(Collection::new( + row.get(0)?, + row.get(1)?, + row.get(2)?, + Path::new(&path).to_path_buf(), + )) })?; return Ok(collections.map(|v| v.unwrap()).collect()); @@ -77,15 +97,17 @@ impl DatabaseCollectionHandler for Database { ) -> Result, DatabaseError> { if let Some(connection) = self.get_connection() { let mut query = connection.prepare( - "SELECT id, parent_collection, name FROM collections WHERE parent_collection = (?1)", + "SELECT id, parent_collection, name, path FROM collections WHERE parent_collection = (?1)", )?; let collections = query.query_map([parent], |row| { - Ok(Collection { - id: row.get(0)?, - name: row.get(2)?, - parent_collection: row.get(1)?, - }) + let path: String = row.get(3)?; + Ok(Collection::new( + row.get(0)?, + row.get(1)?, + row.get(2)?, + Path::new(&path).to_path_buf(), + )) })?; return Ok(collections.map(|v| v.unwrap()).collect()); diff --git a/src/database/handler.rs b/src/database/handler.rs index 7d6ccdc..3a6e352 100644 --- a/src/database/handler.rs +++ b/src/database/handler.rs @@ -1,7 +1,6 @@ use crate::state::browser::Directory; use super::*; -use itertools::Itertools; use rusqlite::Connection; use std::{ any::Any, @@ -13,11 +12,12 @@ use std::{ rc::Rc, sync::atomic::AtomicUsize, }; +use vizia::prelude::*; pub const DATABASE_FILE_NAME: &str = ".database.vsb"; pub const AUDIO_FILE_EXTENSIONS: [&'static str; 1] = ["wav"]; -#[derive(Debug)] +#[derive(Debug, Lens)] pub struct Database { pub(super) path: PathBuf, pub(super) conn: Option, @@ -76,7 +76,7 @@ impl Database { }; // Insert collection - let collection = Collection::new(id, parent_id, name); + let collection = Collection::new(id, parent_id, name, path.clone()); db.insert_collection(collection); @@ -154,67 +154,10 @@ where Ok(()) } -#[derive(Clone, Debug)] -struct RecursiveDir { - id: CollectionID, - parent_id: Option, - name: String, - children: Vec, -} - -fn query_to_recursive(db: &Database) -> RecursiveDir { - let collections = db.get_all_collections().unwrap(); - - let mut hm: HashMap = HashMap::new(); - - for coll in collections { - hm.insert( - coll.id(), - RecursiveDir { - id: coll.id(), - parent_id: coll.parent_collection(), - name: coll.name().to_string(), - children: Vec::new(), - }, - ); - } - - fn children_of_collection( - map: &HashMap, - coll: CollectionID, - ) -> VecDeque { - map.values().filter(|v| v.parent_id == Some(coll)).map(|v| v.id).collect() - } - - let mut root_dir = hm.values().find(|v| v.parent_id.is_none()).unwrap().clone(); - - let mut collection_stack: VecDeque = children_of_collection(&hm, root_dir.id); - - while let Some(coll) = collection_stack.pop_front() { - let mut children = children_of_collection(&hm, coll); - collection_stack.append(&mut children); - - let coll_data = hm.get(&coll).unwrap().clone(); - root_dir.children.push(coll_data); - } - - root_dir -} - -#[test] -fn query_to_recursive_test() { - let mut handle = Database::from_connection("", Some(Connection::open_in_memory().unwrap())); - handle.get_connection().unwrap().execute_batch(include_str!("sqls/schema.sql")).unwrap(); - handle.get_connection().unwrap().execute_batch(include_str!("sqls/test.sql")).unwrap(); - - let root = query_to_recursive(&handle); - - print_directory(&root); -} - -fn print_directory(dir: &RecursiveDir) { - println!("{:?}", dir.name); - for child in dir.children.iter() { - print_directory(child) +impl PartialEq for Database { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + && self.conn.is_some() == other.conn.is_some() + && self.meta == other.meta } } diff --git a/src/database/sqls/schema.sql b/src/database/sqls/schema.sql index 14c1d48..fe5e5c9 100644 --- a/src/database/sqls/schema.sql +++ b/src/database/sqls/schema.sql @@ -1,6 +1,7 @@ CREATE TABLE collections ( id integer UNIQUE PRIMARY KEY, parent_collection integer NULL, + path nvarchar(255), name nvarchar(255), FOREIGN KEY(parent_collection) REFERENCES collections(id) diff --git a/src/database/sqls/test.sql b/src/database/sqls/test.sql index 096b226..8c5e38b 100644 --- a/src/database/sqls/test.sql +++ b/src/database/sqls/test.sql @@ -1,8 +1,8 @@ -INSERT INTO collections(id, parent_collection, name) VALUES (0, NULL, "Sample Library"); -INSERT INTO collections(id, parent_collection, name) VALUES (1, 0, "Library 1"); -INSERT INTO collections(id, parent_collection, name) VALUES (2, 0, "Library 2"); -INSERT INTO collections(id, parent_collection, name) VALUES (3, 1, "Sub Library 1.1"); -INSERT INTO collections(id, parent_collection, name) VALUES (4, 1, "Sub Library 1.2"); +INSERT INTO collections(id, parent_collection, name, path) VALUES (0, NULL, "Sample Library", ""); +INSERT INTO collections(id, parent_collection, name, path) VALUES (1, 0, "Library 1", ""); +INSERT INTO collections(id, parent_collection, name, path) VALUES (2, 0, "Library 2", ""); +INSERT INTO collections(id, parent_collection, name, path) VALUES (3, 1, "Sub Library 1.1", ""); +INSERT INTO collections(id, parent_collection, name, path) VALUES (4, 1, "Sub Library 1.2", ""); INSERT INTO audio_files(id, name, collection, duration, sample_rate, bit_depth, bpm, key, size) VALUES (0, "Audio File 0", 0, 0, 0, 0, 0, 0, 0); INSERT INTO audio_files(id, name, collection, duration, sample_rate, bit_depth, bpm, key, size) VALUES (1, "Audio File 1", 1, 0, 0, 0, 0, 0, 0); diff --git a/src/database/store.rs b/src/database/store.rs index f9aa0d9..7f72de5 100644 --- a/src/database/store.rs +++ b/src/database/store.rs @@ -5,6 +5,7 @@ use std::{ fs::{create_dir, File}, path::PathBuf, }; +use vizia::prelude::*; pub const DATABASE_META_DIRECTORY_NAME: &str = ".vsb-meta/"; pub const DATABASE_DATABASE_NAME: &str = ".vsb-database"; @@ -12,7 +13,7 @@ pub const DATABASE_META_NAME: &str = ".vsb-meta"; pub type Hash = String; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, Lens, PartialEq)] pub struct DatabaseMetadata { pub(super) map: HashMap, } diff --git a/src/database/tests/get_collections.rs b/src/database/tests/get_collections.rs index 4b7fc6b..0141bfb 100644 --- a/src/database/tests/get_collections.rs +++ b/src/database/tests/get_collections.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use crate::database::{prelude::*, tests::init_test_database}; #[test] @@ -9,11 +11,11 @@ pub fn get_all_collections() { assert_eq!( handle.get_all_collections().unwrap(), vec![ - Collection::new(0, None, "Sample Library".to_string()), - Collection::new(1, Some(0), "Library 1".to_string()), - Collection::new(2, Some(0), "Library 2".to_string()), - Collection::new(3, Some(1), "Sub Library 1.1".to_string()), - Collection::new(4, Some(1), "Sub Library 1.2".to_string()), + Collection::new(0, None, "Sample Library".to_string(), Path::new("").to_path_buf()), + Collection::new(1, Some(0), "Library 1".to_string(), Path::new("").to_path_buf()), + Collection::new(2, Some(0), "Library 2".to_string(), Path::new("").to_path_buf()), + Collection::new(3, Some(1), "Sub Library 1.1".to_string(), Path::new("").to_path_buf()), + Collection::new(4, Some(1), "Sub Library 1.2".to_string(), Path::new("").to_path_buf()), ] ); } @@ -26,7 +28,7 @@ pub fn get_root_collection() { assert_eq!( handle.get_root_collection().unwrap(), - Collection::new(0, None, "Sample Library".to_string()) + Collection::new(0, None, "Sample Library".to_string(), Path::new("").to_path_buf()) ); } @@ -39,16 +41,16 @@ pub fn get_child_collections() { assert_eq!( handle.get_child_collections(0).unwrap(), vec![ - Collection::new(1, Some(0), "Library 1".to_string()), - Collection::new(2, Some(0), "Library 2".to_string()), + Collection::new(1, Some(0), "Library 1".to_string(), Path::new("").to_path_buf()), + Collection::new(2, Some(0), "Library 2".to_string(), Path::new("").to_path_buf()), ] ); assert_eq!( handle.get_child_collections(1).unwrap(), vec![ - Collection::new(3, Some(1), "Sub Library 1.1".to_string()), - Collection::new(4, Some(1), "Sub Library 1.2".to_string()), + Collection::new(3, Some(1), "Sub Library 1.1".to_string(), Path::new("").to_path_buf()), + Collection::new(4, Some(1), "Sub Library 1.2".to_string(), Path::new("").to_path_buf()), ] ); } diff --git a/src/main.rs b/src/main.rs index 3525275..4d80a26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,10 @@ use app_data::AppData; use rusqlite::Connection; +use std::{ + collections::{HashMap, VecDeque}, + sync::{Arc, Mutex}, +}; use views::smart_table::SmartTable; use vizia::{ icons::{ICON_LIST_SEARCH, ICON_SEARCH}, @@ -26,8 +30,6 @@ use app_data::*; mod popup_menu; fn main() { - let database_handle = Database::from_connection("", Some(Connection::open(".vsb").unwrap())); - Application::new(|cx| { // Add resources cx.add_stylesheet(include_style!("resources/themes/style.css")) @@ -69,6 +71,9 @@ fn main() { }) .collect::>(); + let mut db = + Database::from_connection("test_files/", Some(Connection::open(".vsb").unwrap())); + AppData { browser: BrowserState::default(), browser_width: 300.0, @@ -76,6 +81,9 @@ fn main() { table_headers: headers, table_rows: rows, search_text: String::new(), + + // + database: Arc::new(Mutex::new(db)), } .build(cx); diff --git a/src/state/browser.rs b/src/state/browser.rs index 86ab0f4..79d939d 100644 --- a/src/state/browser.rs +++ b/src/state/browser.rs @@ -1,11 +1,10 @@ +use crate::{app_data::AppData, database::prelude::*}; +use fuzzy_matcher::skim::SkimMatcherV2; +use fuzzy_matcher::FuzzyMatcher; use std::{ - collections::HashSet, + collections::{HashMap, HashSet, VecDeque}, path::{Path, PathBuf}, }; - -use fuzzy_matcher::skim::SkimMatcherV2; -use fuzzy_matcher::FuzzyMatcher; - use vizia::prelude::*; #[derive(Debug, Lens, Clone, Data)] @@ -42,6 +41,8 @@ pub enum BrowserEvent { #[derive(Debug, Clone, Data, Lens)] pub struct Directory { + pub id: CollectionID, + pub parent_id: Option, pub name: String, pub path: PathBuf, pub children: Vec, @@ -54,15 +55,7 @@ pub struct Directory { impl Default for BrowserState { fn default() -> Self { Self { - libraries: vec![Directory { - name: String::from("root"), - path: PathBuf::from("the-libre-sample-pack"), - children: vec![], - is_open: false, - num_files: 0, - match_indices: Vec::default(), - shown: true, - }], + libraries: Vec::new(), selected: HashSet::new(), focused: None, search_text: String::new(), @@ -77,9 +70,10 @@ impl Model for BrowserState { event.map(|browser_event, _| match browser_event { // Temp: Load the assets directory for the treeview BrowserEvent::ViewAll => { - if let Some(root) = visit_dirs(Path::new("the-libre-sample-pack"), &mut 0) { - self.libraries[0] = root; - } + let db_ref = AppData::database.get(cx); + let db = db_ref.lock().unwrap(); + let root = collections_to_directories(&mut db.get_all_collections().unwrap()); + self.libraries[0] = root; } BrowserEvent::Search(search_text) => { @@ -308,40 +302,44 @@ fn is_collapsed<'a>(root: &'a Directory, dir: &PathBuf) -> bool { false } -// Recursively build directory tree from root path -fn visit_dirs(dir: &Path, num_files: &mut usize) -> Option { - let name = dir.file_name()?.to_str()?.to_string(); - let mut children = Vec::new(); - - let mut file_count = 0; - - if dir.is_dir() { - for entry in std::fs::read_dir(dir).ok()? { - let entry = entry.ok()?; - let path = entry.path(); - if path.is_dir() { - children.push(visit_dirs(&path, &mut file_count)?); - } else { - // TODO: Check for audio files - file_count += 1; - } - } +fn collections_to_directories(collections: &mut Vec) -> Directory { + let mut hm: HashMap = HashMap::new(); + + for coll in collections { + hm.insert( + coll.id(), + Directory { + id: coll.id(), + parent_id: coll.parent_collection(), + name: coll.name().to_string(), + path: coll.path().clone(), + is_open: false, + num_files: 0, + shown: true, + match_indices: Vec::new(), + children: Vec::new(), + }, + ); } - *num_files += file_count; + fn children_of_collection( + map: &HashMap, + coll: CollectionID, + ) -> VecDeque { + map.values().filter(|v| v.parent_id == Some(coll)).map(|v| v.id).collect() + } + + let mut root_dir = hm.values().find(|v| v.parent_id.is_none()).unwrap().clone(); - // Sort by alphabetical (should this be a setting?) - children.sort_by(|a, b| a.name.cmp(&b.name)); + let mut collection_stack: VecDeque = children_of_collection(&hm, root_dir.id); - let has_children = !children.is_empty(); + while let Some(coll) = collection_stack.pop_front() { + let mut children = children_of_collection(&hm, coll); + collection_stack.append(&mut children); + + let coll_data = hm.get(&coll).unwrap().clone(); + root_dir.children.push(coll_data); + } - Some(Directory { - name, - path: PathBuf::from(dir), - children, - is_open: has_children, - num_files: file_count, - match_indices: Vec::default(), - shown: true, - }) + root_dir } From 64a8ffeb470bc2deb0781ba2c977ef2d76e1c361 Mon Sep 17 00:00:00 2001 From: George Atkinson Date: Fri, 11 Aug 2023 00:04:32 +0100 Subject: [PATCH 02/17] Move directory creation to main --- src/main.rs | 47 +++++++++++++++++++++++++++++-- src/panels/browser.rs | 2 -- src/state/browser.rs | 64 ++++++++----------------------------------- 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4d80a26..f2bdcc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,14 +74,15 @@ fn main() { let mut db = Database::from_connection("test_files/", Some(Connection::open(".vsb").unwrap())); + let root = collections_to_directories(&mut db.get_all_collections().unwrap()); + AppData { - browser: BrowserState::default(), + browser: BrowserState::new(root), browser_width: 300.0, table_height: 300.0, table_headers: headers, table_rows: rows, search_text: String::new(), - // database: Arc::new(Mutex::new(db)), } @@ -126,3 +127,45 @@ fn main() { .inner_size((1400, 800)) .run(); } + +fn collections_to_directories(collections: &mut Vec) -> Directory { + let mut hm: HashMap = HashMap::new(); + + for coll in collections { + hm.insert( + coll.id(), + Directory { + id: coll.id(), + parent_id: coll.parent_collection(), + name: coll.name().to_string(), + path: coll.path().clone(), + is_open: false, + num_files: 0, + shown: true, + match_indices: Vec::new(), + children: Vec::new(), + }, + ); + } + + fn children_of_collection( + map: &HashMap, + coll: CollectionID, + ) -> VecDeque { + map.values().filter(|v| v.parent_id == Some(coll)).map(|v| v.id).collect() + } + + let mut root_dir = hm.values().find(|v| v.parent_id.is_none()).unwrap().clone(); + + let mut collection_stack: VecDeque = children_of_collection(&hm, root_dir.id); + + while let Some(coll) = collection_stack.pop_front() { + let mut children = children_of_collection(&hm, coll); + collection_stack.append(&mut children); + + let coll_data = hm.get(&coll).unwrap().clone(); + root_dir.children.push(coll_data); + } + + root_dir +} diff --git a/src/panels/browser.rs b/src/panels/browser.rs index f37a3c8..bdeeee1 100644 --- a/src/panels/browser.rs +++ b/src/panels/browser.rs @@ -21,8 +21,6 @@ pub struct BrowserPanel { impl BrowserPanel { pub fn new(cx: &mut Context) -> Handle { Self { search_shown: true, tree_view: true }.build(cx, |cx| { - cx.emit(BrowserEvent::ViewAll); - Keymap::from(vec![( KeyChord::new(Modifiers::CTRL, Code::KeyF), KeymapEntry::new((), |cx| cx.emit(BrowserEvent::ToggleShowSearch)), diff --git a/src/state/browser.rs b/src/state/browser.rs index 79d939d..e545a70 100644 --- a/src/state/browser.rs +++ b/src/state/browser.rs @@ -1,4 +1,5 @@ -use crate::{app_data::AppData, database::prelude::*}; +use crate::app_data::AppData; +use crate::database::prelude::*; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; use std::{ @@ -7,6 +8,8 @@ use std::{ }; use vizia::prelude::*; +use self::browser_state_derived_lenses::libraries; + #[derive(Debug, Lens, Clone, Data)] pub struct BrowserState { pub libraries: Vec, @@ -17,9 +20,14 @@ pub struct BrowserState { pub search_case_sensitive: bool, } +impl BrowserState { + pub fn new(directory: Directory) -> Self { + Self { libraries: vec![directory], ..Default::default() } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum BrowserEvent { - ViewAll, Search(String), Select(PathBuf), Deselect, @@ -39,7 +47,7 @@ pub enum BrowserEvent { ShowList, } -#[derive(Debug, Clone, Data, Lens)] +#[derive(Debug, Clone, Data, Lens, Default)] pub struct Directory { pub id: CollectionID, pub parent_id: Option, @@ -68,14 +76,6 @@ impl Default for BrowserState { impl Model for BrowserState { fn event(&mut self, cx: &mut EventContext, event: &mut Event) { event.map(|browser_event, _| match browser_event { - // Temp: Load the assets directory for the treeview - BrowserEvent::ViewAll => { - let db_ref = AppData::database.get(cx); - let db = db_ref.lock().unwrap(); - let root = collections_to_directories(&mut db.get_all_collections().unwrap()); - self.libraries[0] = root; - } - BrowserEvent::Search(search_text) => { self.focused = None; self.search_text = search_text.clone(); @@ -301,45 +301,3 @@ fn is_collapsed<'a>(root: &'a Directory, dir: &PathBuf) -> bool { false } - -fn collections_to_directories(collections: &mut Vec) -> Directory { - let mut hm: HashMap = HashMap::new(); - - for coll in collections { - hm.insert( - coll.id(), - Directory { - id: coll.id(), - parent_id: coll.parent_collection(), - name: coll.name().to_string(), - path: coll.path().clone(), - is_open: false, - num_files: 0, - shown: true, - match_indices: Vec::new(), - children: Vec::new(), - }, - ); - } - - fn children_of_collection( - map: &HashMap, - coll: CollectionID, - ) -> VecDeque { - map.values().filter(|v| v.parent_id == Some(coll)).map(|v| v.id).collect() - } - - let mut root_dir = hm.values().find(|v| v.parent_id.is_none()).unwrap().clone(); - - let mut collection_stack: VecDeque = children_of_collection(&hm, root_dir.id); - - while let Some(coll) = collection_stack.pop_front() { - let mut children = children_of_collection(&hm, coll); - collection_stack.append(&mut children); - - let coll_data = hm.get(&coll).unwrap().clone(); - root_dir.children.push(coll_data); - } - - root_dir -} From cc0d0fe4ae2d6e6ad67989dbccda6a77b99daf10 Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Fri, 11 Aug 2023 01:34:43 +0200 Subject: [PATCH 03/17] fix db init crash --- src/database/collection.rs | 11 +++++++---- src/database/handler.rs | 6 +++++- src/database/sqls/schema.sql | 2 +- src/main.rs | 4 ++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/database/collection.rs b/src/database/collection.rs index dd3cae8..69ae935 100644 --- a/src/database/collection.rs +++ b/src/database/collection.rs @@ -76,12 +76,15 @@ impl DatabaseCollectionHandler for Database { connection.prepare("SELECT id, parent_collection, name, path FROM collections")?; let collections = query.query_map([], |row| { - let path: String = row.get(3)?; Ok(Collection::new( row.get(0)?, row.get(1)?, row.get(2)?, - Path::new(&path).to_path_buf(), + Path::new(&{ + let s: String = row.get(3)?; + s + }) + .to_path_buf(), )) })?; @@ -119,8 +122,8 @@ impl DatabaseCollectionHandler for Database { fn insert_collection(&mut self, collection: Collection) -> Result<(), DatabaseError> { if let Some(connection) = self.get_connection() { connection.execute( - "INSERT INTO collections (id, parent_collection, name) VALUES (?1, ?2, ?3)", - (collection.id, collection.parent_collection, collection.name), + "INSERT INTO collections (id, parent_collection, name, path) VALUES (?1, ?2, ?3, ?4)", + (collection.id, collection.parent_collection, collection.name, collection.path.to_str().unwrap()), )?; } diff --git a/src/database/handler.rs b/src/database/handler.rs index 3a6e352..7aa88cd 100644 --- a/src/database/handler.rs +++ b/src/database/handler.rs @@ -39,8 +39,8 @@ impl Database { s.open_connection()?; + s.initialize_empty_database(); // if !database_exists { - // s.initialize_empty_database(); // } else { // s.update_database(); // } @@ -68,6 +68,10 @@ impl Database { let mut colls = collections.borrow_mut(); let name = path.file_name().unwrap().to_str().unwrap().to_string(); + if name == ".vsb-meta" { + return; + } + let id = collection_count.load(std::sync::atomic::Ordering::Relaxed); collection_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); let parent_id = match parent_path.is_none() { diff --git a/src/database/sqls/schema.sql b/src/database/sqls/schema.sql index fe5e5c9..e700237 100644 --- a/src/database/sqls/schema.sql +++ b/src/database/sqls/schema.sql @@ -1,8 +1,8 @@ CREATE TABLE collections ( id integer UNIQUE PRIMARY KEY, parent_collection integer NULL, - path nvarchar(255), name nvarchar(255), + path nvarchar(255), FOREIGN KEY(parent_collection) REFERENCES collections(id) ); diff --git a/src/main.rs b/src/main.rs index f2bdcc9..e3f0f2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use app_data::AppData; use rusqlite::Connection; use std::{ collections::{HashMap, VecDeque}, + path::Path, sync::{Arc, Mutex}, }; use views::smart_table::SmartTable; @@ -71,8 +72,7 @@ fn main() { }) .collect::>(); - let mut db = - Database::from_connection("test_files/", Some(Connection::open(".vsb").unwrap())); + let mut db = Database::from_directory(Path::new("test_files/").to_path_buf()).unwrap(); let root = collections_to_directories(&mut db.get_all_collections().unwrap()); From 5f538c7617d67a2a4115f13c52d4deb31c1bea89 Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Fri, 11 Aug 2023 01:38:46 +0200 Subject: [PATCH 04/17] the ? crash fix --- src/database/store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/store.rs b/src/database/store.rs index 7f72de5..74880ce 100644 --- a/src/database/store.rs +++ b/src/database/store.rs @@ -72,7 +72,7 @@ pub trait DatabaseStore { // fn initialize_or_create_stores(&self) -> Result<(), DatabaseError> { - create_dir(self.get_meta_directory_path())?; + create_dir(self.get_meta_directory_path()); Ok(()) } From ca8092068e7cf6a56afc21d529b08afcdd68cf64 Mon Sep 17 00:00:00 2001 From: George Atkinson Date: Fri, 11 Aug 2023 01:39:58 +0100 Subject: [PATCH 05/17] Start to modify table to show `AudioFile` data --- src/app_data.rs | 15 +++++++++++-- src/app_event.rs | 1 - src/database/audio_file.rs | 6 ++++-- src/main.rs | 44 +++++++++++++++++++++----------------- src/panels/browser.rs | 9 +++++--- src/panels/samples.rs | 7 +++--- src/state/browser.rs | 7 +++--- src/views/smart_table.rs | 19 ++++++++-------- 8 files changed, 64 insertions(+), 44 deletions(-) delete mode 100644 src/app_event.rs diff --git a/src/app_data.rs b/src/app_data.rs index 12b3f36..30843d2 100644 --- a/src/app_data.rs +++ b/src/app_data.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; use vizia::prelude::*; use crate::{ - database::prelude::Database, + database::prelude::{AudioFile, CollectionID, Database, DatabaseAudioFileHandler}, state::browser::{BrowserState, Directory}, }; @@ -14,13 +14,14 @@ pub struct AppData { pub browser_width: f32, pub table_height: f32, pub table_headers: Vec, - pub table_rows: Vec>, + pub table_rows: Vec, pub search_text: String, } pub enum AppEvent { SetBrowserWidth(f32), SetTableHeight(f32), + ViewCollection(CollectionID), } impl Model for AppData { @@ -30,6 +31,16 @@ impl Model for AppData { event.map(|app_event, _| match app_event { AppEvent::SetBrowserWidth(width) => self.browser_width = *width, AppEvent::SetTableHeight(height) => self.table_height = *height, + AppEvent::ViewCollection(id) => { + println!("selected: {}", id); + if let Ok(db) = self.database.lock() { + if let Ok(audio_files) = db.get_all_audio_files() { + println!("ALL {}", audio_files.len()); + self.table_rows = audio_files; + } + } + println!("num rows: {}", self.table_rows.len()); + } }); } } diff --git a/src/app_event.rs b/src/app_event.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/app_event.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/database/audio_file.rs b/src/database/audio_file.rs index 7394db3..093a40f 100644 --- a/src/database/audio_file.rs +++ b/src/database/audio_file.rs @@ -1,11 +1,13 @@ +use vizia::prelude::*; + use super::{CollectionID, Database, DatabaseConnectionHandle, DatabaseError}; use serde::{Deserialize, Serialize}; pub type AudioFileID = usize; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Data, Lens)] pub struct AudioFile { id: AudioFileID, - name: String, + pub name: String, collection: CollectionID, duration: f32, sample_rate: f32, diff --git a/src/main.rs b/src/main.rs index e3f0f2f..6159d45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,34 +54,38 @@ fn main() { .map(|v| v.to_string()) .collect::>(); - let rows = (0..20) - .map(|row| { - vec![ - &format!("MSL_snare_{:02}", row), - "?", - "5.3 sec", - "44100", - "24", - "?", - "?", - "2.5MB", - ] - .iter_mut() - .map(|v| v.to_string()) - .collect::>() - }) - .collect::>(); - - let mut db = Database::from_directory(Path::new("test_files/").to_path_buf()).unwrap(); + // let rows = (0..20) + // .map(|row| { + // vec![ + // &format!("MSL_snare_{:02}", row), + // "?", + // "5.3 sec", + // "44100", + // "24", + // "?", + // "?", + // "2.5MB", + // ] + // .iter_mut() + // .map(|v| v.to_string()) + // .collect::>() + // }) + // .collect::>(); + + let mut db = + Database::from_directory(Path::new("the-libre-sample-pack/").to_path_buf()).unwrap(); let root = collections_to_directories(&mut db.get_all_collections().unwrap()); + let audio_files = db.get_all_audio_files().unwrap().len(); + println!("num: {}", audio_files); + AppData { browser: BrowserState::new(root), browser_width: 300.0, table_height: 300.0, table_headers: headers, - table_rows: rows, + table_rows: Vec::new(), search_text: String::new(), // database: Arc::new(Mutex::new(db)), diff --git a/src/panels/browser.rs b/src/panels/browser.rs index bdeeee1..f08e092 100644 --- a/src/panels/browser.rs +++ b/src/panels/browser.rs @@ -8,6 +8,7 @@ use vizia::icons::{ use vizia::prelude::*; use crate::app_data::AppData; +use crate::database::prelude::CollectionID; use crate::state::browser::directory_derived_lenses::children; use crate::state::browser::*; use crate::views::{ToggleButton, ToggleButtonModifiers}; @@ -192,6 +193,7 @@ where pub struct DirectoryItem { path: PathBuf, + collection: CollectionID, } impl DirectoryItem { @@ -202,7 +204,8 @@ impl DirectoryItem { focused: impl Lens, path: PathBuf, ) -> Handle { - Self { path: path.clone() } + let id = root.get(cx).id; + Self { path: path.clone(), collection: id } .build(cx, |cx| { // Arrow Icon Icon::new(cx, ICON_CHEVRON_DOWN) @@ -219,7 +222,7 @@ impl DirectoryItem { .on_press(move |cx| { cx.emit(BrowserEvent::ToggleDirectory(path.clone())); cx.emit(BrowserEvent::SetFocused(Some(path.clone()))); - cx.emit(BrowserEvent::Select(path.clone())); + cx.emit(BrowserEvent::Select(path.clone(), id)); }) .cursor(CursorIcon::Hand); @@ -287,7 +290,7 @@ impl View for DirectoryItem { if cx.modifiers().contains(Modifiers::CTRL) { cx.emit(BrowserEvent::AddSelection(self.path.clone())); } else { - cx.emit(BrowserEvent::Select(self.path.clone())); + cx.emit(BrowserEvent::Select(self.path.clone(), self.collection)); } } diff --git a/src/panels/samples.rs b/src/panels/samples.rs index cfa1c94..3278748 100644 --- a/src/panels/samples.rs +++ b/src/panels/samples.rs @@ -2,6 +2,7 @@ use vizia::icons::{ICON_LIST_SEARCH, ICON_SEARCH}; use vizia::prelude::*; use crate::app_data::AppData; +use crate::database::prelude::AudioFile; use crate::views::SmartTable; pub struct SamplesPanel {} @@ -31,13 +32,13 @@ impl SamplesPanel { .height(Auto) .class("header"); - SmartTable::new(cx, AppData::table_headers, AppData::table_rows, |cx, item| { - Label::new(cx, item) + SmartTable::new(cx, AppData::table_headers, AppData::table_rows, |cx, row, index| { + Label::new(cx, row.then(AudioFile::name)) .width(Stretch(1.0)) .border_color(Color::bisque()) // .border_width(Pixels(1.0)) .child_space(Stretch(1.0)) - .child_left(if item.idx() == 0 { Pixels(4.0) } else { Stretch(1.0) }); + .child_left(if index == 0 { Pixels(4.0) } else { Stretch(1.0) }); }); }) } diff --git a/src/state/browser.rs b/src/state/browser.rs index e545a70..833aea6 100644 --- a/src/state/browser.rs +++ b/src/state/browser.rs @@ -1,4 +1,4 @@ -use crate::app_data::AppData; +use crate::app_data::{AppData, AppEvent}; use crate::database::prelude::*; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; @@ -29,7 +29,7 @@ impl BrowserState { #[derive(Debug, Clone, PartialEq)] pub enum BrowserEvent { Search(String), - Select(PathBuf), + Select(PathBuf, CollectionID), Deselect, AddSelection(PathBuf), SetFocused(Option), @@ -131,9 +131,10 @@ impl Model for BrowserState { } } - BrowserEvent::Select(path) => { + BrowserEvent::Select(path, collection) => { self.selected.clear(); self.selected.insert(path.clone()); + cx.emit(AppEvent::ViewCollection(*collection)); } BrowserEvent::AddSelection(path) => { diff --git a/src/views/smart_table.rs b/src/views/smart_table.rs index 3a99f25..718e4e9 100644 --- a/src/views/smart_table.rs +++ b/src/views/smart_table.rs @@ -31,7 +31,7 @@ pub struct SmartTable { } impl SmartTable { - pub fn new( + pub fn new( cx: &mut Context, headers: L1, rows: L2, @@ -42,10 +42,10 @@ impl SmartTable { L2: Lens, ::Target: std::ops::Deref, ::Target: std::ops::Deref, - R: Data + std::ops::Deref, + R: Data, T1: Data + ToStringLocalized, - T2: Data + ToStringLocalized, - F: 'static + Copy + Fn(&mut Context, Index, T2>), + // T2: Data + ToStringLocalized, + F: 'static + Copy + Fn(&mut Context, Index, usize), { let num_cols = headers.map(|h| h.len()).get(cx); @@ -84,11 +84,12 @@ impl SmartTable { }) .height(Auto); // - VirtualList::new(cx, rows, 26.0, move |cx, row_index, row| { + List::new(cx, rows, move |cx, row_index, row| { + println!("rebuild virtual list"); // - List::new(cx, row, move |cx, col_index, item| { + List::new(cx, headers, move |cx, col_index, _| { HStack::new(cx, move |cx| { - (content)(cx, item); + (content)(cx, row, col_index); }) .width(Self::widths.index(col_index)) .height(Auto); @@ -96,9 +97,7 @@ impl SmartTable { .class("row") .toggle_class("odd", row_index % 2 == 0) .width(Stretch(1.0)) - .layout_type(LayoutType::Row) - - // Element::new(cx).background_color(Color::blue()).width(Stretch(1.0)) + .layout_type(LayoutType::Row); }) .width(Stretch(1.0)); From c43906a0773af23ccbb675ba1ec11ad75334b4d4 Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Fri, 11 Aug 2023 12:40:43 +0200 Subject: [PATCH 06/17] fix recursive browser --- src/database/error.rs | 2 +- src/database/handler.rs | 2 +- src/main.rs | 85 +++++++++++++++++++++++++++++++---------- src/state/browser.rs | 1 + 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/database/error.rs b/src/database/error.rs index b7f4630..8c03f97 100644 --- a/src/database/error.rs +++ b/src/database/error.rs @@ -1,7 +1,7 @@ #[derive(Debug)] pub enum DatabaseError { ConnectionClosed, - PathNotDirecotry, + PathNotDirectory, RusqliteError(rusqlite::Error), IOError(std::io::Error), } diff --git a/src/database/handler.rs b/src/database/handler.rs index 7aa88cd..ce3686b 100644 --- a/src/database/handler.rs +++ b/src/database/handler.rs @@ -28,7 +28,7 @@ impl Database { pub fn from_directory(path: PathBuf) -> Result { // Check file is directory if !directory_exists(&path) { - return Err(DatabaseError::PathNotDirecotry); + return Err(DatabaseError::PathNotDirectory); } // Open connection diff --git a/src/main.rs b/src/main.rs index 6159d45..7248d27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,18 @@ #![allow(unused)] // Disable stupid warnings for now use app_data::AppData; +use itertools::Itertools; use rusqlite::Connection; use std::{ collections::{HashMap, VecDeque}, - path::Path, + path::{Path, PathBuf}, + rc::Rc, sync::{Arc, Mutex}, }; use views::smart_table::SmartTable; use vizia::{ icons::{ICON_LIST_SEARCH, ICON_SEARCH}, - prelude::*, + prelude::{GenerationalId, *}, }; mod state; @@ -72,8 +74,7 @@ fn main() { // }) // .collect::>(); - let mut db = - Database::from_directory(Path::new("the-libre-sample-pack/").to_path_buf()).unwrap(); + let mut db = Database::from_directory(Path::new("test_files/").to_path_buf()).unwrap(); let root = collections_to_directories(&mut db.get_all_collections().unwrap()); @@ -132,44 +133,86 @@ fn main() { .run(); } +#[derive(Clone)] +struct RecursiveInner { + id: CollectionID, + parent_id: Option, + name: String, + path: PathBuf, + children: Vec>>, +} + +impl RecursiveInner { + fn to_directory(inner: &mut RecursiveInner) -> Directory { + let children = inner + .children + .iter_mut() + .map(|child| RecursiveInner::to_directory(&mut child.lock().unwrap())) + .collect(); + + Directory { + id: inner.id, + parent_id: inner.parent_id, + name: inner.name.clone(), + path: inner.path.clone(), + children, + is_open: false, + shown: true, + ..Default::default() + } + } +} + fn collections_to_directories(collections: &mut Vec) -> Directory { - let mut hm: HashMap = HashMap::new(); + let mut hm: HashMap>> = HashMap::new(); for coll in collections { hm.insert( coll.id(), - Directory { + Rc::new(Mutex::new(RecursiveInner { id: coll.id(), parent_id: coll.parent_collection(), name: coll.name().to_string(), path: coll.path().clone(), - is_open: false, - num_files: 0, - shown: true, - match_indices: Vec::new(), children: Vec::new(), - }, + })), ); } fn children_of_collection( - map: &HashMap, + map: &HashMap>>, coll: CollectionID, ) -> VecDeque { - map.values().filter(|v| v.parent_id == Some(coll)).map(|v| v.id).collect() + map.values() + .filter(|v| v.lock().unwrap().parent_id == Some(coll)) + .map(|v| v.lock().unwrap().id) + .collect() } - let mut root_dir = hm.values().find(|v| v.parent_id.is_none()).unwrap().clone(); + let mut root_dir = hm.values().find(|v| v.lock().unwrap().parent_id.is_none()).unwrap(); + let mut directory_stack: VecDeque>> = VecDeque::new(); + directory_stack.push_back(root_dir.clone()); + + while let Some(mut coll) = directory_stack.pop_front() { + let id: usize = coll.lock().unwrap().id; + let mut children = children_of_collection(&hm, id); + let mut children_dir: VecDeque>> = VecDeque::new(); + children.iter_mut().for_each(|v| children_dir.push_back(hm.get(&v).unwrap().clone())); - let mut collection_stack: VecDeque = children_of_collection(&hm, root_dir.id); + for mut child_ref in children_dir { + let mut child = child_ref.lock().unwrap(); - while let Some(coll) = collection_stack.pop_front() { - let mut children = children_of_collection(&hm, coll); - collection_stack.append(&mut children); + // Each child inside the current focused directory appends to the recursive structure + coll.lock().unwrap().children.push(child_ref.clone()); - let coll_data = hm.get(&coll).unwrap().clone(); - root_dir.children.push(coll_data); + // Reference each of those children to iterate in the stack + directory_stack.push_back(child_ref.clone()); + } } - root_dir + // Transform root dir to Directory + let mut root_directory = root_dir.lock().unwrap().clone(); + let directory = RecursiveInner::to_directory(&mut root_directory); + + directory } diff --git a/src/state/browser.rs b/src/state/browser.rs index 833aea6..b2df907 100644 --- a/src/state/browser.rs +++ b/src/state/browser.rs @@ -1,5 +1,6 @@ use crate::app_data::{AppData, AppEvent}; use crate::database::prelude::*; +use crate::RecursiveInner; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; use std::{ From dad014227990edbfd11ee2f076ef069906374118 Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Fri, 11 Aug 2023 12:59:46 +0200 Subject: [PATCH 07/17] recursive benchmark --- src/database/tests/mod.rs | 1 + src/database/tests/recursive_benchmark.rs | 116 ++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/database/tests/recursive_benchmark.rs diff --git a/src/database/tests/mod.rs b/src/database/tests/mod.rs index d18dd25..483ff9c 100644 --- a/src/database/tests/mod.rs +++ b/src/database/tests/mod.rs @@ -4,6 +4,7 @@ pub mod get_audio_files; pub mod get_collections; pub mod get_tags; pub mod insert; +pub mod recursive_benchmark; const TEST_DIRECTORY: &str = "test_files/"; const TEST_META_DIRECTORY: &str = "test_files/.vsb-meta/"; diff --git a/src/database/tests/recursive_benchmark.rs b/src/database/tests/recursive_benchmark.rs new file mode 100644 index 0000000..850122d --- /dev/null +++ b/src/database/tests/recursive_benchmark.rs @@ -0,0 +1,116 @@ +use crate::{database::prelude::*, state::browser::Directory, RecursiveInner}; +use std::{ + collections::{HashMap, VecDeque}, + path::Path, + rc::Rc, + sync::Mutex, + time::Instant, +}; + +#[test] +fn test_recursive_algorithm() { + let mut db = Database::from_directory(Path::new("test_files/").to_path_buf()).unwrap(); + + let collections = db.get_all_collections().unwrap(); + let root = collections.iter().find(|v| v.parent_collection().is_none()).unwrap(); + + for n in 0..10 { + let pre = Instant::now(); + + let _ = implicit_collections_to_directories(&collections); + + let after = Instant::now(); + + let elapsed = after - pre; + + println!("(TRY {}) Implicit algorithm time: {} µs", n, elapsed.as_micros()); + } + + for n in 0..10 { + let pre = Instant::now(); + + let _ = recursive_collections_to_directories(&collections, root.clone()); + + let after = Instant::now(); + + let elapsed = after - pre; + + println!("(TRY {}) Recursive algorithm time: {} µs", n, elapsed.as_micros()); + } +} + +fn implicit_collections_to_directories(collections: &Vec) -> Directory { + let mut hm: HashMap>> = HashMap::new(); + + for coll in collections { + hm.insert( + coll.id(), + Rc::new(Mutex::new(RecursiveInner { + id: coll.id(), + parent_id: coll.parent_collection(), + name: coll.name().to_string(), + path: coll.path().clone(), + children: Vec::new(), + })), + ); + } + + fn children_of_collection( + map: &HashMap>>, + coll: CollectionID, + ) -> VecDeque { + map.values() + .filter(|v| v.lock().unwrap().parent_id == Some(coll)) + .map(|v| v.lock().unwrap().id) + .collect() + } + + let mut root_dir = hm.values().find(|v| v.lock().unwrap().parent_id.is_none()).unwrap(); + let mut directory_stack: VecDeque>> = VecDeque::new(); + directory_stack.push_back(root_dir.clone()); + + while let Some(mut coll) = directory_stack.pop_front() { + let id: usize = coll.lock().unwrap().id; + let mut children = children_of_collection(&hm, id); + let mut children_dir: VecDeque>> = VecDeque::new(); + children.iter_mut().for_each(|v| children_dir.push_back(hm.get(&v).unwrap().clone())); + + for mut child_ref in children_dir { + let mut child = child_ref.lock().unwrap(); + + // Each child inside the current focused directory appends to the recursive structure + coll.lock().unwrap().children.push(child_ref.clone()); + + // Reference each of those children to iterate in the stack + directory_stack.push_back(child_ref.clone()); + } + } + + // Transform root dir to Directory + let mut root_directory = root_dir.lock().unwrap().clone(); + let directory = RecursiveInner::to_directory(&mut root_directory); + + directory +} + +fn recursive_collections_to_directories( + collections: &Vec, + current: Collection, +) -> Directory { + let children: Vec = collections + .iter() + .filter(|v| v.parent_collection() == Some(current.id())) + .map(|v| recursive_collections_to_directories(collections, v.clone())) + .collect(); + + Directory { + id: current.id(), + parent_id: current.parent_collection(), + name: current.name().to_string(), + path: current.path().clone(), + shown: true, + is_open: false, + children, + ..Default::default() + } +} From a88ed8eb0c26077f6f98dcba9020716726c3cd35 Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Fri, 11 Aug 2023 13:04:09 +0200 Subject: [PATCH 08/17] result-checking --- src/database/tests/recursive_benchmark.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/database/tests/recursive_benchmark.rs b/src/database/tests/recursive_benchmark.rs index 850122d..2f6399f 100644 --- a/src/database/tests/recursive_benchmark.rs +++ b/src/database/tests/recursive_benchmark.rs @@ -17,10 +17,14 @@ fn test_recursive_algorithm() { for n in 0..10 { let pre = Instant::now(); - let _ = implicit_collections_to_directories(&collections); + let result = implicit_collections_to_directories(&collections); let after = Instant::now(); + if n == 0 { + print_recursive_structure(&result, 0); + } + let elapsed = after - pre; println!("(TRY {}) Implicit algorithm time: {} µs", n, elapsed.as_micros()); @@ -29,10 +33,14 @@ fn test_recursive_algorithm() { for n in 0..10 { let pre = Instant::now(); - let _ = recursive_collections_to_directories(&collections, root.clone()); + let result = recursive_collections_to_directories(&collections, root.clone()); let after = Instant::now(); + if n == 0 { + print_recursive_structure(&result, 0); + } + let elapsed = after - pre; println!("(TRY {}) Recursive algorithm time: {} µs", n, elapsed.as_micros()); @@ -114,3 +122,10 @@ fn recursive_collections_to_directories( ..Default::default() } } + +fn print_recursive_structure(dir: &Directory, depth: usize) { + println!("{} - ({}) {}", " ".repeat(depth), dir.id, dir.name); + for child in dir.children.iter() { + print_recursive_structure(child, depth + 1); + } +} From 9abf5d898a20f8dcd7bc0afb234a740d33291ea3 Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Fri, 11 Aug 2023 13:08:58 +0200 Subject: [PATCH 09/17] change to recursive --- src/database/tests/mod.rs | 1 - src/database/tests/recursive_benchmark.rs | 32 ++++++- src/main.rs | 102 +++++----------------- src/state/browser.rs | 1 - 4 files changed, 51 insertions(+), 85 deletions(-) diff --git a/src/database/tests/mod.rs b/src/database/tests/mod.rs index 483ff9c..d18dd25 100644 --- a/src/database/tests/mod.rs +++ b/src/database/tests/mod.rs @@ -4,7 +4,6 @@ pub mod get_audio_files; pub mod get_collections; pub mod get_tags; pub mod insert; -pub mod recursive_benchmark; const TEST_DIRECTORY: &str = "test_files/"; const TEST_META_DIRECTORY: &str = "test_files/.vsb-meta/"; diff --git a/src/database/tests/recursive_benchmark.rs b/src/database/tests/recursive_benchmark.rs index 2f6399f..e803648 100644 --- a/src/database/tests/recursive_benchmark.rs +++ b/src/database/tests/recursive_benchmark.rs @@ -1,4 +1,4 @@ -use crate::{database::prelude::*, state::browser::Directory, RecursiveInner}; +use crate::{database::prelude::*, state::browser::Directory}; use std::{ collections::{HashMap, VecDeque}, path::Path, @@ -7,6 +7,36 @@ use std::{ time::Instant, }; +#[derive(Clone)] +struct RecursiveInner { + id: CollectionID, + parent_id: Option, + name: String, + path: PathBuf, + children: Vec>>, +} + +impl RecursiveInner { + fn to_directory(inner: &mut RecursiveInner) -> Directory { + let children = inner + .children + .iter_mut() + .map(|child| RecursiveInner::to_directory(&mut child.lock().unwrap())) + .collect(); + + Directory { + id: inner.id, + parent_id: inner.parent_id, + name: inner.name.clone(), + path: inner.path.clone(), + children, + is_open: false, + shown: true, + ..Default::default() + } + } +} + #[test] fn test_recursive_algorithm() { let mut db = Database::from_directory(Path::new("test_files/").to_path_buf()).unwrap(); diff --git a/src/main.rs b/src/main.rs index 7248d27..cc5dd29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,10 @@ fn main() { let mut db = Database::from_directory(Path::new("test_files/").to_path_buf()).unwrap(); - let root = collections_to_directories(&mut db.get_all_collections().unwrap()); + let collections = db.get_all_collections().unwrap(); + let root = collections.iter().find(|v| v.parent_collection().is_none()).unwrap(); + + let root = collections_to_directories(&collections, root.clone()); let audio_files = db.get_all_audio_files().unwrap().len(); println!("num: {}", audio_files); @@ -133,86 +136,21 @@ fn main() { .run(); } -#[derive(Clone)] -struct RecursiveInner { - id: CollectionID, - parent_id: Option, - name: String, - path: PathBuf, - children: Vec>>, -} - -impl RecursiveInner { - fn to_directory(inner: &mut RecursiveInner) -> Directory { - let children = inner - .children - .iter_mut() - .map(|child| RecursiveInner::to_directory(&mut child.lock().unwrap())) - .collect(); - - Directory { - id: inner.id, - parent_id: inner.parent_id, - name: inner.name.clone(), - path: inner.path.clone(), - children, - is_open: false, - shown: true, - ..Default::default() - } - } -} - -fn collections_to_directories(collections: &mut Vec) -> Directory { - let mut hm: HashMap>> = HashMap::new(); - - for coll in collections { - hm.insert( - coll.id(), - Rc::new(Mutex::new(RecursiveInner { - id: coll.id(), - parent_id: coll.parent_collection(), - name: coll.name().to_string(), - path: coll.path().clone(), - children: Vec::new(), - })), - ); - } - - fn children_of_collection( - map: &HashMap>>, - coll: CollectionID, - ) -> VecDeque { - map.values() - .filter(|v| v.lock().unwrap().parent_id == Some(coll)) - .map(|v| v.lock().unwrap().id) - .collect() +fn collections_to_directories(collections: &Vec, current: Collection) -> Directory { + let children: Vec = collections + .iter() + .filter(|v| v.parent_collection() == Some(current.id())) + .map(|v| collections_to_directories(collections, v.clone())) + .collect(); + + Directory { + id: current.id(), + parent_id: current.parent_collection(), + name: current.name().to_string(), + path: current.path().clone(), + shown: true, + is_open: false, + children, + ..Default::default() } - - let mut root_dir = hm.values().find(|v| v.lock().unwrap().parent_id.is_none()).unwrap(); - let mut directory_stack: VecDeque>> = VecDeque::new(); - directory_stack.push_back(root_dir.clone()); - - while let Some(mut coll) = directory_stack.pop_front() { - let id: usize = coll.lock().unwrap().id; - let mut children = children_of_collection(&hm, id); - let mut children_dir: VecDeque>> = VecDeque::new(); - children.iter_mut().for_each(|v| children_dir.push_back(hm.get(&v).unwrap().clone())); - - for mut child_ref in children_dir { - let mut child = child_ref.lock().unwrap(); - - // Each child inside the current focused directory appends to the recursive structure - coll.lock().unwrap().children.push(child_ref.clone()); - - // Reference each of those children to iterate in the stack - directory_stack.push_back(child_ref.clone()); - } - } - - // Transform root dir to Directory - let mut root_directory = root_dir.lock().unwrap().clone(); - let directory = RecursiveInner::to_directory(&mut root_directory); - - directory } diff --git a/src/state/browser.rs b/src/state/browser.rs index b2df907..833aea6 100644 --- a/src/state/browser.rs +++ b/src/state/browser.rs @@ -1,6 +1,5 @@ use crate::app_data::{AppData, AppEvent}; use crate::database::prelude::*; -use crate::RecursiveInner; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; use std::{ From 1ccc90f811cfff89da1246d6cb8f6ec73dc1fe7c Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Fri, 11 Aug 2023 14:04:44 +0200 Subject: [PATCH 10/17] remove tests --- src/database/audio_file.rs | 4 +- src/database/tests/recursive_benchmark.rs | 161 ---------------------- src/main.rs | 17 ++- 3 files changed, 16 insertions(+), 166 deletions(-) delete mode 100644 src/database/tests/recursive_benchmark.rs diff --git a/src/database/audio_file.rs b/src/database/audio_file.rs index 093a40f..f79f1b1 100644 --- a/src/database/audio_file.rs +++ b/src/database/audio_file.rs @@ -6,9 +6,9 @@ use serde::{Deserialize, Serialize}; pub type AudioFileID = usize; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Data, Lens)] pub struct AudioFile { - id: AudioFileID, + pub id: AudioFileID, pub name: String, - collection: CollectionID, + pub collection: CollectionID, duration: f32, sample_rate: f32, bit_depth: f32, diff --git a/src/database/tests/recursive_benchmark.rs b/src/database/tests/recursive_benchmark.rs deleted file mode 100644 index e803648..0000000 --- a/src/database/tests/recursive_benchmark.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::{database::prelude::*, state::browser::Directory}; -use std::{ - collections::{HashMap, VecDeque}, - path::Path, - rc::Rc, - sync::Mutex, - time::Instant, -}; - -#[derive(Clone)] -struct RecursiveInner { - id: CollectionID, - parent_id: Option, - name: String, - path: PathBuf, - children: Vec>>, -} - -impl RecursiveInner { - fn to_directory(inner: &mut RecursiveInner) -> Directory { - let children = inner - .children - .iter_mut() - .map(|child| RecursiveInner::to_directory(&mut child.lock().unwrap())) - .collect(); - - Directory { - id: inner.id, - parent_id: inner.parent_id, - name: inner.name.clone(), - path: inner.path.clone(), - children, - is_open: false, - shown: true, - ..Default::default() - } - } -} - -#[test] -fn test_recursive_algorithm() { - let mut db = Database::from_directory(Path::new("test_files/").to_path_buf()).unwrap(); - - let collections = db.get_all_collections().unwrap(); - let root = collections.iter().find(|v| v.parent_collection().is_none()).unwrap(); - - for n in 0..10 { - let pre = Instant::now(); - - let result = implicit_collections_to_directories(&collections); - - let after = Instant::now(); - - if n == 0 { - print_recursive_structure(&result, 0); - } - - let elapsed = after - pre; - - println!("(TRY {}) Implicit algorithm time: {} µs", n, elapsed.as_micros()); - } - - for n in 0..10 { - let pre = Instant::now(); - - let result = recursive_collections_to_directories(&collections, root.clone()); - - let after = Instant::now(); - - if n == 0 { - print_recursive_structure(&result, 0); - } - - let elapsed = after - pre; - - println!("(TRY {}) Recursive algorithm time: {} µs", n, elapsed.as_micros()); - } -} - -fn implicit_collections_to_directories(collections: &Vec) -> Directory { - let mut hm: HashMap>> = HashMap::new(); - - for coll in collections { - hm.insert( - coll.id(), - Rc::new(Mutex::new(RecursiveInner { - id: coll.id(), - parent_id: coll.parent_collection(), - name: coll.name().to_string(), - path: coll.path().clone(), - children: Vec::new(), - })), - ); - } - - fn children_of_collection( - map: &HashMap>>, - coll: CollectionID, - ) -> VecDeque { - map.values() - .filter(|v| v.lock().unwrap().parent_id == Some(coll)) - .map(|v| v.lock().unwrap().id) - .collect() - } - - let mut root_dir = hm.values().find(|v| v.lock().unwrap().parent_id.is_none()).unwrap(); - let mut directory_stack: VecDeque>> = VecDeque::new(); - directory_stack.push_back(root_dir.clone()); - - while let Some(mut coll) = directory_stack.pop_front() { - let id: usize = coll.lock().unwrap().id; - let mut children = children_of_collection(&hm, id); - let mut children_dir: VecDeque>> = VecDeque::new(); - children.iter_mut().for_each(|v| children_dir.push_back(hm.get(&v).unwrap().clone())); - - for mut child_ref in children_dir { - let mut child = child_ref.lock().unwrap(); - - // Each child inside the current focused directory appends to the recursive structure - coll.lock().unwrap().children.push(child_ref.clone()); - - // Reference each of those children to iterate in the stack - directory_stack.push_back(child_ref.clone()); - } - } - - // Transform root dir to Directory - let mut root_directory = root_dir.lock().unwrap().clone(); - let directory = RecursiveInner::to_directory(&mut root_directory); - - directory -} - -fn recursive_collections_to_directories( - collections: &Vec, - current: Collection, -) -> Directory { - let children: Vec = collections - .iter() - .filter(|v| v.parent_collection() == Some(current.id())) - .map(|v| recursive_collections_to_directories(collections, v.clone())) - .collect(); - - Directory { - id: current.id(), - parent_id: current.parent_collection(), - name: current.name().to_string(), - path: current.path().clone(), - shown: true, - is_open: false, - children, - ..Default::default() - } -} - -fn print_recursive_structure(dir: &Directory, depth: usize) { - println!("{} - ({}) {}", " ".repeat(depth), dir.id, dir.name); - for child in dir.children.iter() { - print_recursive_structure(child, depth + 1); - } -} diff --git a/src/main.rs b/src/main.rs index cc5dd29..4fef6da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,9 +77,10 @@ fn main() { let mut db = Database::from_directory(Path::new("test_files/").to_path_buf()).unwrap(); let collections = db.get_all_collections().unwrap(); + let audio_files = db.get_all_audio_files().unwrap(); let root = collections.iter().find(|v| v.parent_collection().is_none()).unwrap(); - let root = collections_to_directories(&collections, root.clone()); + let root = collections_to_directories(&collections, &audio_files, root.clone()); let audio_files = db.get_all_audio_files().unwrap().len(); println!("num: {}", audio_files); @@ -136,13 +137,22 @@ fn main() { .run(); } -fn collections_to_directories(collections: &Vec, current: Collection) -> Directory { +fn collections_to_directories( + collections: &Vec, + audio_files: &Vec, + current: Collection, +) -> Directory { let children: Vec = collections .iter() .filter(|v| v.parent_collection() == Some(current.id())) - .map(|v| collections_to_directories(collections, v.clone())) + .map(|v| collections_to_directories(collections, audio_files, v.clone())) .collect(); + let afs: Vec<&AudioFile> = + audio_files.iter().filter(|v| v.collection == current.id()).collect(); + + println!("{:?} {}", current, afs.len()); + Directory { id: current.id(), parent_id: current.parent_collection(), @@ -150,6 +160,7 @@ fn collections_to_directories(collections: &Vec, current: Collection path: current.path().clone(), shown: true, is_open: false, + num_files: children.iter().map(|v| v.num_files).sum::() + afs.len(), children, ..Default::default() } From 3e52c384532540bb78ff86dc2c9da2f29a3e7a05 Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Fri, 11 Aug 2023 14:45:47 +0200 Subject: [PATCH 11/17] audio file insert bug fix --- src/database/audio_file.rs | 1 - src/main.rs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/database/audio_file.rs b/src/database/audio_file.rs index f79f1b1..a50ed0c 100644 --- a/src/database/audio_file.rs +++ b/src/database/audio_file.rs @@ -103,7 +103,6 @@ impl DatabaseAudioFileHandler for Database { audio_file.bit_depth, audio_file.bpm, audio_file.key, - audio_file.key, audio_file.size, ), )?; diff --git a/src/main.rs b/src/main.rs index 4fef6da..61fc07e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,12 +78,12 @@ fn main() { let collections = db.get_all_collections().unwrap(); let audio_files = db.get_all_audio_files().unwrap(); + let root = collections.iter().find(|v| v.parent_collection().is_none()).unwrap(); let root = collections_to_directories(&collections, &audio_files, root.clone()); let audio_files = db.get_all_audio_files().unwrap().len(); - println!("num: {}", audio_files); AppData { browser: BrowserState::new(root), @@ -151,8 +151,6 @@ fn collections_to_directories( let afs: Vec<&AudioFile> = audio_files.iter().filter(|v| v.collection == current.id()).collect(); - println!("{:?} {}", current, afs.len()); - Directory { id: current.id(), parent_id: current.parent_collection(), From b6ceb4d353f9a915311fe5dd9e25d7a08a55fb1f Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Mon, 21 Aug 2023 21:00:13 +0200 Subject: [PATCH 12/17] database update on startup --- .vsb | 0 src/database/audio_file.rs | 61 ++++++++++- src/database/collection.rs | 87 ++++++++++++++- src/database/comparator.rs | 167 ++++++++++++++++++++++++++++ src/database/connection.rs | 10 +- src/database/handler.rs | 183 ++++++++++++++++++++++++++----- src/database/meta.rs | 136 +++++++++++++++++++++++ src/database/mod.rs | 160 ++++++++++++++++++++++++++- src/database/sqls/schema.sql | 20 +++- src/database/store.rs | 46 +++++--- src/database/tags.rs | 7 +- src/database/tests/comparator.rs | 86 +++++++++++++++ src/database/tests/mod.rs | 9 +- 13 files changed, 897 insertions(+), 75 deletions(-) delete mode 100644 .vsb create mode 100644 src/database/comparator.rs create mode 100644 src/database/meta.rs create mode 100644 src/database/tests/comparator.rs diff --git a/.vsb b/.vsb deleted file mode 100644 index e69de29..0000000 diff --git a/src/database/audio_file.rs b/src/database/audio_file.rs index a50ed0c..1d03797 100644 --- a/src/database/audio_file.rs +++ b/src/database/audio_file.rs @@ -1,7 +1,7 @@ -use vizia::prelude::*; - -use super::{CollectionID, Database, DatabaseConnectionHandle, DatabaseError}; +use super::prelude::*; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use vizia::prelude::*; pub type AudioFileID = usize; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Data, Lens)] @@ -18,6 +18,20 @@ pub struct AudioFile { } impl AudioFile { + pub fn from_path(path: &PathBuf, id: AudioFileID) -> Option { + let extension = path.extension().map(|v| v.to_str().unwrap()).unwrap_or(""); + + if !AUDIO_FILE_EXTENSIONS.contains(&extension) { + return None; + } + + let name = path.file_name().unwrap().to_str().unwrap(); + + let audio_file = AudioFile::new(id, name.to_string(), id, 0.0, 0.0, 0.0, None, None, 0.0); + + Some(audio_file) + } + pub fn new( id: AudioFileID, name: String, @@ -35,8 +49,10 @@ impl AudioFile { pub trait DatabaseAudioFileHandler { fn get_all_audio_files(&self) -> Result, DatabaseError>; + fn get_audio_file_by_path(&self, path: &PathBuf) -> Result; fn get_child_audio_files(&self, parent: CollectionID) -> Result, DatabaseError>; fn insert_audio_file(&mut self, audio_file: AudioFile) -> Result<(), DatabaseError>; + fn remove_audio_file(&mut self, audio_file: AudioFileID) -> Result<(), DatabaseError>; } impl DatabaseAudioFileHandler for Database { @@ -65,13 +81,40 @@ impl DatabaseAudioFileHandler for Database { Err(DatabaseError::ConnectionClosed) } + fn get_audio_file_by_path(&self, path: &PathBuf) -> Result { + if let Some(connection) = self.get_connection() { + let mut query = connection.prepare( + "SELECT id, name, collection, duration, sample_rate, bit_depth, bpm, key, size FROM audio_files WHERE path = (?1)", + )?; + + let col: AudioFile = query.query_row([path.to_str().unwrap()], |row| { + let path: String = row.get(3)?; + Ok(AudioFile { + id: row.get(0)?, + name: row.get(1)?, + collection: row.get(2)?, + duration: row.get(3)?, + sample_rate: row.get(4)?, + bit_depth: row.get(5)?, + bpm: row.get(6)?, + key: row.get(7)?, + size: row.get(8)?, + }) + })?; + + return Ok(col); + } + + Err(DatabaseError::ConnectionClosed) + } + fn get_child_audio_files(&self, parent: CollectionID) -> Result, DatabaseError> { if let Some(connection) = self.get_connection() { let mut query = connection.prepare( "SELECT id, name, collection, duration, sample_rate, bit_depth, bpm, key, size FROM audio_files WHERE collection = (?1)", )?; - let collections = query.query_map([parent], |row| { + let audio_files = query.query_map([parent], |row| { Ok(AudioFile { id: row.get(0)?, name: row.get(1)?, @@ -85,7 +128,7 @@ impl DatabaseAudioFileHandler for Database { }) })?; - return Ok(collections.map(|v| v.unwrap()).collect()); + return Ok(audio_files.map(|v| v.unwrap()).collect()); } Err(DatabaseError::ConnectionClosed) } @@ -110,6 +153,14 @@ impl DatabaseAudioFileHandler for Database { Ok(()) } + + fn remove_audio_file(&mut self, audio_file: AudioFileID) -> Result<(), DatabaseError> { + if let Some(connection) = self.get_connection() { + connection.execute("DELETE FROM audio_files WHERE id = (?1)", [audio_file])?; + } + + Ok(()) + } } impl From for usize { diff --git a/src/database/collection.rs b/src/database/collection.rs index 69ae935..b12c475 100644 --- a/src/database/collection.rs +++ b/src/database/collection.rs @@ -1,7 +1,6 @@ -use std::path::{Path, PathBuf}; - -use super::{Database, DatabaseConnectionHandle, DatabaseError}; +use super::prelude::*; use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; pub type CollectionID = usize; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -39,15 +38,19 @@ impl Collection { } } -pub trait DatabaseCollectionHandler { +pub trait DatabaseCollection { fn get_root_collection(&self) -> Result; + fn get_collection(&self, collection: CollectionID) -> Result; + fn get_collection_by_name(&self, name: &str) -> Result; + fn get_collection_by_path(&self, path: &PathBuf) -> Result; fn get_all_collections(&self) -> Result, DatabaseError>; fn get_child_collections(&self, parent: CollectionID) -> Result, DatabaseError>; fn insert_collection(&mut self, collection: Collection) -> Result<(), DatabaseError>; + fn remove_collection(&mut self, colletion: CollectionID) -> Result<(), DatabaseError>; } -impl DatabaseCollectionHandler for Database { +impl DatabaseCollection for Database { fn get_root_collection(&self) -> Result { if let Some(connection) = self.get_connection() { let mut query = connection.prepare( @@ -70,6 +73,72 @@ impl DatabaseCollectionHandler for Database { Err(DatabaseError::ConnectionClosed) } + fn get_collection(&self, collection: CollectionID) -> Result { + if let Some(connection) = self.get_connection() { + let mut query = connection.prepare( + "SELECT id, parent_collection, name, path FROM collections WHERE id IS (?1)", + )?; + + let col: Collection = query.query_row([collection], |row| { + let path: String = row.get(3)?; + Ok(Collection::new( + row.get(0)?, + row.get(1)?, + row.get(2)?, + Path::new(&path).to_path_buf(), + )) + })?; + + return Ok(col); + } + + Err(DatabaseError::ConnectionClosed) + } + + fn get_collection_by_name(&self, name: &str) -> Result { + if let Some(connection) = self.get_connection() { + let mut query = connection.prepare( + "SELECT id, parent_collection, name, path FROM collections WHERE name IS (?1)", + )?; + + let col: Collection = query.query_row([name], |row| { + let path: String = row.get(3)?; + Ok(Collection::new( + row.get(0)?, + row.get(1)?, + row.get(2)?, + Path::new(&path).to_path_buf(), + )) + })?; + + return Ok(col); + } + + Err(DatabaseError::ConnectionClosed) + } + + fn get_collection_by_path(&self, path: &PathBuf) -> Result { + if let Some(connection) = self.get_connection() { + let mut query = connection.prepare( + "SELECT id, parent_collection, name, path FROM collections WHERE path IS (?1)", + )?; + + let col: Collection = query.query_row([path.to_str().unwrap()], |row| { + let path: String = row.get(3)?; + Ok(Collection::new( + row.get(0)?, + row.get(1)?, + row.get(2)?, + Path::new(&path).to_path_buf(), + )) + })?; + + return Ok(col); + } + + Err(DatabaseError::ConnectionClosed) + } + fn get_all_collections(&self) -> Result, DatabaseError> { if let Some(connection) = self.get_connection() { let mut query = @@ -129,6 +198,14 @@ impl DatabaseCollectionHandler for Database { Ok(()) } + + fn remove_collection(&mut self, collection: CollectionID) -> Result<(), DatabaseError> { + if let Some(connection) = self.get_connection() { + connection.execute("DELETE FROM collections WHERE id = (?1)", [collection])?; + } + + Ok(()) + } } impl From for usize { diff --git a/src/database/comparator.rs b/src/database/comparator.rs new file mode 100644 index 0000000..2b8aa87 --- /dev/null +++ b/src/database/comparator.rs @@ -0,0 +1,167 @@ +use super::prelude::*; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use std::{collections::VecDeque, fmt::Debug, fs::read_dir, path::PathBuf}; +use vizia::prelude::*; + +pub static IGNORE_DIRS: [&'static str; 1] = [".vsb-meta"]; + +pub type DirectoryEntryID = usize; +#[derive(Clone, Debug, Serialize, Deserialize, Lens, Eq)] +pub struct DirectoryEntry { + pub name: String, + pub path: PathBuf, + pub id: DirectoryEntryID, + pub parent: Option, + pub parent_path: Option, +} + +impl DirectoryEntry { + pub fn from_path( + path: &PathBuf, + id: DirectoryEntryID, + parent: Option, + parent_path: Option, + ) -> Self { + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + let path = path.clone(); + + Self { name, path, id, parent, parent_path } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Change { + Add(T), + Remove(T), + None, +} +pub fn compare_vec(v1: &Vec, v2: &Vec) -> Vec> +where + T: Clone + Ord + Debug, +{ + let mut v1_i = 0; + let mut v2_i = 0; + + let mut changes = Vec::new(); + + loop { + if v1_i == v1.len() && v2_i == v2.len() { + break; + } else if v1.get(v1_i).is_some() && v2.get(v2_i).is_some() { + let v1_val = v1.get(v1_i).unwrap(); + let v2_val = v2.get(v2_i).unwrap(); + + match v1_val.cmp(v2_val) { + std::cmp::Ordering::Less => { + changes.push(Change::Remove(v1_val.clone())); + println!("Remove {v1_val:?}"); + + // Advance v2 + v1_i += 1; + } + std::cmp::Ordering::Greater => { + changes.push(Change::Add(v2_val.clone())); + println!("Add {v2_val:?}"); + // Advance v1 + v2_i += 1; + } + std::cmp::Ordering::Equal => { + v1_i += 1; + v2_i += 1; + } + } + } else if v1_i == v1.len() { + for dir in v2[v2_i..].iter() { + changes.push(Change::Add(dir.clone())); + println!("Add {dir:?}"); + } + break; + } else if v2_i == v2.len() { + for dir in v1[v1_i..].iter() { + changes.push(Change::Remove(dir.clone())); + println!("Remove {dir:?}"); + } + break; + } + } + + changes +} + +pub fn build_dir_trees_from_directory(dir: &PathBuf) -> Vec { + let mut dirs = Vec::new(); + + let mut dir_stack: VecDeque<(PathBuf, Option, Option, Option)> = + VecDeque::new(); + + let mut next_id = 0; + + dir_stack.push_back((dir.clone(), None, None, None)); + + while let Some((next_dir, parent_id, parent, parent_path)) = dir_stack.pop_front() { + let dir = DirectoryEntry::from_path(&next_dir, next_id, parent, parent_path); + next_id += 1; + + if next_dir.is_dir() { + read_dir(next_dir) + .unwrap() + .filter(|v| v.is_ok()) + .map(|v| v.unwrap()) + .filter(|v| !IGNORE_DIRS.contains(&v.path().file_name().unwrap().to_str().unwrap())) + .sorted_by(|a, b| a.file_name().cmp(&b.file_name())) + .for_each(|v| { + dir_stack.push_back(( + v.path(), + Some(dir.id), + dir.parent.clone(), + Some(dir.path.clone()), + )) + }); + } + + dirs.push(dir); + } + + dirs +} + +pub trait DatabaseComparator { + fn check_changes(&self) -> bool; + fn update_changes(&self); +} + +impl DatabaseComparator for Database { + fn check_changes(&self) -> bool { + true + } + + fn update_changes(&self) {} +} + +impl Hash for DirectoryEntry { + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.path.hash(state); + self.parent_path.hash(state); + } +} + +impl PartialEq for DirectoryEntry { + fn eq(&self, other: &Self) -> bool { + self.path.eq(&other.path) + } +} + +impl PartialOrd for DirectoryEntry { + fn partial_cmp(&self, other: &Self) -> Option { + self.path.partial_cmp(&other.path) + } +} + +impl Ord for DirectoryEntry { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.path.cmp(&other.path) + } +} diff --git a/src/database/connection.rs b/src/database/connection.rs index 8e0487e..e41e72d 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -1,16 +1,14 @@ -use std::fs::File; - +use super::prelude::*; use rusqlite::Connection; +use std::fs::File; -use super::{Database, DatabaseError, DatabaseStore}; - -pub trait DatabaseConnectionHandle { +pub trait DatabaseConnection { fn get_connection(&self) -> Option<&Connection>; fn open_connection(&mut self) -> Result<(), DatabaseError>; fn close_connection(&mut self) -> Result<(), DatabaseError>; } -impl DatabaseConnectionHandle for Database { +impl DatabaseConnection for Database { fn get_connection(&self) -> Option<&Connection> { self.conn.as_ref() } diff --git a/src/database/handler.rs b/src/database/handler.rs index ce3686b..bf3bad5 100644 --- a/src/database/handler.rs +++ b/src/database/handler.rs @@ -5,9 +5,10 @@ use rusqlite::Connection; use std::{ any::Any, cell::RefCell, - collections::{BTreeSet, HashMap, VecDeque}, + collections::{hash_map::DefaultHasher, BTreeSet, HashMap, VecDeque}, error::Error, fs::{create_dir, read_dir, DirEntry, File}, + hash::{Hash, Hasher}, path::{Path, PathBuf}, rc::Rc, sync::atomic::AtomicUsize, @@ -19,35 +20,166 @@ pub const AUDIO_FILE_EXTENSIONS: [&'static str; 1] = ["wav"]; #[derive(Debug, Lens)] pub struct Database { - pub(super) path: PathBuf, - pub(super) conn: Option, - pub(super) meta: DatabaseMetadata, + pub path: PathBuf, + pub conn: Option, + pub meta: DatabaseMetadata, } impl Database { pub fn from_directory(path: PathBuf) -> Result { - // Check file is directory + // Check file is directoryS if !directory_exists(&path) { return Err(DatabaseError::PathNotDirectory); } - // Open connection - let mut s = Self { path, conn: None, meta: DatabaseMetadata::new() }; - s.initialize_or_create_stores()?; + let mut s: Database = Self { path, conn: None, meta: DatabaseMetadata::new() }; - // let database_exists = File::open(s.get_database_path()).is_ok(); + let directory_created = directory_exists(&s.get_meta_directory_path()); + + if directory_created { + s.meta = ron::from_str(&std::fs::read_to_string(s.get_meta_path()).unwrap()).unwrap(); + } else { + create_dir(s.get_meta_directory_path()); + } s.open_connection()?; - s.initialize_empty_database(); - // if !database_exists { - // } else { - // s.update_database(); - // } + match directory_created { + true => s.update_database(), + false => s.initialize_empty_database(), + } + + s.store_metadata(); Ok(s) } + fn update_database(&mut self) { + let mut hasher = DefaultHasher::new(); + let tree = build_dir_trees_from_directory(&Path::new("test_files/").to_path_buf()); + + for entry in tree.iter() { + entry.hash(&mut hasher); + } + + let hash_generated = hasher.finish(); + + if hash_generated != self.meta.hash_id { + // Update database & entries + self.walk_to_new_tree(&tree); + + self.meta.entries = tree; + self.meta.hash_id = hash_generated; + } + } + + pub fn walk_to_new_tree(&mut self, new_tree: &Vec) { + let root = self.meta.entries.iter().find(|v| v.parent.is_none()).unwrap(); + + fn children_of_directory( + tree: &Vec, + dir: DirectoryEntryID, + ) -> Vec { + tree.iter().filter(|v| v.parent == Some(dir)).map(|v| v.clone()).collect() + } + + let mut dir_stack = VecDeque::new(); + let mut to_add = Vec::new(); + let mut to_remove = Vec::new(); + + dir_stack.push_back(root.clone()); + + // First walk, retreive changes and store them + while let Some(dir) = dir_stack.pop_front() { + let mut children = children_of_directory(&self.meta.entries, dir.id); + let children_new = children_of_directory(new_tree, dir.id); + + let changes = compare_vec(&children, &children_new); + + for change in changes { + match change { + Change::Add(c) => { + // Add directory recursively here + to_add.push((dir.path.clone(), c.clone())); + let pos = children.iter().position(|v| *v == c).unwrap(); + children.remove(pos); + } + Change::Remove(c) => { + // Remove directory recursively here + to_remove.push((dir.path.clone(), c.clone())); + let pos = children.iter().position(|v| *v == c).unwrap(); + children.remove(pos); + } + Change::None => {} + } + } + + children.iter().for_each(|v| dir_stack.push_back(v.clone())) + } + + // Remove directories that need to be removed + + for (path, dir) in to_add { + self.insert_recursive_directory(&dir.path, &path); + } + + for (path, dir) in to_remove { + self.remove_recursive_dir(&dir.path); + } + } + + fn insert_recursive_directory(&mut self, parent_path: &PathBuf, path: &PathBuf) { + let read_dir = read_dir(path).unwrap(); + + let mut child_directories = Vec::new(); + let mut child_files = Vec::new(); + + read_dir.filter(|v| v.is_ok()).map(|v| v.unwrap()).for_each(|v| { + match v.metadata().unwrap().is_dir() { + true => child_directories.push(v), + false => child_files.push(v), + } + }); + + // + + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + if name == ".vsb-meta" { + return; + } + + self.meta.last_collection_id += 1; + let id = self.meta.last_collection_id; + + let parent_col = self.get_collection_by_path(&parent_path).unwrap(); + + let collection = Collection::new(id, Some(parent_col.id()), name, path.clone()); + + println!("INSERT COLLECTION {:?}", collection); + + self.insert_collection(collection); + + // + + for file in child_files { + self.meta.last_audio_file += 1; + let audio_file = AudioFile::from_path(&file.path(), self.meta.last_audio_file).unwrap(); + + println!("INSERT AUDIOFILE {:?}", audio_file); + self.insert_audio_file(audio_file); + } + + for directory in child_directories { + self.insert_recursive_directory(path, &directory.path()); + } + } + + fn remove_recursive_dir(&mut self, path: &PathBuf) { + let coll = self.get_collection_by_path(path).unwrap(); + println!("REMOVE COLLECTION {:?}", coll); + self.remove_collection(coll.id()); + } + fn clear_database(&mut self) { self.get_connection().unwrap().execute_batch(include_str!("sqls/clear.sql")).unwrap(); } @@ -89,22 +221,19 @@ impl Database { // Insert each non-directory child for child_file in files { - let p = child_file.path(); - let extension = p.extension().map(|v| v.to_str().unwrap()).unwrap_or(""); - - if !AUDIO_FILE_EXTENSIONS.contains(&extension) { - break; - } - - let file_id = audio_file_count.load(std::sync::atomic::Ordering::Relaxed); + let audio_file = AudioFile::from_path( + &child_file.path(), + audio_file_count.load(std::sync::atomic::Ordering::Relaxed), + ) + .unwrap(); audio_file_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let name = child_file.file_name().to_str().unwrap().to_string(); - - let audio_file = AudioFile::new(file_id, name, id, 0.0, 0.0, 0.0, None, None, 0.0); db.insert_audio_file(audio_file); } }); + + self.meta.last_collection_id = collection_count.load(std::sync::atomic::Ordering::Relaxed); + self.meta.last_audio_file = audio_file_count.load(std::sync::atomic::Ordering::Relaxed); } pub fn from_connection(path: &str, connection: Option) -> Self { @@ -123,8 +252,8 @@ impl Database { impl Drop for Database { fn drop(&mut self) { - let meta_dir = self.get_meta_directory_path(); - std::fs::remove_dir_all(meta_dir); + // let meta_dir = self.get_meta_directory_path(); + // std::fs::remove_dir_all(meta_dir); } } diff --git a/src/database/meta.rs b/src/database/meta.rs new file mode 100644 index 0000000..41339d9 --- /dev/null +++ b/src/database/meta.rs @@ -0,0 +1,136 @@ +use super::prelude::*; + +pub trait DatabaseMeta { + fn update_database(&mut self); + fn walk_to_new_tree(&mut self, new_tree: &Vec); + fn insert_recursive_directory(&mut self, parent_path: &PathBuf, path: &PathBuf); +} + +impl DatabaseMeta for Database { + /// Queries all directories and files of the database path and updates the database + fn update_database(&mut self) { + // Create new hash + let mut hasher = DefaultHasher::new(); + let tree = build_dir_trees_from_directory(&self.path); + + for entry in tree.iter() { + entry.hash(&mut hasher); + } + + let hash_generated = hasher.finish(); + + // Update metadata and database if required + + if hash_generated != self.meta.hash_id { + // Update database & entries + self.walk_to_new_tree(&tree); + + self.meta.entries = tree; + self.meta.hash_id = hash_generated; + } + } + + fn walk_to_new_tree(&mut self, new_tree: &Vec) { + /// Returns all children of the directory in a tree + fn children_of_directory<'a>( + tree: &'a Vec, + dir: &'a DirectoryEntry, + ) -> Vec<&'a DirectoryEntry> { + tree.iter().filter(|v| v.parent_path == Some(dir.path.clone())).collect() + } + + let root = self.meta.entries.iter().find(|v| v.parent.is_none()).unwrap(); + + let mut dir_stack = VecDeque::new(); + let mut to_add = Vec::new(); + let mut to_remove = Vec::new(); + + dir_stack.push_back(root.clone()); + + // Recursively walk the directories storing each change for later + while let Some(dir) = dir_stack.pop_front() { + let mut children = children_of_directory(&self.meta.entries, &dir); + let mut children_new = children_of_directory(new_tree, &dir); + + let changes = compare_vec(&children, &children_new); + + for change in changes { + match change { + Change::Add(c) => { + // Add directory recursively here + to_add.push((dir.path.clone(), c.clone())); + let pos = children_new.iter().position(|v| *v == c).unwrap(); + children_new.remove(pos); + } + Change::Remove(c) => { + // Remove directory recursively here + to_remove.push((dir.path.clone(), c.clone())); + let pos = children.iter().position(|v| *v == c).unwrap(); + children.remove(pos); + } + Change::None => {} + } + } + + children.iter().for_each(|v| dir_stack.push_back((*v).clone())) + } + + // Insert new directories + for (path, dir) in to_add { + self.insert_recursive_directory(&path, &dir.path); + } + + // Remove directories that need to be removed + for (path, dir) in to_remove { + let coll = self.get_collection_by_path(&dir.path).unwrap(); + println!("REMOVE COLLECTION {:?}", coll); + self.remove_collection(coll.id()); + } + } + + fn insert_recursive_directory(&mut self, parent_path: &PathBuf, path: &PathBuf) { + let read_dir = read_dir(path).unwrap(); + + let mut child_directories = Vec::new(); + let mut child_files = Vec::new(); + + read_dir.filter(|v| v.is_ok()).map(|v| v.unwrap()).for_each(|v| { + match v.metadata().unwrap().is_dir() { + true => child_directories.push(v), + false => child_files.push(v), + } + }); + + // + + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + if name == ".vsb-meta" { + return; + } + + self.meta.last_collection_id += 1; + let id = self.meta.last_collection_id; + + let parent_col = self.get_collection_by_path(&parent_path).unwrap(); + + let collection = Collection::new(id, Some(parent_col.id()), name, path.clone()); + + println!("INSERT COLLECTION {:?}", collection); + + self.insert_collection(collection); + + // + + for file in child_files { + self.meta.last_audio_file += 1; + let audio_file = AudioFile::from_path(&file.path(), self.meta.last_audio_file).unwrap(); + + println!("INSERT AUDIOFILE {:?}", audio_file); + self.insert_audio_file(audio_file); + } + + for directory in child_directories { + self.insert_recursive_directory(path, &directory.path()); + } + } +} diff --git a/src/database/mod.rs b/src/database/mod.rs index bd67bfd..2e3af59 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,9 +1,19 @@ -use std::path::PathBuf; - +use super::*; +use crate::state::browser::Directory; +use rusqlite::Connection; use serde::{Deserialize, Serialize}; - -pub mod handler; -pub use handler::*; +use std::{ + any::Any, + cell::RefCell, + collections::{hash_map::DefaultHasher, BTreeSet, HashMap, VecDeque}, + error::Error, + fs::{create_dir, read_dir, DirEntry, File}, + hash::{Hash, Hasher}, + path::{Path, PathBuf}, + rc::Rc, + sync::atomic::AtomicUsize, +}; +use vizia::prelude::*; pub mod connection; pub use connection::*; @@ -23,6 +33,12 @@ pub use audio_file::*; pub mod tags; pub use tags::*; +pub mod meta; +pub use meta::*; + +pub mod comparator; +pub use comparator::*; + mod tests; pub mod prelude { @@ -30,9 +46,10 @@ pub mod prelude { pub use super::collection::*; pub use super::connection::*; pub use super::error::*; - pub use super::handler::*; + pub use super::meta::*; pub use super::store::*; pub use super::tags::*; + pub use super::*; pub use rusqlite::*; } @@ -43,3 +60,134 @@ fn file_exists(path: &PathBuf) -> bool { fn directory_exists(path: &PathBuf) -> bool { std::fs::read_dir(path).is_ok() } + +pub const DATABASE_FILE_NAME: &str = ".database.vsb"; +pub const AUDIO_FILE_EXTENSIONS: [&'static str; 1] = ["wav"]; + +#[derive(Debug, Lens)] +pub struct Database { + pub path: PathBuf, + pub conn: Option, + pub meta: DatabaseMetadata, +} + +impl Database { + pub fn from_connection(path: &str, connection: Option) -> Self { + Database { + path: Path::new(path).to_path_buf(), + conn: connection, + meta: DatabaseMetadata::new(), + } + } + + pub fn from_directory(path: PathBuf) -> Result { + // Check file is directoryS + if !directory_exists(&path) { + return Err(DatabaseError::PathNotDirectory); + } + + let mut s: Database = Self { path, conn: None, meta: DatabaseMetadata::new() }; + + let directory_created = directory_exists(&s.get_meta_directory_path()); + + if directory_created { + s.meta = ron::from_str(&std::fs::read_to_string(s.get_meta_path()).unwrap()).unwrap(); + } else { + create_dir(s.get_meta_directory_path()); + } + + s.open_connection()?; + + if directory_created { + s.update_database(); + } else { + s.initialize_empty_database(); + } + + s.store_metadata(); + + Ok(s) + } + + fn clear_database(&mut self) { + self.get_connection().unwrap().execute_batch(include_str!("sqls/clear.sql")).unwrap(); + } + + fn initialize_empty_database(&mut self) -> Result<(), std::io::Error> { + let mut audio_file_count = 0; + let mut collection_count = 0; + + let connection = self.get_connection().unwrap(); + + let mut dir_stack: VecDeque<(PathBuf, Option, Option)> = VecDeque::new(); + dir_stack.push_back((self.path.clone(), None, None)); + + while let Some((path, parent, parent_id)) = dir_stack.pop_front() { + let read_dir = read_dir(&path)?; + + let mut child_directories = VecDeque::new(); + let mut child_files = Vec::new(); + + read_dir.filter(|v| v.is_ok()).map(|v| v.unwrap()).for_each(|v| { + match v.metadata().unwrap().is_dir() { + true => child_directories.push_back(v), + false => child_files.push(v), + } + }); + + // + + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + if name.starts_with(".") { + continue; + } + + let id = collection_count; + collection_count += 1; + + for child_dir in child_directories { + dir_stack.push_back((child_dir.path(), Some(path.clone()), Some(id))); + } + + // Insert collection + let collection = Collection::new(id, parent_id, name, path.clone()); + + self.insert_collection(collection); + + // Insert each non-directory child + for child_file in child_files { + let audio_file = + AudioFile::from_path(&child_file.path(), audio_file_count).unwrap(); + audio_file_count += 1; + + self.insert_audio_file(audio_file); + } + } + // Recursively check each directory under the root + + self.meta.last_collection_id = collection_count; + self.meta.last_audio_file = audio_file_count; + + Ok(()) + } + + pub fn close_database(&mut self) { + self.store_metadata(); + self.close_connection().unwrap(); + } +} + +impl Drop for Database { + fn drop(&mut self) { + // let meta_dir = self.get_meta_directory_path(); + // std::fs::remove_dir_all(meta_dir); + } +} + +impl PartialEq for Database { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + && self.conn.is_some() == other.conn.is_some() + && self.meta == other.meta + } +} diff --git a/src/database/sqls/schema.sql b/src/database/sqls/schema.sql index e700237..72bf3ed 100644 --- a/src/database/sqls/schema.sql +++ b/src/database/sqls/schema.sql @@ -4,7 +4,10 @@ CREATE TABLE collections ( name nvarchar(255), path nvarchar(255), - FOREIGN KEY(parent_collection) REFERENCES collections(id) + CONSTRAINT fk_coll_coll + FOREIGN KEY(parent_collection) + REFERENCES collections(id) + ON DELETE CASCADE ); CREATE TABLE audio_files ( @@ -18,7 +21,10 @@ CREATE TABLE audio_files ( key integer NULL, size integer, - FOREIGN KEY(collection) REFERENCES collections(id) + CONSTRAINT fk_af_coll + FOREIGN KEY(collection) + REFERENCES collections(id) + ON DELETE CASCADE ); CREATE TABLE tags ( @@ -33,6 +39,12 @@ CREATE TABLE audio_files_tags ( PRIMARY KEY (audio_file, tag) - FOREIGN KEY(audio_file) REFERENCES audio_files(id) - FOREIGN KEY(tag) REFERENCES tags(id) + CONSTRAINT fk_aft_af + FOREIGN KEY(audio_file) + REFERENCES audio_files(id) + ON DELETE CASCADE + CONSTRAINT fk_aft_tag + FOREIGN KEY(tag) + REFERENCES tags(id) + ON DELETE CASCADE ); \ No newline at end of file diff --git a/src/database/store.rs b/src/database/store.rs index 74880ce..9515418 100644 --- a/src/database/store.rs +++ b/src/database/store.rs @@ -1,7 +1,10 @@ -use super::{Database, DatabaseError}; +use super::prelude::*; +use chrono::Utc; use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use std::path::Path; use std::{ - collections::HashMap, + collections::{hash_map::DefaultHasher, HashMap}, fs::{create_dir, File}, path::PathBuf, }; @@ -11,23 +14,40 @@ pub const DATABASE_META_DIRECTORY_NAME: &str = ".vsb-meta/"; pub const DATABASE_DATABASE_NAME: &str = ".vsb-database"; pub const DATABASE_META_NAME: &str = ".vsb-meta"; -pub type Hash = String; - #[derive(Clone, Debug, Serialize, Deserialize, Lens, PartialEq)] pub struct DatabaseMetadata { - pub(super) map: HashMap, + pub(super) version: String, + pub(super) hash_id: u64, + pub(super) last_changed: chrono::DateTime, + pub(super) entries: Vec, + + pub(super) last_collection_id: usize, + pub(super) last_audio_file: usize, } impl DatabaseMetadata { pub fn new() -> Self { - Self { map: HashMap::new() } + let mut hasher = DefaultHasher::new(); + + let tree = build_dir_trees_from_directory(&Path::new("test_files/").to_path_buf()); + + for entry in tree.iter() { + entry.hash(&mut hasher); + } + + Self { + version: env!("CARGO_PKG_VERSION").to_string(), + hash_id: hasher.finish(), + last_changed: Utc::now(), + entries: tree, + + last_collection_id: 0, + last_audio_file: 0, + } } pub fn need_update(&self, other: &Self) -> bool { - let mut self_collect: Vec = self.map.keys().map(|v| v.clone()).collect(); - let mut other_collect: Vec = other.map.keys().map(|v| v.clone()).collect(); - - self_collect.len() != other_collect.len() || self_collect != other_collect + true } } @@ -71,12 +91,6 @@ pub trait DatabaseStore { } // - fn initialize_or_create_stores(&self) -> Result<(), DatabaseError> { - create_dir(self.get_meta_directory_path()); - - Ok(()) - } - fn store_metadata(&self); fn retreive_metadata(&mut self); } diff --git a/src/database/tags.rs b/src/database/tags.rs index b354cbb..d6b77f5 100644 --- a/src/database/tags.rs +++ b/src/database/tags.rs @@ -1,5 +1,4 @@ -use super::prelude::{AudioFileID, Database, DatabaseError}; -use crate::database::prelude::*; +use super::prelude::*; use serde::{Deserialize, Serialize}; pub type TagID = usize; @@ -16,7 +15,7 @@ impl Tag { } } -pub trait DatabaseTagHandler { +pub trait DatabaseTags { fn get_all_tags(&self) -> Result, DatabaseError>; fn get_tags_from_audio_file(&self, audio_file: AudioFileID) -> Result, DatabaseError>; fn insert_tag(&mut self, tag: Tag) -> Result<(), DatabaseError>; @@ -27,7 +26,7 @@ pub trait DatabaseTagHandler { ) -> Result<(), DatabaseError>; } -impl DatabaseTagHandler for Database { +impl DatabaseTags for Database { fn get_all_tags(&self) -> Result, DatabaseError> { if let Some(connection) = self.get_connection() { let mut query = connection.prepare("SELECT id, name, color FROM tags")?; diff --git a/src/database/tests/comparator.rs b/src/database/tests/comparator.rs new file mode 100644 index 0000000..f2577b4 --- /dev/null +++ b/src/database/tests/comparator.rs @@ -0,0 +1,86 @@ +use std::path::Path; + +use crate::database::{build_dir_trees_from_directory, compare_vec, Change, DirectoryEntry}; + +use super::TEST_DIRECTORY; + +#[test] +fn compare_test() { + let vec1 = vec![2, 5, 7, 9]; + let vec2 = vec![1, 2, 5, 6, 7, 10]; + + let changes = compare_vec(&vec1, &vec2); + + assert_eq!(changes, vec![Change::Add(1), Change::Add(6), Change::Remove(9), Change::Add(10)]) +} + +#[test] +fn build_tree_from_directory() { + let path = Path::new(TEST_DIRECTORY).to_path_buf(); + + let tree = build_dir_trees_from_directory(&path); + + for dir_entry in tree.iter() { + println!("{:?}", dir_entry); + } +} + +#[test] +fn compare_directory_entry() { + let v1 = vec![ + DirectoryEntry { + name: "One".to_string(), + id: 0, + path: Path::new("").to_path_buf(), + parent: None, + parent_path: None, + }, + DirectoryEntry { + name: "Sub One".to_string(), + id: 1, + path: Path::new("/1").to_path_buf(), + parent: Some(0), + parent_path: Some(Path::new("").to_path_buf()), + }, + DirectoryEntry { + name: "Sub Two".to_string(), + id: 2, + path: Path::new("/2").to_path_buf(), + parent: Some(0), + parent_path: Some(Path::new("").to_path_buf()), + }, + DirectoryEntry { + name: "Sub Three".to_string(), + id: 2, + path: Path::new("/3").to_path_buf(), + parent: Some(0), + parent_path: Some(Path::new("").to_path_buf()), + }, + ]; + + let v2 = vec![ + DirectoryEntry { + name: "One".to_string(), + id: 0, + path: Path::new("").to_path_buf(), + parent: None, + parent_path: None, + }, + DirectoryEntry { + name: "Sub One".to_string(), + id: 1, + path: Path::new("/1").to_path_buf(), + parent: Some(0), + parent_path: Some(Path::new("").to_path_buf()), + }, + DirectoryEntry { + name: "Sub Two".to_string(), + id: 2, + path: Path::new("/1/2").to_path_buf(), + parent: Some(1), + parent_path: Some(Path::new("").to_path_buf()), + }, + ]; + + let changes = compare_vec(&v1, &v2); +} diff --git a/src/database/tests/mod.rs b/src/database/tests/mod.rs index d18dd25..bcc6925 100644 --- a/src/database/tests/mod.rs +++ b/src/database/tests/mod.rs @@ -1,10 +1,12 @@ -use super::prelude::Database; +use super::prelude::*; pub mod get_audio_files; pub mod get_collections; pub mod get_tags; pub mod insert; +pub mod comparator; + const TEST_DIRECTORY: &str = "test_files/"; const TEST_META_DIRECTORY: &str = "test_files/.vsb-meta/"; const TEST_META: &str = "test_files/.vsb-meta/.vsb-meta"; @@ -16,11 +18,14 @@ fn init_test_database() -> Database { let mut handle = Database::from_connection("", Some(Connection::open_in_memory().unwrap())); handle.get_connection().unwrap().execute_batch(include_str!("../sqls/schema.sql")).unwrap(); - handle.get_connection().unwrap().execute_batch(include_str!("../sqls/test.sql")).unwrap(); handle } +fn insert_test_data(db: &mut Database) { + db.get_connection().unwrap().execute_batch(include_str!("../sqls/test.sql")).unwrap(); +} + pub fn check_meta_directory_exists() -> bool { std::fs::read_dir(TEST_META_DIRECTORY).is_ok() } From f3ef5147fe8bce949044db8f1e47ed0b8c3a9209 Mon Sep 17 00:00:00 2001 From: lunaesomnia Date: Mon, 21 Aug 2023 21:00:13 +0200 Subject: [PATCH 13/17] database update on startup From 81a6e2976379a3ffee288fb6498c5ed8bed0d5a5 Mon Sep 17 00:00:00 2001 From: Lunae Somnia <77613968+LunaeSomnia@users.noreply.github.com> Date: Mon, 21 Aug 2023 21:24:06 +0200 Subject: [PATCH 14/17] clippy help --- src/database/comparator.rs | 7 +++---- src/database/meta.rs | 10 ++++------ src/database/mod.rs | 12 +++++------- src/database/store.rs | 4 ++-- src/panels/browser.rs | 2 +- src/popup_menu/mod.rs | 15 +++------------ src/state/browser.rs | 28 +++++----------------------- src/views/toggle_button.rs | 8 +++----- 8 files changed, 26 insertions(+), 60 deletions(-) diff --git a/src/database/comparator.rs b/src/database/comparator.rs index 2b8aa87..3dfa0ea 100644 --- a/src/database/comparator.rs +++ b/src/database/comparator.rs @@ -5,7 +5,7 @@ use std::hash::{Hash, Hasher}; use std::{collections::VecDeque, fmt::Debug, fs::read_dir, path::PathBuf}; use vizia::prelude::*; -pub static IGNORE_DIRS: [&'static str; 1] = [".vsb-meta"]; +pub static IGNORE_DIRS: [&str; 1] = [".vsb-meta"]; pub type DirectoryEntryID = usize; #[derive(Clone, Debug, Serialize, Deserialize, Lens, Eq)] @@ -107,15 +107,14 @@ pub fn build_dir_trees_from_directory(dir: &PathBuf) -> Vec { if next_dir.is_dir() { read_dir(next_dir) .unwrap() - .filter(|v| v.is_ok()) - .map(|v| v.unwrap()) + .filter_map(|v| v.ok()) .filter(|v| !IGNORE_DIRS.contains(&v.path().file_name().unwrap().to_str().unwrap())) .sorted_by(|a, b| a.file_name().cmp(&b.file_name())) .for_each(|v| { dir_stack.push_back(( v.path(), Some(dir.id), - dir.parent.clone(), + dir.parent, Some(dir.path.clone()), )) }); diff --git a/src/database/meta.rs b/src/database/meta.rs index 41339d9..a48e370 100644 --- a/src/database/meta.rs +++ b/src/database/meta.rs @@ -94,11 +94,9 @@ impl DatabaseMeta for Database { let mut child_directories = Vec::new(); let mut child_files = Vec::new(); - read_dir.filter(|v| v.is_ok()).map(|v| v.unwrap()).for_each(|v| { - match v.metadata().unwrap().is_dir() { - true => child_directories.push(v), - false => child_files.push(v), - } + read_dir.filter_map(|v| v.ok()).for_each(|v| match v.metadata().unwrap().is_dir() { + true => child_directories.push(v), + false => child_files.push(v), }); // @@ -111,7 +109,7 @@ impl DatabaseMeta for Database { self.meta.last_collection_id += 1; let id = self.meta.last_collection_id; - let parent_col = self.get_collection_by_path(&parent_path).unwrap(); + let parent_col = self.get_collection_by_path(parent_path).unwrap(); let collection = Collection::new(id, Some(parent_col.id()), name, path.clone()); diff --git a/src/database/mod.rs b/src/database/mod.rs index 2e3af59..2d6d33b 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -62,7 +62,7 @@ fn directory_exists(path: &PathBuf) -> bool { } pub const DATABASE_FILE_NAME: &str = ".database.vsb"; -pub const AUDIO_FILE_EXTENSIONS: [&'static str; 1] = ["wav"]; +pub const AUDIO_FILE_EXTENSIONS: [&str; 1] = ["wav"]; #[derive(Debug, Lens)] pub struct Database { @@ -128,17 +128,15 @@ impl Database { let mut child_directories = VecDeque::new(); let mut child_files = Vec::new(); - read_dir.filter(|v| v.is_ok()).map(|v| v.unwrap()).for_each(|v| { - match v.metadata().unwrap().is_dir() { - true => child_directories.push_back(v), - false => child_files.push(v), - } + read_dir.filter_map(|v| v.ok()).for_each(|v| match v.metadata().unwrap().is_dir() { + true => child_directories.push_back(v), + false => child_files.push(v), }); // let name = path.file_name().unwrap().to_str().unwrap().to_string(); - if name.starts_with(".") { + if name.starts_with('.') { continue; } diff --git a/src/database/store.rs b/src/database/store.rs index 9515418..b9e1a02 100644 --- a/src/database/store.rs +++ b/src/database/store.rs @@ -62,13 +62,13 @@ pub trait DatabaseStore { } fn get_database_path(&self) -> PathBuf { - let mut path = self.get_meta_directory_path().clone(); + let mut path = self.get_meta_directory_path(); path.push(DATABASE_DATABASE_NAME); path } fn get_meta_path(&self) -> PathBuf { - let mut path = self.get_meta_directory_path().clone(); + let mut path = self.get_meta_directory_path(); path.push(DATABASE_META_NAME); path } diff --git a/src/panels/browser.rs b/src/panels/browser.rs index f08e092..28c68b0 100644 --- a/src/panels/browser.rs +++ b/src/panels/browser.rs @@ -61,7 +61,7 @@ impl BrowserPanel { // Search Box HStack::new(cx, |cx| { Textbox::new(cx, AppData::browser.then(BrowserState::search_text)) - .on_edit(|cx, text| cx.emit(BrowserEvent::Search(text.clone()))) + .on_edit(|cx, text| cx.emit(BrowserEvent::Search(text))) .placeholder(Localized::new("search")) .width(Stretch(1.0)) .bind(BrowserPanel::search_shown, |mut handle, shown| { diff --git a/src/popup_menu/mod.rs b/src/popup_menu/mod.rs index 8ec597e..6ebe789 100644 --- a/src/popup_menu/mod.rs +++ b/src/popup_menu/mod.rs @@ -73,14 +73,8 @@ impl View for PopupMenu { } fn event(&mut self, cx: &mut EventContext, event: &mut Event) { - event.map(|e, em| match e { - // PopupEvent::SetMenu(entity, menu, meta) => { - // self.target_entity = Some(*entity); - // self.meta = meta.clone(); - - // em.consume(); - // } - PopupEvent::Show => { + event.map(|e, em| { + if let PopupEvent::Show = e { // if self.shown { // cx.emit(PopupEvent::Hide); // return; @@ -132,10 +126,7 @@ impl View for PopupMenu { cx.set_translate((Pixels(desired_pos.0 / scale), Pixels(desired_pos.1 / scale))); cx.needs_redraw(); // self.shown = true; - } // PopupEvent::Hide => { - // self.shown = false; - // } - _ => {} + } }); } } diff --git a/src/state/browser.rs b/src/state/browser.rs index 833aea6..12c8f7f 100644 --- a/src/state/browser.rs +++ b/src/state/browser.rs @@ -10,7 +10,7 @@ use vizia::prelude::*; use self::browser_state_derived_lenses::libraries; -#[derive(Debug, Lens, Clone, Data)] +#[derive(Debug, Lens, Clone, Data, Default)] pub struct BrowserState { pub libraries: Vec, pub selected: HashSet, @@ -60,19 +60,6 @@ pub struct Directory { pub shown: bool, } -impl Default for BrowserState { - fn default() -> Self { - Self { - libraries: Vec::new(), - selected: HashSet::new(), - focused: None, - search_text: String::new(), - filter_search: false, - search_case_sensitive: false, - } - } -} - impl Model for BrowserState { fn event(&mut self, cx: &mut EventContext, event: &mut Event) { event.map(|browser_event, _| match browser_event { @@ -123,7 +110,7 @@ impl Model for BrowserState { BrowserEvent::CollapseDirectory => { if let Some(focused) = &self.focused { - if is_collapsed(&mut self.libraries[0], focused) { + if is_collapsed(&self.libraries[0], focused) { self.focused = focused.parent().map(|p| p.to_owned()); } else { set_expand_directory(&mut self.libraries[0], focused, false); @@ -255,12 +242,7 @@ fn recursive_prev<'a>( RetItem::NotFound(prev) } -fn search<'a>( - root: &'a mut Directory, - search_text: &String, - filter: bool, - ignore_case: bool, -) -> bool { +fn search(root: &mut Directory, search_text: &String, filter: bool, ignore_case: bool) -> bool { let mut parent_is_shown = !filter; let mut matcher = SkimMatcherV2::default(); @@ -284,10 +266,10 @@ fn search<'a>( root.shown = parent_is_shown | child_is_shown; - return root.shown; + root.shown } -fn is_collapsed<'a>(root: &'a Directory, dir: &PathBuf) -> bool { +fn is_collapsed(root: &Directory, dir: &PathBuf) -> bool { if root.path == *dir { if !root.is_open { return true; diff --git a/src/views/toggle_button.rs b/src/views/toggle_button.rs index 66b7323..087350a 100644 --- a/src/views/toggle_button.rs +++ b/src/views/toggle_button.rs @@ -48,15 +48,13 @@ impl View for ToggleButton { cx.release(); } - WindowEvent::ActionRequest(action) => match action.action { - Action::Default => { + WindowEvent::ActionRequest(action) => { + if let Action::Default = action.action { if let Some(callback) = &self.on_toggle { (callback)(cx); } } - - _ => {} - }, + } _ => {} }); From 57e06c338295480d6a26116f091eebc148ad79ac Mon Sep 17 00:00:00 2001 From: Lunae Somnia <77613968+LunaeSomnia@users.noreply.github.com> Date: Mon, 21 Aug 2023 21:24:28 +0200 Subject: [PATCH 15/17] delete handler --- src/database/handler.rs | 296 ---------------------------------------- 1 file changed, 296 deletions(-) delete mode 100644 src/database/handler.rs diff --git a/src/database/handler.rs b/src/database/handler.rs deleted file mode 100644 index bf3bad5..0000000 --- a/src/database/handler.rs +++ /dev/null @@ -1,296 +0,0 @@ -use crate::state::browser::Directory; - -use super::*; -use rusqlite::Connection; -use std::{ - any::Any, - cell::RefCell, - collections::{hash_map::DefaultHasher, BTreeSet, HashMap, VecDeque}, - error::Error, - fs::{create_dir, read_dir, DirEntry, File}, - hash::{Hash, Hasher}, - path::{Path, PathBuf}, - rc::Rc, - sync::atomic::AtomicUsize, -}; -use vizia::prelude::*; - -pub const DATABASE_FILE_NAME: &str = ".database.vsb"; -pub const AUDIO_FILE_EXTENSIONS: [&'static str; 1] = ["wav"]; - -#[derive(Debug, Lens)] -pub struct Database { - pub path: PathBuf, - pub conn: Option, - pub meta: DatabaseMetadata, -} - -impl Database { - pub fn from_directory(path: PathBuf) -> Result { - // Check file is directoryS - if !directory_exists(&path) { - return Err(DatabaseError::PathNotDirectory); - } - - let mut s: Database = Self { path, conn: None, meta: DatabaseMetadata::new() }; - - let directory_created = directory_exists(&s.get_meta_directory_path()); - - if directory_created { - s.meta = ron::from_str(&std::fs::read_to_string(s.get_meta_path()).unwrap()).unwrap(); - } else { - create_dir(s.get_meta_directory_path()); - } - - s.open_connection()?; - - match directory_created { - true => s.update_database(), - false => s.initialize_empty_database(), - } - - s.store_metadata(); - - Ok(s) - } - - fn update_database(&mut self) { - let mut hasher = DefaultHasher::new(); - let tree = build_dir_trees_from_directory(&Path::new("test_files/").to_path_buf()); - - for entry in tree.iter() { - entry.hash(&mut hasher); - } - - let hash_generated = hasher.finish(); - - if hash_generated != self.meta.hash_id { - // Update database & entries - self.walk_to_new_tree(&tree); - - self.meta.entries = tree; - self.meta.hash_id = hash_generated; - } - } - - pub fn walk_to_new_tree(&mut self, new_tree: &Vec) { - let root = self.meta.entries.iter().find(|v| v.parent.is_none()).unwrap(); - - fn children_of_directory( - tree: &Vec, - dir: DirectoryEntryID, - ) -> Vec { - tree.iter().filter(|v| v.parent == Some(dir)).map(|v| v.clone()).collect() - } - - let mut dir_stack = VecDeque::new(); - let mut to_add = Vec::new(); - let mut to_remove = Vec::new(); - - dir_stack.push_back(root.clone()); - - // First walk, retreive changes and store them - while let Some(dir) = dir_stack.pop_front() { - let mut children = children_of_directory(&self.meta.entries, dir.id); - let children_new = children_of_directory(new_tree, dir.id); - - let changes = compare_vec(&children, &children_new); - - for change in changes { - match change { - Change::Add(c) => { - // Add directory recursively here - to_add.push((dir.path.clone(), c.clone())); - let pos = children.iter().position(|v| *v == c).unwrap(); - children.remove(pos); - } - Change::Remove(c) => { - // Remove directory recursively here - to_remove.push((dir.path.clone(), c.clone())); - let pos = children.iter().position(|v| *v == c).unwrap(); - children.remove(pos); - } - Change::None => {} - } - } - - children.iter().for_each(|v| dir_stack.push_back(v.clone())) - } - - // Remove directories that need to be removed - - for (path, dir) in to_add { - self.insert_recursive_directory(&dir.path, &path); - } - - for (path, dir) in to_remove { - self.remove_recursive_dir(&dir.path); - } - } - - fn insert_recursive_directory(&mut self, parent_path: &PathBuf, path: &PathBuf) { - let read_dir = read_dir(path).unwrap(); - - let mut child_directories = Vec::new(); - let mut child_files = Vec::new(); - - read_dir.filter(|v| v.is_ok()).map(|v| v.unwrap()).for_each(|v| { - match v.metadata().unwrap().is_dir() { - true => child_directories.push(v), - false => child_files.push(v), - } - }); - - // - - let name = path.file_name().unwrap().to_str().unwrap().to_string(); - if name == ".vsb-meta" { - return; - } - - self.meta.last_collection_id += 1; - let id = self.meta.last_collection_id; - - let parent_col = self.get_collection_by_path(&parent_path).unwrap(); - - let collection = Collection::new(id, Some(parent_col.id()), name, path.clone()); - - println!("INSERT COLLECTION {:?}", collection); - - self.insert_collection(collection); - - // - - for file in child_files { - self.meta.last_audio_file += 1; - let audio_file = AudioFile::from_path(&file.path(), self.meta.last_audio_file).unwrap(); - - println!("INSERT AUDIOFILE {:?}", audio_file); - self.insert_audio_file(audio_file); - } - - for directory in child_directories { - self.insert_recursive_directory(path, &directory.path()); - } - } - - fn remove_recursive_dir(&mut self, path: &PathBuf) { - let coll = self.get_collection_by_path(path).unwrap(); - println!("REMOVE COLLECTION {:?}", coll); - self.remove_collection(coll.id()); - } - - fn clear_database(&mut self) { - self.get_connection().unwrap().execute_batch(include_str!("sqls/clear.sql")).unwrap(); - } - - fn initialize_empty_database(&mut self) { - let audio_file_count = AtomicUsize::new(0); - let collection_count = AtomicUsize::new(0); - - let collections: Rc>> = - Rc::new(RefCell::new(HashMap::new())); - - let connection = Rc::new(RefCell::new(self.get_connection().unwrap())); - - let path = self.path.clone(); - - // Recursively check each directory under the root - recursive_directory_closure(self, &path, None, |db, path, parent_path, files| { - let mut colls = collections.borrow_mut(); - - let name = path.file_name().unwrap().to_str().unwrap().to_string(); - if name == ".vsb-meta" { - return; - } - - let id = collection_count.load(std::sync::atomic::Ordering::Relaxed); - collection_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let parent_id = match parent_path.is_none() { - true => None, - false => Some(*colls.get(parent_path.unwrap()).unwrap()), - }; - - // Insert collection - let collection = Collection::new(id, parent_id, name, path.clone()); - - db.insert_collection(collection); - - colls.insert(path.clone(), id); - drop(colls); - - // Insert each non-directory child - for child_file in files { - let audio_file = AudioFile::from_path( - &child_file.path(), - audio_file_count.load(std::sync::atomic::Ordering::Relaxed), - ) - .unwrap(); - audio_file_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - - db.insert_audio_file(audio_file); - } - }); - - self.meta.last_collection_id = collection_count.load(std::sync::atomic::Ordering::Relaxed); - self.meta.last_audio_file = audio_file_count.load(std::sync::atomic::Ordering::Relaxed); - } - - pub fn from_connection(path: &str, connection: Option) -> Self { - Database { - path: Path::new(path).to_path_buf(), - conn: connection, - meta: DatabaseMetadata::new(), - } - } - - pub fn close_database(&mut self) { - self.store_metadata(); - self.close_connection().unwrap(); - } -} - -impl Drop for Database { - fn drop(&mut self) { - // let meta_dir = self.get_meta_directory_path(); - // std::fs::remove_dir_all(meta_dir); - } -} - -fn recursive_directory_closure( - db: &mut Database, - path: &PathBuf, - parent_path: Option<&PathBuf>, - mut closure: F, -) -> Result<(), std::io::Error> -where - F: FnMut(&mut Database, &PathBuf, Option<&PathBuf>, &Vec) + Clone, -{ - let read_dir = read_dir(&path)?; - - let mut child_directories = Vec::new(); - let mut child_files = Vec::new(); - - read_dir.filter(|v| v.is_ok()).map(|v| v.unwrap()).for_each(|v| { - match v.metadata().unwrap().is_dir() { - true => child_directories.push(v), - false => child_files.push(v), - } - }); - - (closure)(db, &path, parent_path, &child_files); - - for directory in child_directories { - recursive_directory_closure(db, &directory.path(), Some(&path), closure.clone())?; - } - - Ok(()) -} - -impl PartialEq for Database { - fn eq(&self, other: &Self) -> bool { - self.path == other.path - && self.conn.is_some() == other.conn.is_some() - && self.meta == other.meta - } -} From 3c8771a828225698f4ac0fca131afefa0caa8580 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Wed, 14 Aug 2024 20:42:09 +0100 Subject: [PATCH 16/17] fix build --- src/database/audio_file.rs | 2 +- src/database/collection.rs | 3 +-- src/database/mod.rs | 13 ++++++++----- src/database/store.rs | 2 -- src/main.rs | 4 +++- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/database/audio_file.rs b/src/database/audio_file.rs index 20dc6a4..ea4740c 100644 --- a/src/database/audio_file.rs +++ b/src/database/audio_file.rs @@ -1,6 +1,6 @@ use vizia::prelude::*; -use super::{CollectionID, Database, DatabaseConnectionHandle, DatabaseError}; +use super::{CollectionID, Database, DatabaseConnection, DatabaseError, AUDIO_FILE_EXTENSIONS}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use vizia::prelude::*; diff --git a/src/database/collection.rs b/src/database/collection.rs index 6a323bc..081fcb0 100644 --- a/src/database/collection.rs +++ b/src/database/collection.rs @@ -1,8 +1,7 @@ use std::path::{Path, PathBuf}; -use super::{Database, DatabaseConnectionHandle, DatabaseError}; +use super::{Database, DatabaseConnection, DatabaseError}; use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; pub type CollectionID = usize; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/src/database/mod.rs b/src/database/mod.rs index 2d6d33b..f61db74 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,5 +1,5 @@ use super::*; -use crate::state::browser::Directory; +use crate::data::browser::Directory; use rusqlite::Connection; use serde::{Deserialize, Serialize}; use std::{ @@ -154,13 +154,16 @@ impl Database { // Insert each non-directory child for child_file in child_files { - let audio_file = - AudioFile::from_path(&child_file.path(), audio_file_count).unwrap(); - audio_file_count += 1; + if let Some(audio_file) = AudioFile::from_path(&child_file.path(), audio_file_count) + { + audio_file_count += 1; - self.insert_audio_file(audio_file); + self.insert_audio_file(audio_file); + } } } + + println!("{}", audio_file_count); // Recursively check each directory under the root self.meta.last_collection_id = collection_count; diff --git a/src/database/store.rs b/src/database/store.rs index 3c788a6..f704ed2 100644 --- a/src/database/store.rs +++ b/src/database/store.rs @@ -14,8 +14,6 @@ pub const DATABASE_META_DIRECTORY_NAME: &str = ".vsb-meta/"; pub const DATABASE_DATABASE_NAME: &str = ".vsb-database"; pub const DATABASE_META_NAME: &str = ".vsb-meta"; -pub type Hash = String; - #[derive(Clone, Debug, Serialize, Deserialize, Lens, PartialEq)] pub struct DatabaseMetadata { pub(super) version: String, diff --git a/src/main.rs b/src/main.rs index 043a66a..6bcd22a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -99,6 +99,8 @@ fn main() -> Result<(), AppError> { let collections = db.get_all_collections().unwrap(); let audio_files = db.get_all_audio_files().unwrap(); + println!("{}", audio_files.len()); + let root = collections.iter().find(|v| v.parent_collection().is_none()).unwrap(); let root = collections_to_directories(&collections, &audio_files, root.clone()); @@ -214,4 +216,4 @@ fn collections_to_directories( children, ..Default::default() } -} +} \ No newline at end of file From 2a5a03e5fe6dbaafbef6e01d1f289346abe41aa3 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Sun, 18 Aug 2024 11:20:53 +0100 Subject: [PATCH 17/17] Fix build --- src/data/app_data.rs | 4 ++-- src/database/collection.rs | 23 ----------------------- src/database/mod.rs | 2 +- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/data/app_data.rs b/src/data/app_data.rs index 410466b..73bb53f 100644 --- a/src/data/app_data.rs +++ b/src/data/app_data.rs @@ -13,10 +13,10 @@ use crate::{ TagsData, }, database::prelude::{ - AudioFile, CollectionID, Database, DatabaseAudioFileHandler, DatabaseCollectionHandler, + AudioFile, CollectionID, Database, DatabaseAudioFileHandler, DatabaseCollection, }, engine::{SamplePlayerController, Waveform}, - AudioData, Collection, DatabaseTagHandler, Tag, + AudioData, Collection, DatabaseTags, Tag, }; use super::{SettingsData, TableData}; diff --git a/src/database/collection.rs b/src/database/collection.rs index 3d664ba..081fcb0 100644 --- a/src/database/collection.rs +++ b/src/database/collection.rs @@ -40,7 +40,6 @@ impl Collection { } pub trait DatabaseCollection { - fn get_collection(&self, id: CollectionID) -> Result; fn get_root_collection(&self) -> Result; fn get_collection(&self, collection: CollectionID) -> Result; fn get_collection_by_name(&self, name: &str) -> Result; @@ -53,28 +52,6 @@ pub trait DatabaseCollection { } impl DatabaseCollection for Database { - fn get_collection(&self, id: CollectionID) -> Result { - if let Some(connection) = self.get_connection() { - let mut query = connection.prepare( - "SELECT id, parent_collection, name, path FROM collections WHERE id = (?1)", - )?; - - let collection = query.query_row([id], |row| { - let path: String = row.get(3)?; - Ok(Collection::new( - row.get(0)?, - row.get(1)?, - row.get(2)?, - Path::new(&path).to_path_buf(), - )) - })?; - - return Ok(collection); - } - - Err(DatabaseError::ConnectionClosed) - } - fn get_root_collection(&self) -> Result { if let Some(connection) = self.get_connection() { let mut query = connection.prepare( diff --git a/src/database/mod.rs b/src/database/mod.rs index f61db74..f8ff20f 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,5 +1,5 @@ use super::*; -use crate::data::browser::Directory; +use crate::data::browser_data::Directory; use rusqlite::Connection; use serde::{Deserialize, Serialize}; use std::{