Process manager templating & game importing (#96)

* feat: add new template options, asahi support, and refactoring

* feat: install dir scanning, validation fixes, progress fixes, download manager refactor

This kind of ballooned out of scope, but I implemented some much
needed fixes for the download manager.

First off, I cleanup the Downloadable trait, there was some
duplication of function.

Second, I refactored the "validate" into the GameDownloadAgent,
which calls a 'validate_chunk_logic' yada, same structure as
downloading.

Third, I fixed the progress and validation issues.

Fourth, I added game scanning

* feat: out of box support for Asahi Linux

* fix: clippy

* fix: don't break database
This commit is contained in:
DecDuck
2025-08-02 20:17:27 +10:00
committed by GitHub
parent 5c69669428
commit 94c49bc5c2
36 changed files with 764 additions and 478 deletions
+3 -1
View File
@@ -7,7 +7,7 @@ use std::{
use serde_json::Value;
use crate::{
database::db::borrow_db_mut_checked, error::download_manager_error::DownloadManagerError,
database::{db::borrow_db_mut_checked, scan::scan_install_dirs}, error::download_manager_error::DownloadManagerError,
};
use super::{
@@ -59,6 +59,8 @@ pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>
lock.applications.install_dirs.push(new_dir);
drop(lock);
scan_install_dirs();
Ok(())
}
+16 -14
View File
@@ -10,7 +10,7 @@ use chrono::Utc;
use log::{debug, error, info, warn};
use native_model::{Decode, Encode};
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
use serde::{de::DeserializeOwned, Serialize};
use serde::{Serialize, de::DeserializeOwned};
use url::Url;
use crate::DB;
@@ -67,17 +67,18 @@ impl DatabaseImpls for DatabaseInterface {
let exists = fs::exists(db_path.clone()).unwrap();
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 {
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.as_os_str().to_str().unwrap()
);
PathDatabase::create_at_path(db_path, default)
.expect("Database could not be created")
PathDatabase::create_at_path(db_path, default).expect("Database could not be created")
}
}
@@ -121,24 +122,24 @@ fn handle_invalid_database(
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
impl<'a> Deref for DBWrite<'a> {
type Target = RwLockWriteGuard<'a, Database>;
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 = RwLockReadGuard<'a, Database>;
type Target = Database;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for DBWrite<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Drop for DBWrite<'_> {
fn drop(&mut self) {
unsafe {
@@ -154,6 +155,7 @@ impl Drop for DBWrite<'_> {
}
}
}
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
match DB.borrow_data() {
Ok(data) => DBRead(data),
+1
View File
@@ -2,3 +2,4 @@ pub mod commands;
pub mod db;
pub mod debug;
pub mod models;
pub mod scan;
+24 -21
View File
@@ -1,8 +1,14 @@
use crate::database::models::data::Database;
/**
* NEXT BREAKING CHANGE
*
* UPDATE DATABASE TO USE RPMSERDENAMED
*
* WE CAN'T DELETE ANY FIELDS
*/
pub mod data {
use std::path::PathBuf;
use native_model::native_model;
use serde::{Deserialize, Serialize};
@@ -18,16 +24,14 @@ pub mod data {
pub type DatabaseApplications = v2::DatabaseApplications;
pub type DatabaseCompatInfo = v2::DatabaseCompatInfo;
use std::{collections::HashMap, process::Command};
use crate::process::process_manager::UMU_LAUNCHER_EXECUTABLE;
use std::collections::HashMap;
pub mod v1 {
use crate::process::process_manager::Platform;
use serde_with::serde_as;
use std::{collections::HashMap, path::PathBuf};
use super::{Serialize, Deserialize, native_model};
use super::{Deserialize, Serialize, native_model};
fn default_template() -> String {
"{}".to_owned()
@@ -115,6 +119,7 @@ pub mod data {
Downloading { version_name: String },
Uninstalling {},
Updating { version_name: String },
Validating { version_name: String },
Running {},
}
@@ -174,7 +179,10 @@ pub mod data {
use serde_with::serde_as;
use super::{Serialize, Deserialize, native_model, Settings, DatabaseAuth, v1, GameVersion, DownloadableMetadata, ApplicationTransientStatus};
use super::{
ApplicationTransientStatus, DatabaseAuth, Deserialize, DownloadableMetadata,
GameVersion, Serialize, Settings, native_model, v1,
};
#[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
#[derive(Serialize, Deserialize, Clone, Default)]
@@ -206,7 +214,7 @@ pub mod data {
applications: value.applications,
prev_database: value.prev_database,
cache_dir: value.cache_dir,
compat_info: crate::database::models::Database::create_new_compat_info(),
compat_info: None,
}
}
}
@@ -283,7 +291,10 @@ pub mod data {
mod v3 {
use std::path::PathBuf;
use super::{Serialize, Deserialize, native_model, Settings, DatabaseAuth, DatabaseApplications, DatabaseCompatInfo, v2};
use super::{
DatabaseApplications, DatabaseAuth, DatabaseCompatInfo, Deserialize, Serialize,
Settings, native_model, v2,
};
#[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde)]
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct Database {
@@ -297,6 +308,7 @@ pub mod data {
pub cache_dir: PathBuf,
pub compat_info: Option<DatabaseCompatInfo>,
}
impl From<v2::Database> for Database {
fn from(value: v2::Database) -> Self {
Self {
@@ -306,22 +318,13 @@ pub mod data {
applications: value.applications.into(),
prev_database: value.prev_database,
cache_dir: value.cache_dir,
compat_info: Database::create_new_compat_info(),
compat_info: None,
}
}
}
}
impl Database {
fn create_new_compat_info() -> Option<DatabaseCompatInfo> {
#[cfg(target_os = "windows")]
return None;
let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE).spawn().is_ok();
Some(DatabaseCompatInfo {
umu_installed: has_umu_installed,
})
}
pub fn new<T: Into<PathBuf>>(
games_base_dir: T,
prev_database: Option<PathBuf>,
@@ -340,7 +343,7 @@ pub mod data {
auth: None,
settings: Settings::default(),
cache_dir,
compat_info: Database::create_new_compat_info(),
compat_info: None,
}
}
}
+52
View File
@@ -0,0 +1,52 @@
use std::fs;
use log::warn;
use crate::{
database::{
db::borrow_db_mut_checked,
models::data::v1::{DownloadType, DownloadableMetadata},
},
games::{
downloads::drop_data::{v1::DropData, DROP_DATA_PATH},
library::set_partially_installed_db,
},
};
pub fn scan_install_dirs() {
let mut db_lock = borrow_db_mut_checked();
for install_dir in db_lock.applications.install_dirs.clone() {
let Ok(files) = fs::read_dir(install_dir) else {
continue;
};
for game in files.into_iter().flatten() {
let drop_data_file = game.path().join(DROP_DATA_PATH);
if !drop_data_file.exists() {
continue;
}
let game_id = game.file_name().into_string().unwrap();
let Ok(drop_data) = DropData::read(&game.path()) else {
warn!(
".dropdata exists for {}, but couldn't read it. is it corrupted?",
game.file_name().into_string().unwrap()
);
continue;
};
if db_lock.applications.game_statuses.contains_key(&game_id) {
continue;
}
let metadata = DownloadableMetadata::new(
drop_data.game_id,
Some(drop_data.game_version),
DownloadType::Game,
);
set_partially_installed_db(
&mut db_lock,
&metadata,
drop_data.base_path.to_str().unwrap().to_string(),
None,
);
}
}
}