From d9d0122c3dc35da2a58370334b429c2645aca87c Mon Sep 17 00:00:00 2001 From: quexeky Date: Tue, 24 Dec 2024 12:21:00 +1100 Subject: [PATCH] feat(downloads): Added manifest.json utility for persistent download progress Signed-off-by: quexeky --- src-tauri/src/cleanup.rs | 18 ++++++ src-tauri/src/downloads/download_agent.rs | 32 +++++++++-- src-tauri/src/downloads/mod.rs | 1 + src-tauri/src/downloads/stored_manifest.rs | 66 ++++++++++++++++++++++ src-tauri/src/lib.rs | 15 +---- 5 files changed, 114 insertions(+), 18 deletions(-) create mode 100644 src-tauri/src/cleanup.rs create mode 100644 src-tauri/src/downloads/stored_manifest.rs diff --git a/src-tauri/src/cleanup.rs b/src-tauri/src/cleanup.rs new file mode 100644 index 0000000..2fe2d2d --- /dev/null +++ b/src-tauri/src/cleanup.rs @@ -0,0 +1,18 @@ +use std::sync::Mutex; + +use log::info; +use tauri::AppHandle; + +use crate::AppState; + +#[tauri::command] +pub fn quit(app: tauri::AppHandle) { + cleanup_and_exit(&app); +} + + +pub fn cleanup_and_exit(app: &AppHandle, ) { + info!("exiting drop application..."); + + app.exit(0); +} diff --git a/src-tauri/src/downloads/download_agent.rs b/src-tauri/src/downloads/download_agent.rs index 5a6fad7..07e7f7c 100644 --- a/src-tauri/src/downloads/download_agent.rs +++ b/src-tauri/src/downloads/download_agent.rs @@ -25,6 +25,7 @@ use super::download_logic::download_game_chunk; use super::download_manager::DownloadManagerSignal; use super::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}; use super::progress_object::ProgressObject; +use super::stored_manifest::StoredManifest; pub struct GameDownloadAgent { pub id: String, @@ -36,6 +37,7 @@ pub struct GameDownloadAgent { pub manifest: Mutex>, pub progress: Arc, sender: Sender, + stored_manifest: StoredManifest } #[derive(Debug)] @@ -91,6 +93,8 @@ impl GameDownloadAgent { let base_dir_path = Path::new(&base_dir); let data_base_dir_path = base_dir_path.join(id.clone()); + let stored_manifest = StoredManifest::generate(id.clone(), version.clone(), data_base_dir_path.clone()); + Self { id, version, @@ -101,6 +105,7 @@ impl GameDownloadAgent { completed_contexts: Mutex::new(Vec::new()), progress: Arc::new(ProgressObject::new(0, 0, sender.clone())), sender, + stored_manifest, } } @@ -215,6 +220,11 @@ impl GameDownloadAgent { let base_path = Path::new(&self.base_dir); create_dir_all(base_path).unwrap(); + *self.completed_contexts.lock().unwrap() = self.stored_manifest.get_completed_contexts(); + + info!("Completed contexts: {:?}", *self.completed_contexts.lock().unwrap()); + + for (raw_path, chunk) in manifest { let path = base_path.join(Path::new(&raw_path)); @@ -265,15 +275,17 @@ impl GameDownloadAgent { let completed_lock = self.completed_contexts.lock().unwrap(); for (index, context) in self.contexts.iter().enumerate() { + let progress = self.progress.get(index); // Clone arcs + let progress_handle = ProgressHandle::new(progress, self.progress.clone()); // If we've done this one already, skip it if completed_lock.contains(&index) { + info!("Skipping index {}", index); + progress_handle.add(context.length); continue; } let context = context.clone(); let control_flag = self.control_flag.clone(); // Clone arcs - let progress = self.progress.get(index); // Clone arcs - let progress_handle = ProgressHandle::new(progress, self.progress.clone()); let completed_indexes_ref = completed_indexes_loop_arc.clone(); scope.spawn(move |_| { @@ -293,14 +305,22 @@ impl GameDownloadAgent { } }); - let mut completed_lock = self.completed_contexts.lock().unwrap(); - let newly_completed_lock = completed_indexes.lock().unwrap(); + let completed_lock_len = { + let mut completed_lock = self.completed_contexts.lock().unwrap(); + let newly_completed_lock = completed_indexes.lock().unwrap(); + + completed_lock.extend(newly_completed_lock.iter()); - completed_lock.extend(newly_completed_lock.iter()); + completed_lock.len() + }; // If we're not out of contexts, we're not done, so we don't fire completed - if completed_lock.len() != self.contexts.len() { + if completed_lock_len != self.contexts.len() { info!("da for {} exited without completing", self.id.clone()); + self.stored_manifest.set_completed_contexts(&self.completed_contexts); + info!("Setting completed contexts"); + self.stored_manifest.write(); + info!("Wrote completed contexts"); return Ok(()); } diff --git a/src-tauri/src/downloads/mod.rs b/src-tauri/src/downloads/mod.rs index 0102c33..023b2c7 100644 --- a/src-tauri/src/downloads/mod.rs +++ b/src-tauri/src/downloads/mod.rs @@ -7,3 +7,4 @@ mod download_thread_control_flag; mod manifest; mod progress_object; pub mod queue; +mod stored_manifest; \ No newline at end of file diff --git a/src-tauri/src/downloads/stored_manifest.rs b/src-tauri/src/downloads/stored_manifest.rs new file mode 100644 index 0000000..27b7ba7 --- /dev/null +++ b/src-tauri/src/downloads/stored_manifest.rs @@ -0,0 +1,66 @@ +use std::{default, fs::File, io::{Read, Write}, path::{Path, PathBuf}, sync::Mutex}; + +use log::{error, info}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct StoredManifest { + game_id: String, + game_version: String, + pub completed_contexts: Mutex>, + base_path: PathBuf +} + +impl StoredManifest { + pub fn new(game_id: String, game_version: String, base_path: PathBuf) -> Self { + Self { + base_path, + game_id, + game_version, + completed_contexts: Mutex::new(Vec::new()), + } + } + pub fn generate(game_id: String, game_version: String, base_path: PathBuf) -> Self { + let mut file = match File::open(base_path.join("manifest.json")) { + Ok(file) => file, + Err(_) => return StoredManifest::new(game_id, game_version, base_path), + }; + + let mut s = String::new(); + match file.read_to_string(&mut s) { + Ok(_) => {}, + Err(e) => { error!("{}", e); return StoredManifest::new(game_id, game_version, base_path) }, + }; + + info!("Contexts string: {}", s); + let manifest = match serde_json::from_str::(&s) { + Ok(manifest) => manifest, + Err(e) => { error!("{}", e); StoredManifest::new(game_id, game_version, base_path) }, + }; + info!("Completed manifest: {:?}", manifest); + + return manifest; + } + pub fn write(&self) { + let manifest_json = match serde_json::to_string(&self) { + Ok(json) => json, + Err(_) => return, + }; + + let mut file = match File::create(self.base_path.join("manifest.json")) { + Ok(file) => file, + Err(e) => { error!("{}", e); return; }, + }; + + match file.write_all(manifest_json.as_bytes()) { + Ok(_) => {}, + Err(e) => error!("{}", e), + }; + } + pub fn set_completed_contexts(&self, completed_contexts: &Mutex>) { + *self.completed_contexts.lock().unwrap() = completed_contexts.lock().unwrap().clone(); + } + pub fn get_completed_contexts(&self) -> Vec { + self.completed_contexts.lock().unwrap().clone() + } +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e0037b4..5ed468a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -8,9 +8,11 @@ mod remote; mod state; #[cfg(test)] mod tests; +mod cleanup; use crate::db::DatabaseImpls; use auth::{auth_initiate, generate_authorization_header, recieve_handshake, retry_connect}; +use cleanup::{cleanup_and_exit, quit}; use db::{ add_download_dir, delete_download_dir, fetch_download_dir_stats, DatabaseInterface, DATA_ROOT_DIR, @@ -39,7 +41,6 @@ use tauri::menu::{Menu, MenuItem, MenuItemBuilder, PredefinedMenuItem}; use tauri::tray::TrayIconBuilder; use tauri::{AppHandle, Manager, RunEvent, WindowEvent}; use tauri_plugin_deep_link::DeepLinkExt; -use url::Url; #[derive(Clone, Copy, Serialize)] pub enum AppStatus { @@ -82,11 +83,6 @@ fn fetch_state(state: tauri::State<'_, Mutex>) -> Result AppState { let logfile = FileAppender::builder() .encoder(Box::new(PatternEncoder::new("{d} | {l} | {f} - {m}{n}"))) @@ -94,7 +90,7 @@ fn setup(handle: AppHandle) -> AppState { .unwrap(); let console = ConsoleAppender::builder() - .encoder(Box::new(PatternEncoder::new("{t}|{l}|{f} - {m}{n}"))) + .encoder(Box::new(PatternEncoder::new("{d} | {l} | {f} - {m}{n}"))) .build(); let config = Config::builder() @@ -139,11 +135,6 @@ fn setup(handle: AppHandle) -> AppState { } } -pub fn cleanup_and_exit(app: &AppHandle) { - info!("exiting drop application..."); - - app.exit(0); -} pub static DB: LazyLock = LazyLock::new(DatabaseInterface::set_up_database);