11395dbab1
* fix: Add lint and remove all unwraps from lib.rs Signed-off-by: quexeky <git@quexeky.dev> * chore: Remove all unwraps from util.rs and add state_lock macro Signed-off-by: quexeky <git@quexeky.dev> * chore: Add CacheError and remove unwraps from fetch_object Signed-off-by: quexeky <git@quexeky.dev> * chore: Remove unwraps from fetch_object and server_proto Signed-off-by: quexeky <git@quexeky.dev> * chore: Remove unwraps from auth.rs Signed-off-by: quexeky <git@quexeky.dev> * chore: Remove unwraps from process_handlers Signed-off-by: quexeky <git@quexeky.dev> * chore: Clippy unwrap linting Signed-off-by: quexeky <git@quexeky.dev> * chore: Remove lint Because not everything is actually resolved yet: will be resolved with a restructure of the library Signed-off-by: quexeky <git@quexeky.dev> * chore: Make the rest of clippy happy Signed-off-by: quexeky <git@quexeky.dev> * fix: Send download signal instead of triggering self.on_error Signed-off-by: quexeky <git@quexeky.dev> * fix: Corrupted state should panic Signed-off-by: quexeky <git@quexeky.dev> * fix: Use debug instead of display for specific errors Signed-off-by: quexeky <git@quexeky.dev> * fix: Settings now log error instead of panicking Signed-off-by: quexeky <git@quexeky.dev> --------- Signed-off-by: quexeky <git@quexeky.dev>
224 lines
6.7 KiB
Rust
224 lines
6.7 KiB
Rust
use std::{
|
|
fs::{self, create_dir_all},
|
|
mem::ManuallyDrop,
|
|
ops::{Deref, DerefMut},
|
|
path::PathBuf,
|
|
sync::{Arc, LazyLock, RwLockReadGuard, RwLockWriteGuard},
|
|
};
|
|
|
|
use chrono::Utc;
|
|
use log::{debug, error, info, warn};
|
|
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
|
use serde::{Serialize, de::DeserializeOwned};
|
|
use url::Url;
|
|
|
|
use crate::DB;
|
|
|
|
use super::models::data::Database;
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
static DATA_ROOT_PREFIX: &'static str = "drop";
|
|
#[cfg(debug_assertions)]
|
|
static DATA_ROOT_PREFIX: &str = "drop-debug";
|
|
|
|
pub static DATA_ROOT_DIR: LazyLock<Arc<PathBuf>> = LazyLock::new(|| {
|
|
Arc::new(
|
|
dirs::data_dir()
|
|
.expect("Failed to get data dir")
|
|
.join(DATA_ROOT_PREFIX),
|
|
)
|
|
});
|
|
|
|
// Custom JSON serializer to support everything we need
|
|
#[derive(Debug, Default, Clone)]
|
|
pub struct DropDatabaseSerializer;
|
|
|
|
impl<T: native_model::Model + Serialize + DeserializeOwned> DeSerializer<T>
|
|
for DropDatabaseSerializer
|
|
{
|
|
fn serialize(&self, val: &T) -> rustbreak::error::DeSerResult<Vec<u8>> {
|
|
native_model::encode(val)
|
|
.map_err(|e| DeSerError::Internal(e.to_string()))
|
|
}
|
|
|
|
fn deserialize<R: std::io::Read>(&self, mut s: R) -> rustbreak::error::DeSerResult<T> {
|
|
let mut buf = Vec::new();
|
|
s.read_to_end(&mut buf)
|
|
.map_err(|e| rustbreak::error::DeSerError::Other(e.into()))?;
|
|
let (val, _version) = native_model::decode(buf)
|
|
.map_err(|e| DeSerError::Internal(e.to_string()))?;
|
|
Ok(val)
|
|
}
|
|
}
|
|
|
|
pub type DatabaseInterface =
|
|
rustbreak::Database<Database, rustbreak::backend::PathBackend, DropDatabaseSerializer>;
|
|
|
|
pub trait DatabaseImpls {
|
|
fn set_up_database() -> DatabaseInterface;
|
|
fn database_is_set_up(&self) -> bool;
|
|
fn fetch_base_url(&self) -> Url;
|
|
}
|
|
impl DatabaseImpls for DatabaseInterface {
|
|
fn set_up_database() -> DatabaseInterface {
|
|
let db_path = DATA_ROOT_DIR.join("drop.db");
|
|
let games_base_dir = DATA_ROOT_DIR.join("games");
|
|
let logs_root_dir = DATA_ROOT_DIR.join("logs");
|
|
let cache_dir = DATA_ROOT_DIR.join("cache");
|
|
let pfx_dir = DATA_ROOT_DIR.join("pfx");
|
|
|
|
debug!("creating data directory at {DATA_ROOT_DIR:?}");
|
|
create_dir_all(DATA_ROOT_DIR.as_path()).unwrap_or_else(|e| {
|
|
panic!(
|
|
"Failed to create directory {} with error {}",
|
|
DATA_ROOT_DIR.display(),
|
|
e
|
|
)
|
|
});
|
|
create_dir_all(&games_base_dir).unwrap_or_else(|e| {
|
|
panic!(
|
|
"Failed to create directory {} with error {}",
|
|
games_base_dir.display(),
|
|
e
|
|
)
|
|
});
|
|
create_dir_all(&logs_root_dir).unwrap_or_else(|e| {
|
|
panic!(
|
|
"Failed to create directory {} with error {}",
|
|
logs_root_dir.display(),
|
|
e
|
|
)
|
|
});
|
|
create_dir_all(&cache_dir).unwrap_or_else(|e| {
|
|
panic!(
|
|
"Failed to create directory {} with error {}",
|
|
cache_dir.display(),
|
|
e
|
|
)
|
|
});
|
|
create_dir_all(&pfx_dir).unwrap_or_else(|e| {
|
|
panic!(
|
|
"Failed to create directory {} with error {}",
|
|
pfx_dir.display(),
|
|
e
|
|
)
|
|
});
|
|
|
|
let exists = fs::exists(db_path.clone()).unwrap_or_else(|e| {
|
|
panic!(
|
|
"Failed to find if {} exists with error {}",
|
|
db_path.display(),
|
|
e
|
|
)
|
|
});
|
|
|
|
if exists {
|
|
match PathDatabase::load_from_path(db_path.clone()) {
|
|
Ok(db) => db,
|
|
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
|
|
}
|
|
} else {
|
|
let default = Database::new(games_base_dir, None, cache_dir);
|
|
debug!("Creating database at path {}", db_path.display());
|
|
PathDatabase::create_at_path(db_path, default).expect("Database could not be created")
|
|
}
|
|
}
|
|
|
|
fn database_is_set_up(&self) -> bool {
|
|
!borrow_db_checked().base_url.is_empty()
|
|
}
|
|
|
|
fn fetch_base_url(&self) -> Url {
|
|
let handle = borrow_db_checked();
|
|
Url::parse(&handle.base_url)
|
|
.unwrap_or_else(|_| panic!("Failed to parse base url {}", handle.base_url))
|
|
}
|
|
}
|
|
|
|
// TODO: Make the error relelvant rather than just assume that it's a Deserialize error
|
|
fn handle_invalid_database(
|
|
_e: RustbreakError,
|
|
db_path: PathBuf,
|
|
games_base_dir: PathBuf,
|
|
cache_dir: PathBuf,
|
|
) -> rustbreak::Database<Database, rustbreak::backend::PathBackend, DropDatabaseSerializer> {
|
|
warn!("{_e}");
|
|
let new_path = {
|
|
let time = Utc::now().timestamp();
|
|
let mut base = db_path.clone();
|
|
base.set_file_name(format!("drop.db.backup-{time}"));
|
|
base
|
|
};
|
|
info!("old database stored at: {}", new_path.to_string_lossy());
|
|
fs::rename(&db_path, &new_path).unwrap_or_else(|e| {
|
|
panic!(
|
|
"Could not rename database {} to {} with error {}",
|
|
db_path.display(),
|
|
new_path.display(),
|
|
e
|
|
)
|
|
});
|
|
|
|
let db = Database::new(games_base_dir, Some(new_path), cache_dir);
|
|
|
|
PathDatabase::create_at_path(db_path, db).expect("Database could not be created")
|
|
}
|
|
|
|
// To automatically save the database upon drop
|
|
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
|
|
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
|
|
impl<'a> Deref for DBWrite<'a> {
|
|
type Target = Database;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
impl<'a> DerefMut for DBWrite<'a> {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
impl<'a> Deref for DBRead<'a> {
|
|
type Target = Database;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
impl Drop for DBWrite<'_> {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
ManuallyDrop::drop(&mut self.0);
|
|
}
|
|
|
|
match DB.save() {
|
|
Ok(()) => {}
|
|
Err(e) => {
|
|
error!("database failed to save with error {e}");
|
|
panic!("database failed to save with error {e}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
|
match DB.borrow_data() {
|
|
Ok(data) => DBRead(data),
|
|
Err(e) => {
|
|
error!("database borrow failed with error {e}");
|
|
panic!("database borrow failed with error {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn borrow_db_mut_checked<'a>() -> DBWrite<'a> {
|
|
match DB.borrow_data_mut() {
|
|
Ok(data) => DBWrite(ManuallyDrop::new(data)),
|
|
Err(e) => {
|
|
error!("database borrow mut failed with error {e}");
|
|
panic!("database borrow mut failed with error {e}");
|
|
}
|
|
}
|
|
}
|