- library creation flow complete

- library/client db records unique and tied to local state
- config remove unwrap
This commit is contained in:
Jamie
2022-03-01 02:26:11 -08:00
parent d1b27db390
commit 3eebe6cd56
16 changed files with 197 additions and 186 deletions

View File

@@ -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<Directory, String> {
#[tauri::command]
pub fn get_config() -> ClientState {
get().unwrap()
client::get()
}
#[tauri::command]

View File

@@ -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"
}
}
}

View File

@@ -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<DatabaseConnection, DbErr> {
let config = state::client::get().unwrap();
pub static DB: OnceCell<DatabaseConnection> = 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<DatabaseConnection> = 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(())
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -10,7 +10,7 @@ pub async fn get_thumbs_for_directory(path: &str) -> impl Stream<Item = ClientEv
let dir = retrieve::get_dir_with_contents(&path).await.unwrap();
stream::iter(dir.contents.into_iter()).filter_map(|file| async {
let config = state::client::get().unwrap();
let config = state::client::get();
let icon_name = format!(
"{}.png",
if file.is_dir {

View File

@@ -5,8 +5,9 @@ use chrono::Utc;
use sea_orm::{entity::*, QueryOrder};
use walkdir::{DirEntry, WalkDir};
use crate::db::{connection::DB_INSTANCE, entity::file};
use crate::file::{checksum::create_meta_integrity_hash, init};
use crate::db::{connection::db, entity::file};
use crate::file::checksum::create_meta_integrity_hash;
use crate::library;
use crate::util::time;
use super::locations::get_location;
@@ -25,8 +26,9 @@ pub async fn scan_paths(location_id: u32) -> 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<file::ActiveModel> {
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

View File

@@ -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<library::Model> {
// 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(())
}

View File

@@ -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<locations::Model> {
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<locations::Model> {
}
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),

View File

@@ -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;

View File

@@ -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(())
}

View File

@@ -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<String>) {
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();
}

View File

@@ -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<library::Model> {
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<String>) -> 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(())
}

View File

@@ -1,2 +1,3 @@
pub mod client;
pub mod init;
// pub mod init;
pub mod loader;

View File

@@ -72,26 +72,47 @@ pub fn configure(mut data_dir: std::path::PathBuf) -> mpsc::Receiver<ClientEvent
// create data directory if it doesn't exist
fs::create_dir_all(&data_dir).unwrap();
// prepare basic client state
let mut client_config = ClientState::new(data_dir, "spacedrive").unwrap();
let mut client_config = ClientState::new(data_dir, "diamond-mastering-space-dragon").unwrap();
// load from disk
client_config
.read_disk()
.unwrap_or(println!("No client state found, created new one"));
.unwrap_or(println!("No client state found, creating new one..."));
client_config.save();
println!("client config: {:?}", client_config);
// begin asynchronous startup routines
block_on(async {
// init library, updates config with primary library
library::init::init_library().await.unwrap();
// init database for primary library / TODO: rename to create_db
db::connection::create_primary_db().await.unwrap();
// add library to database
library::init::add_library_to_db(Some("My Library".to_string())).await;
println!("Starting up... {:?}", client_config);
if client_config.libraries.len() == 0 {
match library::loader::create(None).await {
Ok(library) => {
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);
});

View File

@@ -19,7 +19,7 @@ pub struct ClientState {
// all the libraries loaded by this client
pub libraries: Vec<LibraryState>,
// 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<Option<ClientState>> = RwLock::new(None);
}
pub fn get() -> Result<ClientState> {
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)
}
}