From 3eebe6cd564a9ff80aa74bd79b57ca3583029daa Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 1 Mar 2022 02:26:11 -0800 Subject: [PATCH] - library creation flow complete - library/client db records unique and tied to local state - config remove unwrap --- apps/desktop/src-tauri/src/commands.rs | 4 +- package.json | 4 +- packages/core/lib/db/connection.rs | 53 +++++------ packages/core/lib/db/entity/client.rs | 2 + .../migrations/{primary => }/V1__initial.sql | 1 + packages/core/lib/file/icon.rs | 2 +- packages/core/lib/file/indexer.rs | 15 ++-- packages/core/lib/file/init.rs | 46 ---------- packages/core/lib/file/locations.rs | 14 +-- packages/core/lib/file/mod.rs | 2 - packages/core/lib/library/client.rs | 42 ++++----- packages/core/lib/library/init.rs | 45 ---------- packages/core/lib/library/loader.rs | 87 +++++++++++++++++++ packages/core/lib/library/mod.rs | 3 +- packages/core/lib/main.rs | 43 ++++++--- packages/core/lib/state/client.rs | 20 +++-- 16 files changed, 197 insertions(+), 186 deletions(-) rename packages/core/lib/db/migrations/{primary => }/V1__initial.sql (99%) delete mode 100644 packages/core/lib/file/init.rs delete mode 100644 packages/core/lib/library/init.rs create mode 100644 packages/core/lib/library/loader.rs diff --git a/apps/desktop/src-tauri/src/commands.rs b/apps/desktop/src-tauri/src/commands.rs index 8b5a2d551..6ff71d925 100644 --- a/apps/desktop/src-tauri/src/commands.rs +++ b/apps/desktop/src-tauri/src/commands.rs @@ -4,7 +4,7 @@ use sdcorelib::{ db::connection::db, file::{icon, indexer, locations, retrieve, retrieve::Directory, watcher::watch_dir}, native, - state::client::{get, ClientState}, + state::{client, client::ClientState}, }; use swift_rs::types::SRObjectArray; @@ -26,7 +26,7 @@ pub async fn get_files(path: String) -> Result { #[tauri::command] pub fn get_config() -> ClientState { - get().unwrap() + client::get() } #[tauri::command] diff --git a/package.json b/package.json index e2a425f32..ee12ca92d 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,9 @@ "desktop": "yarn workspace desktop", "mobile": "yarn workspace mobile", "web": "yarn workspace web", + "docs": "yarn workspace docs", "server": "yarn workspace server" }, - "devDependencies": { "prettier": "^2.5.1", "turbo": "latest" @@ -62,4 +62,4 @@ "npm": ">=7.0.0", "node": ">=14.0.0" } -} \ No newline at end of file +} diff --git a/packages/core/lib/db/connection.rs b/packages/core/lib/db/connection.rs index 0989385a1..d51dd4a29 100644 --- a/packages/core/lib/db/connection.rs +++ b/packages/core/lib/db/connection.rs @@ -1,55 +1,50 @@ -use crate::state; +use crate::state::{self}; use anyhow::Result; use once_cell::sync::OnceCell; use rusqlite::Connection; -use sea_orm::{Database, DatabaseConnection, DbErr}; -// use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode}; -// use sqlx::{ConnectOptions, Connection}; -// use std::str::FromStr; +use sea_orm::{Database, DatabaseConnection}; -pub async fn get_connection() -> Result { - let config = state::client::get().unwrap(); +pub static DB: OnceCell = OnceCell::new(); - let db_url = format!("{}{}{}", "sqlite://", config.data_path, "/library.db"); - - let db = Database::connect(&db_url).await?; - - Ok(db) -} - -pub static DB_INSTANCE: OnceCell = OnceCell::new(); pub async fn db() -> Result<&'static DatabaseConnection, String> { - if DB_INSTANCE.get().is_none() { - let db = get_connection().await.map_err(|e| e.to_string())?; - DB_INSTANCE.set(db).unwrap_or_default(); - Ok(DB_INSTANCE.get().unwrap()) + if DB.get().is_none() { + let config = state::client::get(); + + let current_library = config + .libraries + .iter() + .find(|l| l.library_id == config.current_library_id) + .unwrap(); + + let path = current_library.library_path.clone(); + + let db = Database::connect(format!("sqlite://{}", &path)) + .await + .unwrap(); + + DB.set(db).unwrap_or_default(); + + Ok(DB.get().unwrap()) } else { - Ok(DB_INSTANCE.get().unwrap()) + Ok(DB.get().unwrap()) } } -pub async fn create_primary_db() -> Result<(), sqlx::Error> { - let config = state::client::get().unwrap(); - - let db_url = format!("{}/library.db", config.data_path); - +pub async fn init(db_url: &str) -> Result<(), sqlx::Error> { // establish connection, this is only used to create the db if missing // replace in future let mut connection = Connection::open(&db_url).unwrap(); - println!("Primary database initialized: {}", &db_url); - // migrate db mod embedded_primary { use refinery::embed_migrations; - embed_migrations!("lib/db/migrations/primary"); + embed_migrations!("lib/db/migrations"); } embedded_primary::migrations::runner() .run(&mut connection) .unwrap(); - // close and exit cause we don't need this connection anymore connection.close().unwrap(); Ok(()) } diff --git a/packages/core/lib/db/entity/client.rs b/packages/core/lib/db/entity/client.rs index d409906c7..dff07dec4 100644 --- a/packages/core/lib/db/entity/client.rs +++ b/packages/core/lib/db/entity/client.rs @@ -15,6 +15,8 @@ pub struct Model { // identity #[sea_orm(primary_key)] pub id: u32, + #[sea_orm(unique)] + pub uuid: String, pub name: String, pub platform: Platform, pub online: bool, diff --git a/packages/core/lib/db/migrations/primary/V1__initial.sql b/packages/core/lib/db/migrations/V1__initial.sql similarity index 99% rename from packages/core/lib/db/migrations/primary/V1__initial.sql rename to packages/core/lib/db/migrations/V1__initial.sql index a6dfd3563..5d7479180 100644 --- a/packages/core/lib/db/migrations/primary/V1__initial.sql +++ b/packages/core/lib/db/migrations/V1__initial.sql @@ -14,6 +14,7 @@ CREATE TABLE IF NOT EXISTS libraries ( ); CREATE TABLE IF NOT EXISTS clients ( id INTEGER PRIMARY KEY AUTOINCREMENT, + uuid TEXT NOT NULL UNIQUE, name TEXT NOT NULL, platform INTEGER NOT NULL DEFAULT 0, online BOOLEAN DEFAULT TRUE, diff --git a/packages/core/lib/file/icon.rs b/packages/core/lib/file/icon.rs index 37f47af5e..3066a6771 100644 --- a/packages/core/lib/file/icon.rs +++ b/packages/core/lib/file/icon.rs @@ -10,7 +10,7 @@ pub async fn get_thumbs_for_directory(path: &str) -> impl Stream Result<()> { // creates a vector of valid path buffers from a directory pub async fn scan(path: &str) -> Result<()> { println!("Scanning directory: {}", &path); - let db = DB_INSTANCE.get().unwrap(); - let primary_library = init::get_primary_library(&db).await?; + let current_library = library::loader::get().await?; + + let db = db().await.unwrap(); // query db to highers id, so we can increment it for the new files indexed let mut next_file_id = match file::Entity::find() @@ -81,7 +83,7 @@ pub async fn scan(path: &str) -> Result<()> { path.to_owned(), file_id, parent_dir_id.cloned(), - primary_library.id, + current_library.id, )); if entry.file_type().is_dir() { @@ -146,7 +148,8 @@ fn create_active_file_model( ) -> Result { let metadata = fs::metadata(&uri)?; let size = metadata.len(); - let mut meta_integrity_hash = create_meta_integrity_hash(uri.to_str().unwrap_or_default(), size)?; + let mut meta_integrity_hash = + create_meta_integrity_hash(uri.to_str().unwrap_or_default(), size)?; meta_integrity_hash.truncate(20); let mut location_relative_uri = uri diff --git a/packages/core/lib/file/init.rs b/packages/core/lib/file/init.rs deleted file mode 100644 index 5dd7ca8db..000000000 --- a/packages/core/lib/file/init.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::db::{connection::db, entity::library}; -use anyhow::{bail, Result}; -use sea_orm::{entity::*, DatabaseConnection, QueryFilter}; -use strum::Display; - -#[derive(Display)] -pub enum InitError { - LibraryNotFound, -} - -pub async fn get_primary_library(db: &DatabaseConnection) -> Result { - // get library entity by is_primary column, should be unique - let mut existing_libs = library::Entity::find() - .filter(library::Column::IsPrimary.eq(true)) - .all(db) - .await?; - - // return library - if existing_libs.len() == 0 { - bail!(InitError::LibraryNotFound.to_string()); - } else { - Ok(existing_libs.swap_remove(0)) - } -} - -pub async fn init_library() -> Result<()> { - let db = db().await.unwrap(); - - let library = get_primary_library(&db).await; - // if no library create one now - if library.is_err() { - let library = library::ActiveModel { - name: Set("Primary".to_owned()), - is_primary: Set(true), - ..Default::default() - }; - - let library = library.save(db).await?; - - println!("created library {:?}", &library); - } else { - // println!("library loaded {:?}", library.unwrap()); - }; - - Ok(()) -} diff --git a/packages/core/lib/file/locations.rs b/packages/core/lib/file/locations.rs index bf0f0348d..9e34cde2b 100644 --- a/packages/core/lib/file/locations.rs +++ b/packages/core/lib/file/locations.rs @@ -11,21 +11,20 @@ use std::path::Path; use crate::{ db::{ - connection::DB_INSTANCE, + connection::db, entity::{file, locations}, }, + library, native::methods::get_mounts, }; -use super::init; - #[derive(Serialize, Deserialize)] struct DotSpaceDrive { location_id: u32, } pub async fn get_location(location_id: u32) -> Result { - let db = DB_INSTANCE.get().unwrap(); + let db = db().await.unwrap(); // get location by location_id from db and include location_paths let location = match locations::Entity::find() @@ -41,8 +40,9 @@ pub async fn get_location(location_id: u32) -> Result { } pub async fn create_location(path: &str) -> Result<()> { - let db = DB_INSTANCE.get().unwrap(); - let primary_library = init::get_primary_library(&db).await?; + let db = db().await.unwrap(); + + let library = library::loader::get().await?; let mounts = get_mounts(); // find mount with matching path @@ -77,7 +77,7 @@ pub async fn create_location(path: &str) -> Result<()> { available_capacity: Set(mount.available_capacity.try_into().unwrap()), is_ejectable: Set(mount.is_ejectable), is_removable: Set(mount.is_removable), - library_id: Set(primary_library.id), + library_id: Set(library.id), date_created: Set(Some(Utc::now().naive_utc())), last_indexed: Set(Some(Utc::now().naive_utc())), is_root_filesystem: Set(mount.is_root_filesystem), diff --git a/packages/core/lib/file/mod.rs b/packages/core/lib/file/mod.rs index e1b8521b5..2de1f1dd3 100644 --- a/packages/core/lib/file/mod.rs +++ b/packages/core/lib/file/mod.rs @@ -1,8 +1,6 @@ pub mod checksum; -// pub mod client; pub mod icon; pub mod indexer; -pub mod init; pub mod locations; pub mod retrieve; pub mod watcher; diff --git a/packages/core/lib/library/client.rs b/packages/core/lib/library/client.rs index 5a6b12357..fd9ecf2ab 100644 --- a/packages/core/lib/library/client.rs +++ b/packages/core/lib/library/client.rs @@ -1,12 +1,6 @@ -use std::{collections::HashMap, env}; - use anyhow::Result; - -use sea_orm::EntityTrait; -use sea_orm::Set; - -use sea_orm::{ActiveModelTrait, QueryOrder}; - +use std::env; +// use sea_orm::EntityTrait; use crate::{ db::{ connection::db, @@ -14,23 +8,17 @@ use crate::{ }, state, }; +use sea_orm::ActiveModelTrait; +use sea_orm::Set; pub async fn create() -> Result<()> { - let db = db().await.unwrap(); - let config = state::client::get().unwrap(); + println!("Creating client..."); + let mut config = state::client::get(); - // get highest location id from db - let next_client_id = match client::Entity::find() - .order_by_desc(client::Column::Id) - .one(db) - .await - { - Ok(client) => client.map_or(1, |client| client.id + 1), - Err(_) => 1, - }; + let db = db().await.expect("Could not connect to database"); let hostname = match hostname::get() { - Ok(hostname) => hostname.to_str().unwrap().to_owned(), + Ok(hostname) => hostname.to_str().unwrap_or_default().to_owned(), Err(_) => "unknown".to_owned(), }; @@ -42,17 +30,19 @@ pub async fn create() -> Result<()> { }; let mut client = client::ActiveModel { - // id: Set(next_client_id), - name: Set(hostname), + name: Set(hostname.clone()), + uuid: Set(config.client_id.clone()), platform: Set(platform), online: Set(true), ..Default::default() }; - client = client.save(db).await.map_err(|e| { - println!("error saving client: {:?}", e); - e - })?; + client = client.save(db).await?; + + config.client_name = hostname; + config.save(); + + println!("Created client: {:?}", &client); Ok(()) } diff --git a/packages/core/lib/library/init.rs b/packages/core/lib/library/init.rs deleted file mode 100644 index dd06aa4b2..000000000 --- a/packages/core/lib/library/init.rs +++ /dev/null @@ -1,45 +0,0 @@ -use anyhow::Result; -use sea_orm::ActiveModelTrait; -use sea_orm::Set; -use uuid::Uuid; - -use crate::{ - db::{connection::db, entity::library}, - state, - state::client::LibraryState, -}; - -pub async fn init_library() -> Result<()> { - let mut client_config = state::client::get()?; - - if client_config.libraries.len() == 0 { - // create default library - let uuid = Uuid::new_v4().to_string(); - - let library = LibraryState { - library_id: uuid.clone(), - library_path: format!("{}/library.db", client_config.data_path), - }; - - client_config.libraries.push(library); - - client_config.primary_library_id = uuid; - - client_config.save(); - } - - Ok(()) -} - -// this should also take care of calling the connection module to create the library db before saving -pub async fn add_library_to_db(name: Option) { - let db = db().await.unwrap(); - - let library = library::ActiveModel { - uuid: Set(Uuid::new_v4().to_string()), - name: Set(String::from(name.unwrap_or(String::from("My Library")))), - ..Default::default() - }; - - library.save(db).await.unwrap(); -} diff --git a/packages/core/lib/library/loader.rs b/packages/core/lib/library/loader.rs new file mode 100644 index 000000000..08e71938f --- /dev/null +++ b/packages/core/lib/library/loader.rs @@ -0,0 +1,87 @@ +use anyhow::Result; +use sea_orm::ActiveModelTrait; +use sea_orm::Set; +use sea_orm::{entity::*, query::*}; +use uuid::Uuid; + +use crate::state::client::LibraryState; +use crate::{ + db::connection::{db, init}, + db::entity::library, + state, +}; + +pub static LIBRARY_DB_NAME: &str = "library.db"; +pub static DEFAULT_NAME: &str = "My Library"; + +pub async fn get() -> Result { + let config = state::client::get(); + let db = db().await.unwrap(); + + let library_state = config.get_current_library(); + + // get library from db + let library = match library::Entity::find() + .filter(library::Column::Id.eq(library_state.library_id.clone())) + .one(db) + .await? + { + Some(library) => Ok(library), + None => { + // update config library state to offline + // config.libraries + + Err(anyhow::anyhow!("library_not_found")) + } + }; + + Ok(library.unwrap()) +} + +pub async fn load(library_path: &str, library_id: &str) -> Result<()> { + let mut config = state::client::get(); + + println!("Initializing library: {} {}", &library_id, library_path); + + if config.current_library_id != library_id { + config.current_library_id = library_id.to_string(); + config.save(); + } + // create connection with library database & run migrations + init(&library_path).await?; + // if doesn't exist, mark as offline + Ok(()) +} + +pub async fn create(name: Option) -> Result<()> { + let mut config = state::client::get(); + + let uuid = Uuid::new_v4().to_string(); + + println!("Creating library {:?}, UUID: {:?}", name, uuid); + + let library_state = LibraryState { + library_id: uuid.clone(), + library_path: format!("{}/{}", config.data_path, LIBRARY_DB_NAME), + ..LibraryState::default() + }; + + init(&library_state.library_path).await?; + + config.libraries.push(library_state); + + config.current_library_id = uuid; + + config.save(); + + let db = db().await.unwrap(); + + let library = library::ActiveModel { + uuid: Set(config.current_library_id), + name: Set(String::from(name.unwrap_or(String::from(DEFAULT_NAME)))), + ..Default::default() + }; + + library.save(db).await.unwrap(); + Ok(()) +} diff --git a/packages/core/lib/library/mod.rs b/packages/core/lib/library/mod.rs index 4d041e67f..4b3486ad9 100644 --- a/packages/core/lib/library/mod.rs +++ b/packages/core/lib/library/mod.rs @@ -1,2 +1,3 @@ pub mod client; -pub mod init; +// pub mod init; +pub mod loader; diff --git a/packages/core/lib/main.rs b/packages/core/lib/main.rs index 27db7408f..c953e78ed 100644 --- a/packages/core/lib/main.rs +++ b/packages/core/lib/main.rs @@ -72,26 +72,47 @@ pub fn configure(mut data_dir: std::path::PathBuf) -> mpsc::Receiver { + println!("Created new library: {:?}", library); + } + Err(e) => { + println!("Error creating library: {:?}", e); + } + } + } else { + for library in client_config.libraries.iter() { + // init database for library + match library::loader::load(&library.library_path, &library.library_id).await { + Ok(library) => { + println!("Loaded library: {:?}", library); + } + Err(e) => { + println!("Error loading library: {:?}", e); + } + } + } + } + // init client - library::client::create().await.unwrap(); + match library::client::create().await { + Ok(_) => {} + Err(e) => { + println!("Error initializing client: {:?}", e); + } + }; // activate p2p listeners // p2p::listener::listen(None); }); diff --git a/packages/core/lib/state/client.rs b/packages/core/lib/state/client.rs index d9ba49a01..2c66493da 100644 --- a/packages/core/lib/state/client.rs +++ b/packages/core/lib/state/client.rs @@ -19,7 +19,7 @@ pub struct ClientState { // all the libraries loaded by this client pub libraries: Vec, // used to quickly find the default library - pub primary_library_id: String, + pub current_library_id: String, } pub static CLIENT_STATE_CONFIG_NAME: &str = ".client_state"; @@ -32,7 +32,7 @@ impl Default for ClientState { client_name: "".to_string(), tcp_port: 0, libraries: vec![], - primary_library_id: "".to_string(), + current_library_id: "".to_string(), } } } @@ -41,6 +41,7 @@ impl Default for ClientState { pub struct LibraryState { pub library_id: String, pub library_path: String, + pub offline: bool, } impl Default for LibraryState { @@ -48,6 +49,7 @@ impl Default for LibraryState { LibraryState { library_id: "".to_string(), library_path: "".to_string(), + offline: false, } } } @@ -57,9 +59,11 @@ lazy_static! { static ref CONFIG: RwLock> = RwLock::new(None); } -pub fn get() -> Result { - let client_state = CONFIG.read().unwrap().as_ref().unwrap().clone(); - Ok(client_state) +pub fn get() -> ClientState { + match CONFIG.read() { + Ok(guard) => guard.clone().unwrap_or(ClientState::default()), + Err(_) => return ClientState::default(), + } } impl ClientState { @@ -106,11 +110,11 @@ impl ClientState { } } - pub fn get_primary_library(&self) -> LibraryState { + pub fn get_current_library(&self) -> LibraryState { match self .libraries .iter() - .find(|lib| lib.library_id == self.primary_library_id) + .find(|lib| lib.library_id == self.current_library_id) { Some(lib) => lib.clone(), None => LibraryState::default(), @@ -118,6 +122,6 @@ impl ClientState { } pub fn get_current_library_db_path(&self) -> String { - format!("{}/library.db", &self.get_primary_library().library_path) + format!("{}/library.db", &self.get_current_library().library_path) } }