mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-14 16:51:18 +10:00
feat(downloads): Added manifest.json utility for persistent download progress
Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
18
src-tauri/src/cleanup.rs
Normal file
18
src-tauri/src/cleanup.rs
Normal file
@ -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);
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ use super::download_logic::download_game_chunk;
|
|||||||
use super::download_manager::DownloadManagerSignal;
|
use super::download_manager::DownloadManagerSignal;
|
||||||
use super::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag};
|
use super::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag};
|
||||||
use super::progress_object::ProgressObject;
|
use super::progress_object::ProgressObject;
|
||||||
|
use super::stored_manifest::StoredManifest;
|
||||||
|
|
||||||
pub struct GameDownloadAgent {
|
pub struct GameDownloadAgent {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@ -36,6 +37,7 @@ pub struct GameDownloadAgent {
|
|||||||
pub manifest: Mutex<Option<DropManifest>>,
|
pub manifest: Mutex<Option<DropManifest>>,
|
||||||
pub progress: Arc<ProgressObject>,
|
pub progress: Arc<ProgressObject>,
|
||||||
sender: Sender<DownloadManagerSignal>,
|
sender: Sender<DownloadManagerSignal>,
|
||||||
|
stored_manifest: StoredManifest
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -91,6 +93,8 @@ impl GameDownloadAgent {
|
|||||||
let base_dir_path = Path::new(&base_dir);
|
let base_dir_path = Path::new(&base_dir);
|
||||||
let data_base_dir_path = base_dir_path.join(id.clone());
|
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 {
|
Self {
|
||||||
id,
|
id,
|
||||||
version,
|
version,
|
||||||
@ -101,6 +105,7 @@ impl GameDownloadAgent {
|
|||||||
completed_contexts: Mutex::new(Vec::new()),
|
completed_contexts: Mutex::new(Vec::new()),
|
||||||
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
|
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
|
||||||
sender,
|
sender,
|
||||||
|
stored_manifest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,6 +220,11 @@ impl GameDownloadAgent {
|
|||||||
let base_path = Path::new(&self.base_dir);
|
let base_path = Path::new(&self.base_dir);
|
||||||
create_dir_all(base_path).unwrap();
|
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 {
|
for (raw_path, chunk) in manifest {
|
||||||
let path = base_path.join(Path::new(&raw_path));
|
let path = base_path.join(Path::new(&raw_path));
|
||||||
|
|
||||||
@ -265,15 +275,17 @@ impl GameDownloadAgent {
|
|||||||
let completed_lock = self.completed_contexts.lock().unwrap();
|
let completed_lock = self.completed_contexts.lock().unwrap();
|
||||||
|
|
||||||
for (index, context) in self.contexts.iter().enumerate() {
|
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 we've done this one already, skip it
|
||||||
if completed_lock.contains(&index) {
|
if completed_lock.contains(&index) {
|
||||||
|
info!("Skipping index {}", index);
|
||||||
|
progress_handle.add(context.length);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
let control_flag = self.control_flag.clone(); // Clone arcs
|
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();
|
let completed_indexes_ref = completed_indexes_loop_arc.clone();
|
||||||
|
|
||||||
scope.spawn(move |_| {
|
scope.spawn(move |_| {
|
||||||
@ -293,14 +305,22 @@ impl GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let completed_lock_len = {
|
||||||
let mut completed_lock = self.completed_contexts.lock().unwrap();
|
let mut completed_lock = self.completed_contexts.lock().unwrap();
|
||||||
let newly_completed_lock = completed_indexes.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 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());
|
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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,3 +7,4 @@ mod download_thread_control_flag;
|
|||||||
mod manifest;
|
mod manifest;
|
||||||
mod progress_object;
|
mod progress_object;
|
||||||
pub mod queue;
|
pub mod queue;
|
||||||
|
mod stored_manifest;
|
||||||
66
src-tauri/src/downloads/stored_manifest.rs
Normal file
66
src-tauri/src/downloads/stored_manifest.rs
Normal file
@ -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<Vec<usize>>,
|
||||||
|
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::<StoredManifest>(&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<Vec<usize>>) {
|
||||||
|
*self.completed_contexts.lock().unwrap() = completed_contexts.lock().unwrap().clone();
|
||||||
|
}
|
||||||
|
pub fn get_completed_contexts(&self) -> Vec<usize> {
|
||||||
|
self.completed_contexts.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,9 +8,11 @@ mod remote;
|
|||||||
mod state;
|
mod state;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
mod cleanup;
|
||||||
|
|
||||||
use crate::db::DatabaseImpls;
|
use crate::db::DatabaseImpls;
|
||||||
use auth::{auth_initiate, generate_authorization_header, recieve_handshake, retry_connect};
|
use auth::{auth_initiate, generate_authorization_header, recieve_handshake, retry_connect};
|
||||||
|
use cleanup::{cleanup_and_exit, quit};
|
||||||
use db::{
|
use db::{
|
||||||
add_download_dir, delete_download_dir, fetch_download_dir_stats, DatabaseInterface,
|
add_download_dir, delete_download_dir, fetch_download_dir_stats, DatabaseInterface,
|
||||||
DATA_ROOT_DIR,
|
DATA_ROOT_DIR,
|
||||||
@ -39,7 +41,6 @@ use tauri::menu::{Menu, MenuItem, MenuItemBuilder, PredefinedMenuItem};
|
|||||||
use tauri::tray::TrayIconBuilder;
|
use tauri::tray::TrayIconBuilder;
|
||||||
use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
|
use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize)]
|
#[derive(Clone, Copy, Serialize)]
|
||||||
pub enum AppStatus {
|
pub enum AppStatus {
|
||||||
@ -82,11 +83,6 @@ fn fetch_state(state: tauri::State<'_, Mutex<AppState>>) -> Result<AppState, Str
|
|||||||
Ok(cloned_state)
|
Ok(cloned_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn quit(app: tauri::AppHandle) {
|
|
||||||
cleanup_and_exit(&app);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup(handle: AppHandle) -> AppState {
|
fn setup(handle: AppHandle) -> AppState {
|
||||||
let logfile = FileAppender::builder()
|
let logfile = FileAppender::builder()
|
||||||
.encoder(Box::new(PatternEncoder::new("{d} | {l} | {f} - {m}{n}")))
|
.encoder(Box::new(PatternEncoder::new("{d} | {l} | {f} - {m}{n}")))
|
||||||
@ -94,7 +90,7 @@ fn setup(handle: AppHandle) -> AppState {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let console = ConsoleAppender::builder()
|
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();
|
.build();
|
||||||
|
|
||||||
let config = Config::builder()
|
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<DatabaseInterface> = LazyLock::new(DatabaseInterface::set_up_database);
|
pub static DB: LazyLock<DatabaseInterface> = LazyLock::new(DatabaseInterface::set_up_database);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user