mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-19 11:11:18 +10:00
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:
@ -3,19 +3,23 @@ use std::sync::Mutex;
|
||||
use tauri::AppHandle;
|
||||
|
||||
use crate::{
|
||||
database::models::data::GameVersion,
|
||||
AppState,
|
||||
database::{
|
||||
db::borrow_db_checked,
|
||||
models::data::GameVersion,
|
||||
},
|
||||
error::{library_error::LibraryError, remote_access_error::RemoteAccessError},
|
||||
games::library::{
|
||||
fetch_game_logic_offline, fetch_library_logic_offline, get_current_meta,
|
||||
uninstall_game_logic,
|
||||
},
|
||||
offline, AppState,
|
||||
offline,
|
||||
};
|
||||
|
||||
use super::{
|
||||
library::{
|
||||
fetch_game_logic, fetch_game_verion_options_logic, fetch_library_logic, FetchGameStruct,
|
||||
Game,
|
||||
FetchGameStruct, Game, fetch_game_logic, fetch_game_verion_options_logic,
|
||||
fetch_library_logic,
|
||||
},
|
||||
state::{GameStatusManager, GameStatusWithTransient},
|
||||
};
|
||||
@ -48,7 +52,8 @@ pub fn fetch_game(
|
||||
|
||||
#[tauri::command]
|
||||
pub fn fetch_game_status(id: String) -> GameStatusWithTransient {
|
||||
GameStatusManager::fetch_state(&id)
|
||||
let db_handle = borrow_db_checked();
|
||||
GameStatusManager::fetch_state(&id, &db_handle)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@ -12,8 +12,11 @@ use crate::download_manager::util::progress_object::{ProgressHandle, ProgressObj
|
||||
use crate::error::application_download_error::ApplicationDownloadError;
|
||||
use crate::error::remote_access_error::RemoteAccessError;
|
||||
use crate::games::downloads::manifest::{DropDownloadContext, DropManifest};
|
||||
use crate::games::downloads::validate::game_validate_logic;
|
||||
use crate::games::library::{on_game_complete, on_game_incomplete, push_game_update};
|
||||
use crate::games::downloads::validate::validate_game_chunk;
|
||||
use crate::games::library::{
|
||||
on_game_complete, push_game_update, set_partially_installed,
|
||||
};
|
||||
use crate::games::state::GameStatusManager;
|
||||
use crate::remote::requests::make_request;
|
||||
use crate::remote::utils::DROP_CLIENT_SYNC;
|
||||
use log::{debug, error, info};
|
||||
@ -41,7 +44,7 @@ pub struct GameDownloadAgent {
|
||||
pub manifest: Mutex<Option<DropManifest>>,
|
||||
pub progress: Arc<ProgressObject>,
|
||||
sender: Sender<DownloadManagerSignal>,
|
||||
pub stored_manifest: DropData,
|
||||
pub dropdata: DropData,
|
||||
status: Mutex<DownloadStatus>,
|
||||
}
|
||||
|
||||
@ -82,38 +85,43 @@ impl GameDownloadAgent {
|
||||
context_map: Mutex::new(HashMap::new()),
|
||||
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
|
||||
sender,
|
||||
stored_manifest,
|
||||
dropdata: stored_manifest,
|
||||
status: Mutex::new(DownloadStatus::Queued),
|
||||
}
|
||||
}
|
||||
|
||||
// Blocking
|
||||
pub fn setup_download(&self) -> Result<(), ApplicationDownloadError> {
|
||||
pub fn setup_download(&self, app_handle: &AppHandle) -> Result<(), ApplicationDownloadError> {
|
||||
self.ensure_manifest_exists()?;
|
||||
|
||||
self.ensure_contexts()?;
|
||||
|
||||
self.control_flag.set(DownloadThreadControlFlag::Go);
|
||||
|
||||
let mut db_lock = borrow_db_mut_checked();
|
||||
db_lock.applications.transient_statuses.insert(
|
||||
self.metadata(),
|
||||
ApplicationTransientStatus::Downloading {
|
||||
version_name: self.version.clone(),
|
||||
},
|
||||
);
|
||||
push_game_update(
|
||||
app_handle,
|
||||
&self.metadata().id,
|
||||
None,
|
||||
GameStatusManager::fetch_state(&self.metadata().id, &db_lock),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Blocking
|
||||
pub fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||
self.setup_download()?;
|
||||
self.set_progress_object_params();
|
||||
self.setup_download(app_handle)?;
|
||||
let timer = Instant::now();
|
||||
push_game_update(
|
||||
app_handle,
|
||||
&self.metadata().id,
|
||||
None,
|
||||
(
|
||||
None,
|
||||
Some(ApplicationTransientStatus::Downloading {
|
||||
version_name: self.version.clone(),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
info!("beginning download for {}...", self.metadata().id);
|
||||
|
||||
let res = self
|
||||
.run()
|
||||
.map_err(|()| ApplicationDownloadError::DownloadError);
|
||||
@ -166,12 +174,8 @@ impl GameDownloadAgent {
|
||||
Err(ApplicationDownloadError::Lock)
|
||||
}
|
||||
|
||||
fn set_progress_object_params(&self) {
|
||||
// Avoid re-setting it
|
||||
if self.progress.get_max() != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sets it up for both download and validate
|
||||
fn setup_progress(&self) {
|
||||
let contexts = self.contexts.lock().unwrap();
|
||||
|
||||
let length = contexts.len();
|
||||
@ -180,7 +184,7 @@ impl GameDownloadAgent {
|
||||
|
||||
self.progress.set_max(chunk_count);
|
||||
self.progress.set_size(length);
|
||||
self.progress.set_time_now();
|
||||
self.progress.reset();
|
||||
}
|
||||
|
||||
pub fn ensure_contexts(&self) -> Result<(), ApplicationDownloadError> {
|
||||
@ -188,7 +192,7 @@ impl GameDownloadAgent {
|
||||
self.generate_contexts()?;
|
||||
}
|
||||
|
||||
*self.context_map.lock().unwrap() = self.stored_manifest.get_contexts();
|
||||
*self.context_map.lock().unwrap() = self.dropdata.get_contexts();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -198,7 +202,7 @@ impl GameDownloadAgent {
|
||||
let game_id = self.id.clone();
|
||||
|
||||
let mut contexts = Vec::new();
|
||||
let base_path = Path::new(&self.stored_manifest.base_path);
|
||||
let base_path = Path::new(&self.dropdata.base_path);
|
||||
create_dir_all(base_path).unwrap();
|
||||
|
||||
for (raw_path, chunk) in manifest {
|
||||
@ -207,11 +211,12 @@ impl GameDownloadAgent {
|
||||
let container = path.parent().unwrap();
|
||||
create_dir_all(container).unwrap();
|
||||
|
||||
let already_exists = path.exists();
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(path.clone())
|
||||
.unwrap();
|
||||
let mut running_offset = 0;
|
||||
@ -232,12 +237,12 @@ impl GameDownloadAgent {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
if running_offset > 0 {
|
||||
if running_offset > 0 && !already_exists {
|
||||
let _ = fallocate(file, FallocateFlags::empty(), 0, running_offset);
|
||||
}
|
||||
}
|
||||
let existing_contexts = self.stored_manifest.get_completed_contexts();
|
||||
self.stored_manifest.set_contexts(
|
||||
let existing_contexts = self.dropdata.get_completed_contexts();
|
||||
self.dropdata.set_contexts(
|
||||
&contexts
|
||||
.iter()
|
||||
.map(|x| (x.checksum.clone(), existing_contexts.contains(&x.checksum)))
|
||||
@ -249,8 +254,8 @@ impl GameDownloadAgent {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Change return value on Err
|
||||
pub fn run(&self) -> Result<bool, ()> {
|
||||
fn run(&self) -> Result<bool, ()> {
|
||||
self.setup_progress();
|
||||
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
||||
|
||||
debug!(
|
||||
@ -266,7 +271,6 @@ impl GameDownloadAgent {
|
||||
let completed_indexes_loop_arc = completed_contexts.clone();
|
||||
|
||||
let contexts = self.contexts.lock().unwrap();
|
||||
debug!("{contexts:#?}");
|
||||
pool.scope(|scope| {
|
||||
let client = &DROP_CLIENT_SYNC.clone();
|
||||
let context_map = self.context_map.lock().unwrap();
|
||||
@ -278,7 +282,10 @@ impl GameDownloadAgent {
|
||||
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
|
||||
|
||||
// If we've done this one already, skip it
|
||||
if Some(&true) == context_map.get(&context.checksum) {
|
||||
// Note to future DecDuck, DropData gets loaded into context_map
|
||||
if let Some(v) = context_map.get(&context.checksum)
|
||||
&& *v
|
||||
{
|
||||
progress_handle.skip(context.length);
|
||||
continue;
|
||||
}
|
||||
@ -345,8 +352,8 @@ impl GameDownloadAgent {
|
||||
.collect::<Vec<(String, bool)>>();
|
||||
drop(context_map_lock);
|
||||
|
||||
self.stored_manifest.set_contexts(&contexts);
|
||||
self.stored_manifest.write();
|
||||
self.dropdata.set_contexts(&contexts);
|
||||
self.dropdata.write();
|
||||
|
||||
// If there are any contexts left which are false
|
||||
if !contexts.iter().all(|x| x.1) {
|
||||
@ -361,6 +368,93 @@ impl GameDownloadAgent {
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn setup_validate(&self, app_handle: &AppHandle) {
|
||||
self.setup_progress();
|
||||
|
||||
self.control_flag.set(DownloadThreadControlFlag::Go);
|
||||
|
||||
let mut db_lock = borrow_db_mut_checked();
|
||||
db_lock.applications.transient_statuses.insert(
|
||||
self.metadata(),
|
||||
ApplicationTransientStatus::Validating {
|
||||
version_name: self.version.clone(),
|
||||
},
|
||||
);
|
||||
push_game_update(
|
||||
app_handle,
|
||||
&self.metadata().id,
|
||||
None,
|
||||
GameStatusManager::fetch_state(&self.metadata().id, &db_lock),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||
self.setup_validate(app_handle);
|
||||
|
||||
let contexts = self.contexts.lock().unwrap();
|
||||
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
||||
|
||||
debug!(
|
||||
"validating game: {} with {} threads",
|
||||
self.dropdata.game_id, max_download_threads
|
||||
);
|
||||
let pool = ThreadPoolBuilder::new()
|
||||
.num_threads(max_download_threads)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let invalid_chunks = Arc::new(boxcar::Vec::new());
|
||||
pool.scope(|scope| {
|
||||
for (index, context) in contexts.iter().enumerate() {
|
||||
let current_progress = self.progress.get(index);
|
||||
let progress_handle = ProgressHandle::new(current_progress, self.progress.clone());
|
||||
let invalid_chunks_scoped = invalid_chunks.clone();
|
||||
let sender = self.sender.clone();
|
||||
|
||||
scope.spawn(move |_| {
|
||||
match validate_game_chunk(context, &self.control_flag, progress_handle) {
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
invalid_chunks_scoped.push(context.checksum.clone());
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// If there are any contexts left which are false
|
||||
if !invalid_chunks.is_empty() {
|
||||
info!("validation of game id {} failed", self.id);
|
||||
|
||||
for context in invalid_chunks.iter() {
|
||||
self.dropdata.set_context(context.1.clone(), false);
|
||||
}
|
||||
|
||||
self.dropdata.write();
|
||||
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn cancel(&self, app_handle: &AppHandle) {
|
||||
// See docs on usage
|
||||
set_partially_installed(
|
||||
&self.metadata(),
|
||||
self.dropdata.base_path.to_str().unwrap().to_string(),
|
||||
Some(app_handle),
|
||||
);
|
||||
|
||||
self.dropdata.write();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Downloadable for GameDownloadAgent {
|
||||
@ -369,6 +463,11 @@ impl Downloadable for GameDownloadAgent {
|
||||
self.download(app_handle)
|
||||
}
|
||||
|
||||
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||
*self.status.lock().unwrap() = DownloadStatus::Validating;
|
||||
self.validate(app_handle)
|
||||
}
|
||||
|
||||
fn progress(&self) -> Arc<ProgressObject> {
|
||||
self.progress.clone()
|
||||
}
|
||||
@ -407,37 +506,25 @@ impl Downloadable for GameDownloadAgent {
|
||||
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
||||
on_game_complete(
|
||||
&self.metadata(),
|
||||
self.stored_manifest.base_path.to_string_lossy().to_string(),
|
||||
self.dropdata.base_path.to_string_lossy().to_string(),
|
||||
app_handle,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// TODO: fix this function. It doesn't restart the download properly, nor does it reset the state properly
|
||||
fn on_incomplete(&self, app_handle: &tauri::AppHandle) {
|
||||
on_game_incomplete(
|
||||
&self.metadata(),
|
||||
self.stored_manifest.base_path.to_string_lossy().to_string(),
|
||||
app_handle,
|
||||
)
|
||||
.unwrap();
|
||||
println!("Attempting to redownload");
|
||||
fn on_cancelled(&self, app_handle: &tauri::AppHandle) {
|
||||
self.cancel(app_handle);
|
||||
/*
|
||||
on_game_incomplete(
|
||||
&self.metadata(),
|
||||
self.dropdata.base_path.to_string_lossy().to_string(),
|
||||
app_handle,
|
||||
)
|
||||
.unwrap();
|
||||
*/
|
||||
}
|
||||
|
||||
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
|
||||
|
||||
fn status(&self) -> DownloadStatus {
|
||||
self.status.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<bool, ApplicationDownloadError> {
|
||||
*self.status.lock().unwrap() = DownloadStatus::Validating;
|
||||
game_validate_logic(
|
||||
&self.stored_manifest,
|
||||
self.contexts.lock().unwrap().clone(),
|
||||
self.progress.clone(),
|
||||
self.sender.clone(),
|
||||
&self.control_flag,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
use std::{
|
||||
collections::HashMap, fs::File, io::{Read, Write}, path::PathBuf
|
||||
collections::HashMap, fs::File, io::{self, Read, Write}, path::{Path, PathBuf}
|
||||
};
|
||||
|
||||
use log::{debug, error, info, warn};
|
||||
use log::error;
|
||||
use native_model::{Decode, Encode};
|
||||
|
||||
pub type DropData = v1::DropData;
|
||||
|
||||
static DROP_DATA_PATH: &str = ".dropdata";
|
||||
pub static DROP_DATA_PATH: &str = ".dropdata";
|
||||
|
||||
pub mod v1 {
|
||||
use std::{collections::HashMap, path::PathBuf, sync::Mutex};
|
||||
@ -38,27 +38,18 @@ pub mod v1 {
|
||||
|
||||
impl DropData {
|
||||
pub fn generate(game_id: String, game_version: String, base_path: PathBuf) -> Self {
|
||||
let mut file = if let Ok(file) = File::open(base_path.join(DROP_DATA_PATH)) { file } else {
|
||||
debug!("Generating new dropdata for game {game_id}");
|
||||
return DropData::new(game_id, game_version, base_path);
|
||||
};
|
||||
match DropData::read(&base_path) {
|
||||
Ok(v) => v,
|
||||
Err(_) => DropData::new(game_id, game_version, base_path),
|
||||
}
|
||||
}
|
||||
pub fn read(base_path: &Path) -> Result<Self, io::Error> {
|
||||
let mut file = File::open(base_path.join(DROP_DATA_PATH))?;
|
||||
|
||||
let mut s = Vec::new();
|
||||
match file.read_to_end(&mut s) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
return DropData::new(game_id, game_version, base_path);
|
||||
}
|
||||
}
|
||||
file.read_to_end(&mut s)?;
|
||||
|
||||
match native_model::rmp_serde_1_3::RmpSerde::decode(s) {
|
||||
Ok(manifest) => manifest,
|
||||
Err(e) => {
|
||||
warn!("{e}");
|
||||
DropData::new(game_id, game_version, base_path)
|
||||
}
|
||||
}
|
||||
Ok(native_model::rmp_serde_1_3::RmpSerde::decode(s).unwrap())
|
||||
}
|
||||
pub fn write(&self) {
|
||||
let manifest_raw = match native_model::rmp_serde_1_3::RmpSerde::encode(&self) {
|
||||
@ -94,10 +85,6 @@ impl DropData {
|
||||
.collect()
|
||||
}
|
||||
pub fn get_contexts(&self) -> HashMap<String, bool> {
|
||||
info!(
|
||||
"Any contexts which are complete? {}",
|
||||
self.contexts.lock().unwrap().iter().any(|x| *x.1)
|
||||
);
|
||||
self.contexts.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
pub mod commands;
|
||||
pub mod download_agent;
|
||||
mod download_logic;
|
||||
mod drop_data;
|
||||
pub mod drop_data;
|
||||
mod manifest;
|
||||
pub mod validate;
|
||||
|
||||
@ -1,99 +1,22 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufWriter, Read, Seek, SeekFrom, Write},
|
||||
sync::{Arc, mpsc::Sender},
|
||||
};
|
||||
|
||||
use log::{debug, error, info};
|
||||
use log::debug;
|
||||
use md5::Context;
|
||||
use rayon::ThreadPoolBuilder;
|
||||
|
||||
use crate::{
|
||||
database::db::borrow_db_checked,
|
||||
download_manager::{
|
||||
download_manager_frontend::DownloadManagerSignal,
|
||||
download_manager::
|
||||
util::{
|
||||
download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
|
||||
progress_object::{ProgressHandle, ProgressObject},
|
||||
},
|
||||
},
|
||||
progress_object::ProgressHandle,
|
||||
}
|
||||
,
|
||||
error::application_download_error::ApplicationDownloadError,
|
||||
games::downloads::{drop_data::DropData, manifest::DropDownloadContext},
|
||||
games::downloads::manifest::DropDownloadContext,
|
||||
};
|
||||
|
||||
pub fn game_validate_logic(
|
||||
dropdata: &DropData,
|
||||
contexts: Vec<DropDownloadContext>,
|
||||
progress: Arc<ProgressObject>,
|
||||
sender: Sender<DownloadManagerSignal>,
|
||||
control_flag: &DownloadThreadControl,
|
||||
) -> Result<bool, ApplicationDownloadError> {
|
||||
progress.reset(contexts.len());
|
||||
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
||||
|
||||
debug!(
|
||||
"validating game: {} with {} threads",
|
||||
dropdata.game_id, max_download_threads
|
||||
);
|
||||
let pool = ThreadPoolBuilder::new()
|
||||
.num_threads(max_download_threads)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
debug!("{contexts:#?}");
|
||||
let invalid_chunks = Arc::new(boxcar::Vec::new());
|
||||
pool.scope(|scope| {
|
||||
for (index, context) in contexts.iter().enumerate() {
|
||||
let current_progress = progress.get(index);
|
||||
let progress_handle = ProgressHandle::new(current_progress, progress.clone());
|
||||
let invalid_chunks_scoped = invalid_chunks.clone();
|
||||
let sender = sender.clone();
|
||||
|
||||
scope.spawn(move |_| {
|
||||
match validate_game_chunk(context, control_flag, progress_handle) {
|
||||
Ok(true) => {
|
||||
debug!(
|
||||
"Finished context #{} with checksum {}",
|
||||
index, context.checksum
|
||||
);
|
||||
}
|
||||
Ok(false) => {
|
||||
debug!(
|
||||
"Didn't finish context #{} with checksum {}",
|
||||
index, &context.checksum
|
||||
);
|
||||
invalid_chunks_scoped.push(context.checksum.clone());
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// If there are any contexts left which are false
|
||||
if !invalid_chunks.is_empty() {
|
||||
info!(
|
||||
"validation of game id {} failed for chunks {:?}",
|
||||
dropdata.game_id.clone(),
|
||||
invalid_chunks
|
||||
);
|
||||
|
||||
for context in invalid_chunks.iter() {
|
||||
dropdata.set_context(context.1.clone(), false);
|
||||
}
|
||||
|
||||
dropdata.write();
|
||||
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn validate_game_chunk(
|
||||
ctx: &DropDownloadContext,
|
||||
control_flag: &DownloadThreadControl,
|
||||
@ -129,10 +52,6 @@ pub fn validate_game_chunk(
|
||||
|
||||
let res = hex::encode(hasher.compute().0);
|
||||
if res != ctx.checksum {
|
||||
println!(
|
||||
"Checksum failed. Correct: {}, actual: {}",
|
||||
&ctx.checksum, &res
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,9 @@ use serde::{Deserialize, Serialize};
|
||||
use tauri::AppHandle;
|
||||
use tauri::Emitter;
|
||||
|
||||
use crate::AppState;
|
||||
use crate::database::db::{borrow_db_checked, borrow_db_mut_checked};
|
||||
use crate::database::models::data::Database;
|
||||
use crate::database::models::data::{
|
||||
ApplicationTransientStatus, DownloadableMetadata, GameDownloadStatus, GameVersion,
|
||||
};
|
||||
@ -16,11 +18,11 @@ use crate::error::library_error::LibraryError;
|
||||
use crate::error::remote_access_error::RemoteAccessError;
|
||||
use crate::games::state::{GameStatusManager, GameStatusWithTransient};
|
||||
use crate::remote::auth::generate_authorization_header;
|
||||
use crate::remote::cache::cache_object_db;
|
||||
use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db};
|
||||
use crate::remote::requests::make_request;
|
||||
use crate::remote::utils::DROP_CLIENT_SYNC;
|
||||
use crate::AppState;
|
||||
use bitcode::{Encode, Decode};
|
||||
use bitcode::{Decode, Encode};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct FetchGameStruct {
|
||||
@ -146,27 +148,22 @@ pub fn fetch_game_logic(
|
||||
) -> Result<FetchGameStruct, RemoteAccessError> {
|
||||
let mut state_handle = state.lock().unwrap();
|
||||
|
||||
let handle = borrow_db_checked();
|
||||
let db_lock = borrow_db_checked();
|
||||
|
||||
let metadata_option = handle.applications.installed_game_version.get(&id);
|
||||
let metadata_option = db_lock.applications.installed_game_version.get(&id);
|
||||
let version = match metadata_option {
|
||||
None => None,
|
||||
Some(metadata) => Some(
|
||||
handle
|
||||
.applications
|
||||
.game_versions
|
||||
.get(&metadata.id)
|
||||
.unwrap()
|
||||
.get(metadata.version.as_ref().unwrap())
|
||||
.unwrap()
|
||||
.clone(),
|
||||
),
|
||||
Some(metadata) => db_lock
|
||||
.applications
|
||||
.game_versions
|
||||
.get(&metadata.id)
|
||||
.map(|v| v.get(metadata.version.as_ref().unwrap()).unwrap())
|
||||
.cloned(),
|
||||
};
|
||||
drop(handle);
|
||||
|
||||
let game = state_handle.games.get(&id);
|
||||
if let Some(game) = game {
|
||||
let status = GameStatusManager::fetch_state(&id);
|
||||
let status = GameStatusManager::fetch_state(&id, &db_lock);
|
||||
|
||||
let data = FetchGameStruct {
|
||||
game: game.clone(),
|
||||
@ -174,10 +171,12 @@ pub fn fetch_game_logic(
|
||||
version,
|
||||
};
|
||||
|
||||
cache_object(&id, game)?;
|
||||
cache_object_db(&id, game, &db_lock)?;
|
||||
|
||||
return Ok(data);
|
||||
}
|
||||
drop(db_lock);
|
||||
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(&client, &["/api/v1/client/game/", &id], &[], |r| {
|
||||
r.header("Authorization", generate_authorization_header())
|
||||
@ -203,9 +202,10 @@ pub fn fetch_game_logic(
|
||||
.game_statuses
|
||||
.entry(id.clone())
|
||||
.or_insert(GameDownloadStatus::Remote {});
|
||||
drop(db_handle);
|
||||
|
||||
let status = GameStatusManager::fetch_state(&id);
|
||||
let status = GameStatusManager::fetch_state(&id, &db_handle);
|
||||
|
||||
drop(db_handle);
|
||||
|
||||
let data = FetchGameStruct {
|
||||
game: game.clone(),
|
||||
@ -222,12 +222,12 @@ pub fn fetch_game_logic_offline(
|
||||
id: String,
|
||||
_state: tauri::State<'_, Mutex<AppState>>,
|
||||
) -> Result<FetchGameStruct, RemoteAccessError> {
|
||||
let handle = borrow_db_checked();
|
||||
let metadata_option = handle.applications.installed_game_version.get(&id);
|
||||
let db_handle = borrow_db_checked();
|
||||
let metadata_option = db_handle.applications.installed_game_version.get(&id);
|
||||
let version = match metadata_option {
|
||||
None => None,
|
||||
Some(metadata) => Some(
|
||||
handle
|
||||
db_handle
|
||||
.applications
|
||||
.game_versions
|
||||
.get(&metadata.id)
|
||||
@ -237,11 +237,12 @@ pub fn fetch_game_logic_offline(
|
||||
.clone(),
|
||||
),
|
||||
};
|
||||
drop(handle);
|
||||
|
||||
let status = GameStatusManager::fetch_state(&id);
|
||||
let status = GameStatusManager::fetch_state(&id, &db_handle);
|
||||
let game = get_cached_object::<Game>(&id)?;
|
||||
|
||||
drop(db_handle);
|
||||
|
||||
Ok(FetchGameStruct {
|
||||
game,
|
||||
status,
|
||||
@ -275,7 +276,11 @@ pub fn fetch_game_verion_options_logic(
|
||||
let process_manager_lock = state_lock.process_manager.lock().unwrap();
|
||||
let data: Vec<GameVersion> = data
|
||||
.into_iter()
|
||||
.filter(|v| process_manager_lock.valid_platform(&v.platform).unwrap())
|
||||
.filter(|v| {
|
||||
process_manager_lock
|
||||
.valid_platform(&v.platform, &state_lock)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
drop(process_manager_lock);
|
||||
drop(state_lock);
|
||||
@ -283,6 +288,49 @@ pub fn fetch_game_verion_options_logic(
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by:
|
||||
* - on_cancel, when cancelled, for obvious reasons
|
||||
* - when downloading, so if drop unexpectedly quits, we can resume the download. hidden by the "Downloading..." transient state, though
|
||||
* - when scanning, to import the game
|
||||
*/
|
||||
pub fn set_partially_installed(
|
||||
meta: &DownloadableMetadata,
|
||||
install_dir: String,
|
||||
app_handle: Option<&AppHandle>,
|
||||
) {
|
||||
set_partially_installed_db(&mut borrow_db_mut_checked(), meta, install_dir, app_handle);
|
||||
}
|
||||
|
||||
pub fn set_partially_installed_db(
|
||||
db_lock: &mut Database,
|
||||
meta: &DownloadableMetadata,
|
||||
install_dir: String,
|
||||
app_handle: Option<&AppHandle>,
|
||||
) {
|
||||
db_lock.applications.transient_statuses.remove(meta);
|
||||
db_lock.applications.game_statuses.insert(
|
||||
meta.id.clone(),
|
||||
GameDownloadStatus::PartiallyInstalled {
|
||||
version_name: meta.version.as_ref().unwrap().clone(),
|
||||
install_dir,
|
||||
},
|
||||
);
|
||||
db_lock
|
||||
.applications
|
||||
.installed_game_version
|
||||
.insert(meta.id.clone(), meta.clone());
|
||||
|
||||
if let Some(app_handle) = app_handle {
|
||||
push_game_update(
|
||||
app_handle,
|
||||
&meta.id,
|
||||
None,
|
||||
GameStatusManager::fetch_state(&meta.id, db_lock),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) {
|
||||
debug!("triggered uninstall for agent");
|
||||
let mut db_handle = borrow_db_mut_checked();
|
||||
@ -296,7 +344,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
||||
app_handle,
|
||||
&meta.id,
|
||||
None,
|
||||
(None, Some(ApplicationTransientStatus::Uninstalling {})),
|
||||
GameStatusManager::fetch_state(&meta.id, &db_handle),
|
||||
);
|
||||
|
||||
let previous_state = db_handle.applications.game_statuses.get(&meta.id).cloned();
|
||||
@ -330,31 +378,35 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
||||
drop(db_handle);
|
||||
|
||||
let app_handle = app_handle.clone();
|
||||
spawn(move || if let Err(e) = remove_dir_all(install_dir) {
|
||||
error!("{e}");
|
||||
} else {
|
||||
let mut db_handle = borrow_db_mut_checked();
|
||||
db_handle.applications.transient_statuses.remove(&meta);
|
||||
db_handle
|
||||
.applications
|
||||
.installed_game_version
|
||||
.remove(&meta.id);
|
||||
db_handle
|
||||
.applications
|
||||
.game_statuses
|
||||
.entry(meta.id.clone())
|
||||
.and_modify(|e| *e = GameDownloadStatus::Remote {});
|
||||
drop(db_handle);
|
||||
spawn(move || {
|
||||
if let Err(e) = remove_dir_all(install_dir) {
|
||||
error!("{e}");
|
||||
} else {
|
||||
let mut db_handle = borrow_db_mut_checked();
|
||||
db_handle.applications.transient_statuses.remove(&meta);
|
||||
db_handle
|
||||
.applications
|
||||
.installed_game_version
|
||||
.remove(&meta.id);
|
||||
db_handle
|
||||
.applications
|
||||
.game_statuses
|
||||
.entry(meta.id.clone())
|
||||
.and_modify(|e| *e = GameDownloadStatus::Remote {});
|
||||
let _ = db_handle.applications.transient_statuses.remove(&meta);
|
||||
|
||||
debug!("uninstalled game id {}", &meta.id);
|
||||
app_handle.emit("update_library", ()).unwrap();
|
||||
push_game_update(
|
||||
&app_handle,
|
||||
&meta.id,
|
||||
None,
|
||||
GameStatusManager::fetch_state(&meta.id, &db_handle),
|
||||
);
|
||||
|
||||
push_game_update(
|
||||
&app_handle,
|
||||
&meta.id,
|
||||
None,
|
||||
(Some(GameDownloadStatus::Remote {}), None),
|
||||
);
|
||||
debug!("uninstalled game id {}", &meta.id);
|
||||
app_handle.emit("update_library", ()).unwrap();
|
||||
|
||||
drop(db_handle);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
warn!("invalid previous state for uninstall, failing silently.");
|
||||
@ -369,66 +421,6 @@ pub fn get_current_meta(game_id: &String) -> Option<DownloadableMetadata> {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn on_game_incomplete(
|
||||
meta: &DownloadableMetadata,
|
||||
install_dir: String,
|
||||
app_handle: &AppHandle,
|
||||
) -> Result<(), RemoteAccessError> {
|
||||
// Fetch game version information from remote
|
||||
if meta.version.is_none() {
|
||||
return Err(RemoteAccessError::GameNotFound(meta.id.clone()));
|
||||
}
|
||||
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(
|
||||
&client,
|
||||
&["/api/v1/client/game/version"],
|
||||
&[
|
||||
("id", &meta.id),
|
||||
("version", meta.version.as_ref().unwrap()),
|
||||
],
|
||||
|f| f.header("Authorization", generate_authorization_header()),
|
||||
)?
|
||||
.send()?;
|
||||
|
||||
let game_version: GameVersion = response.json()?;
|
||||
|
||||
let mut handle = borrow_db_mut_checked();
|
||||
handle
|
||||
.applications
|
||||
.game_versions
|
||||
.entry(meta.id.clone())
|
||||
.or_default()
|
||||
.insert(meta.version.clone().unwrap(), game_version.clone());
|
||||
handle
|
||||
.applications
|
||||
.installed_game_version
|
||||
.insert(meta.id.clone(), meta.clone());
|
||||
|
||||
let status = GameDownloadStatus::PartiallyInstalled {
|
||||
version_name: meta.version.clone().unwrap(),
|
||||
install_dir,
|
||||
};
|
||||
|
||||
handle
|
||||
.applications
|
||||
.game_statuses
|
||||
.insert(meta.id.clone(), status.clone());
|
||||
drop(handle);
|
||||
app_handle
|
||||
.emit(
|
||||
&format!("update_game/{}", meta.id),
|
||||
GameUpdateEvent {
|
||||
game_id: meta.id.clone(),
|
||||
status: (Some(status), None),
|
||||
version: Some(game_version),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_game_complete(
|
||||
meta: &DownloadableMetadata,
|
||||
install_dir: String,
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
use crate::database::{
|
||||
db::borrow_db_checked,
|
||||
models::data::{ApplicationTransientStatus, GameDownloadStatus},
|
||||
};
|
||||
use crate::database::models::data::{ApplicationTransientStatus, Database, GameDownloadStatus};
|
||||
|
||||
pub type GameStatusWithTransient = (
|
||||
Option<GameDownloadStatus>,
|
||||
@ -10,14 +7,12 @@ pub type GameStatusWithTransient = (
|
||||
pub struct GameStatusManager {}
|
||||
|
||||
impl GameStatusManager {
|
||||
pub fn fetch_state(game_id: &String) -> GameStatusWithTransient {
|
||||
let db_lock = borrow_db_checked();
|
||||
let online_state = match db_lock.applications.installed_game_version.get(game_id) {
|
||||
Some(meta) => db_lock.applications.transient_statuses.get(meta).cloned(),
|
||||
pub fn fetch_state(game_id: &String, database: &Database) -> GameStatusWithTransient {
|
||||
let online_state = match database.applications.installed_game_version.get(game_id) {
|
||||
Some(meta) => database.applications.transient_statuses.get(meta).cloned(),
|
||||
None => None,
|
||||
};
|
||||
let offline_state = db_lock.applications.game_statuses.get(game_id).cloned();
|
||||
drop(db_lock);
|
||||
let offline_state = database.applications.game_statuses.get(game_id).cloned();
|
||||
|
||||
if online_state.is_some() {
|
||||
return (None, online_state);
|
||||
|
||||
Reference in New Issue
Block a user