mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-12 15:52:43 +10:00
refactor(download manager): Fully separate & generic download manager
Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
@ -26,7 +26,6 @@ tauri-build = { version = "2.0.0", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri-plugin-shell = "2.0.0"
|
tauri-plugin-shell = "2.0.0"
|
||||||
serde = { version = "1", features = ["derive", "rc"] }
|
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde-binary = "0.5.0"
|
serde-binary = "0.5.0"
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
@ -84,6 +83,10 @@ features = [] # You can also use "yaml_enc" or "bin_enc"
|
|||||||
version = "0.12"
|
version = "0.12"
|
||||||
features = ["json", "blocking"]
|
features = ["json", "blocking"]
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
version = "1"
|
||||||
|
features = ["derive", "rc"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::{self, create_dir_all},
|
fs::{self, create_dir_all},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{LazyLock, Mutex, RwLockWriteGuard},
|
sync::{Arc, LazyLock, Mutex, RwLockWriteGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use directories::BaseDirs;
|
use directories::BaseDirs;
|
||||||
@ -12,7 +12,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{library::push_application_update, process::process_manager::Platform, state::DownloadStatusManager, DB};
|
use crate::{download_manager::downloadable_metadata::DownloadableMetadata, library::push_game_update, process::process_manager::Platform, state::DownloadStatusManager, DB};
|
||||||
|
|
||||||
#[derive(serde::Serialize, Clone, Deserialize)]
|
#[derive(serde::Serialize, Clone, Deserialize)]
|
||||||
pub struct DatabaseAuth {
|
pub struct DatabaseAuth {
|
||||||
@ -60,11 +60,11 @@ pub struct ApplicationVersion {
|
|||||||
pub struct DatabaseApplications {
|
pub struct DatabaseApplications {
|
||||||
pub install_dirs: Vec<String>,
|
pub install_dirs: Vec<String>,
|
||||||
// Guaranteed to exist if the game also exists in the app state map
|
// Guaranteed to exist if the game also exists in the app state map
|
||||||
pub statuses: HashMap<String, ApplicationStatus>,
|
pub statuses: HashMap<DownloadableMetadata, ApplicationStatus>,
|
||||||
pub versions: HashMap<String, HashMap<String, ApplicationVersion>>,
|
pub versions: HashMap<DownloadableMetadata, HashMap<String, ApplicationVersion>>,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub transient_statuses: HashMap<String, ApplicationTransientStatus>,
|
pub transient_statuses: HashMap<DownloadableMetadata, ApplicationTransientStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
@ -220,9 +220,9 @@ pub fn fetch_download_dir_stats() -> Result<Vec<String>, String> {
|
|||||||
Ok(directories)
|
Ok(directories)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_application_status<F: FnOnce(&mut RwLockWriteGuard<'_, Database>, &String)>(
|
pub fn set_game_status<F: FnOnce(&mut RwLockWriteGuard<'_, Database>, &Arc<DownloadableMetadata>)>(
|
||||||
app_handle: &AppHandle,
|
app_handle: &AppHandle,
|
||||||
id: String,
|
id: Arc<DownloadableMetadata>,
|
||||||
setter: F,
|
setter: F,
|
||||||
) {
|
) {
|
||||||
let mut db_handle = DB.borrow_data_mut().unwrap();
|
let mut db_handle = DB.borrow_data_mut().unwrap();
|
||||||
@ -232,5 +232,5 @@ pub fn set_application_status<F: FnOnce(&mut RwLockWriteGuard<'_, Database>, &St
|
|||||||
|
|
||||||
let status = DownloadStatusManager::fetch_state(&id);
|
let status = DownloadStatusManager::fetch_state(&id);
|
||||||
|
|
||||||
push_application_update(app_handle, id, status);
|
push_game_update(app_handle, id, status);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,16 +12,9 @@ use std::{
|
|||||||
use log::info;
|
use log::info;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::downloads::download_agent::GameDownloadAgent;
|
|
||||||
|
|
||||||
use super::{application_download_error::ApplicationDownloadError, download_manager_builder::{CurrentProgressObject, DownloadAgent, DownloadableQueueStandin}, downloadable::Downloadable, downloadable_metadata::DownloadableMetadata, queue::Queue};
|
use super::{application_download_error::ApplicationDownloadError, download_manager_builder::{CurrentProgressObject, DownloadAgent}, downloadable_metadata::DownloadableMetadata, queue::Queue};
|
||||||
|
|
||||||
pub enum DownloadType {
|
|
||||||
Game,
|
|
||||||
Tool,
|
|
||||||
DLC,
|
|
||||||
Mod
|
|
||||||
}
|
|
||||||
pub enum DownloadManagerSignal {
|
pub enum DownloadManagerSignal {
|
||||||
/// Resumes (or starts) the DownloadManager
|
/// Resumes (or starts) the DownloadManager
|
||||||
Go,
|
Go,
|
||||||
@ -91,22 +84,6 @@ pub struct DownloadManager {
|
|||||||
progress: CurrentProgressObject,
|
progress: CurrentProgressObject,
|
||||||
command_sender: Sender<DownloadManagerSignal>,
|
command_sender: Sender<DownloadManagerSignal>,
|
||||||
}
|
}
|
||||||
impl<T: Downloadable> From<Arc<T>> for DownloadableQueueStandin {
|
|
||||||
fn from(value: Arc<T>) -> Self {
|
|
||||||
Self {
|
|
||||||
id: value.id(),
|
|
||||||
status: Mutex::from(DownloadStatus::Queued),
|
|
||||||
progress: value.progress().clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Debug for DownloadableQueueStandin {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("DownloadableQueueStandin")
|
|
||||||
.field("id", &self.id)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl DownloadManager {
|
impl DownloadManager {
|
||||||
@ -126,40 +103,34 @@ impl DownloadManager {
|
|||||||
|
|
||||||
pub fn queue_download(
|
pub fn queue_download(
|
||||||
&self,
|
&self,
|
||||||
id: String,
|
download: DownloadAgent
|
||||||
version: String,
|
|
||||||
target_download_dir: usize,
|
|
||||||
) -> Result<(), SendError<DownloadManagerSignal>> {
|
) -> Result<(), SendError<DownloadManagerSignal>> {
|
||||||
info!("Adding download id {}", id);
|
info!("Adding download id {:?}", download.metadata());
|
||||||
self.command_sender.send(DownloadManagerSignal::Queue(
|
self.command_sender.send(DownloadManagerSignal::Queue(download))?;
|
||||||
id,
|
|
||||||
version,
|
|
||||||
target_download_dir,
|
|
||||||
))?;
|
|
||||||
self.command_sender.send(DownloadManagerSignal::Go)
|
self.command_sender.send(DownloadManagerSignal::Go)
|
||||||
}
|
}
|
||||||
pub fn edit(&self) -> MutexGuard<'_, VecDeque<Arc<DownloadableQueueStandin>>> {
|
pub fn edit(&self) -> MutexGuard<'_, VecDeque<Arc<DownloadableMetadata>>> {
|
||||||
self.download_queue.edit()
|
self.download_queue.edit()
|
||||||
}
|
}
|
||||||
pub fn read_queue(&self) -> VecDeque<Arc<DownloadableQueueStandin>> {
|
pub fn read_queue(&self) -> VecDeque<Arc<DownloadableMetadata>> {
|
||||||
self.download_queue.read()
|
self.download_queue.read()
|
||||||
}
|
}
|
||||||
pub fn get_current_download_progress(&self) -> Option<f64> {
|
pub fn get_current_download_progress(&self) -> Option<f64> {
|
||||||
let progress_object = (*self.progress.lock().unwrap()).clone()?;
|
let progress_object = (*self.progress.lock().unwrap()).clone()?;
|
||||||
Some(progress_object.get_progress())
|
Some(progress_object.get_progress())
|
||||||
}
|
}
|
||||||
pub fn rearrange_string(&self, id: String, new_index: usize) {
|
pub fn rearrange_string(&self, meta: &Arc<DownloadableMetadata>, new_index: usize) {
|
||||||
let mut queue = self.edit();
|
let mut queue = self.edit();
|
||||||
let current_index = get_index_from_id(&mut queue, id).unwrap();
|
let current_index = get_index_from_id(&mut queue, meta).unwrap();
|
||||||
let to_move = queue.remove(current_index).unwrap();
|
let to_move = queue.remove(current_index).unwrap();
|
||||||
queue.insert(new_index, to_move);
|
queue.insert(new_index, to_move);
|
||||||
self.command_sender
|
self.command_sender
|
||||||
.send(DownloadManagerSignal::UpdateUIQueue)
|
.send(DownloadManagerSignal::UpdateUIQueue)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
pub fn cancel(&self, id: String) {
|
pub fn cancel(&self, meta: Arc<DownloadableMetadata>) {
|
||||||
self.command_sender
|
self.command_sender
|
||||||
.send(DownloadManagerSignal::Remove(id))
|
.send(DownloadManagerSignal::Remove(meta))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
pub fn rearrange(&self, current_index: usize, new_index: usize) {
|
pub fn rearrange(&self, current_index: usize, new_index: usize) {
|
||||||
@ -170,7 +141,7 @@ impl DownloadManager {
|
|||||||
let needs_pause = current_index == 0 || new_index == 0;
|
let needs_pause = current_index == 0 || new_index == 0;
|
||||||
if needs_pause {
|
if needs_pause {
|
||||||
self.command_sender
|
self.command_sender
|
||||||
.send(DownloadManagerSignal::Cancel)
|
.send(DownloadManagerSignal::Stop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,20 +174,23 @@ impl DownloadManager {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
self.terminator.join()
|
self.terminator.join()
|
||||||
}
|
}
|
||||||
pub fn uninstall_application(&self, id: String) {
|
pub fn uninstall_application(&self, meta: Arc<DownloadableMetadata>) {
|
||||||
self.command_sender
|
self.command_sender
|
||||||
.send(DownloadManagerSignal::Uninstall(id))
|
.send(DownloadManagerSignal::Uninstall(meta))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
pub fn get_sender(&self) -> Sender<DownloadManagerSignal> {
|
||||||
|
self.command_sender.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes in the locked value from .edit() and attempts to
|
/// Takes in the locked value from .edit() and attempts to
|
||||||
/// get the index of whatever id is passed in
|
/// get the index of whatever id is passed in
|
||||||
fn get_index_from_id(
|
fn get_index_from_id(
|
||||||
queue: &mut MutexGuard<'_, VecDeque<Arc<DownloadableQueueStandin>>>,
|
queue: &mut MutexGuard<'_, VecDeque<Arc<DownloadableMetadata>>>,
|
||||||
id: String,
|
meta: &Arc<DownloadableMetadata>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
queue
|
queue
|
||||||
.iter()
|
.iter()
|
||||||
.position(|download_agent| download_agent.id == id)
|
.position(|download_agent| download_agent == meta)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,19 +12,11 @@ use log::{error, info};
|
|||||||
use tauri::{AppHandle, Emitter};
|
use tauri::{AppHandle, Emitter};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{set_application_status, ApplicationStatus, ApplicationTransientStatus, Database}, download_manager::{download_manager::{DownloadStatus, DownloadType}, generate_downloadable::generate_downloadable}, downloads::download_agent::{self, GameDownloadAgent}, library::{
|
download_manager::{download_manager::DownloadStatus, generate_downloadable::generate_downloadable}, library::{QueueUpdateEvent, QueueUpdateEventQueueData, StatsUpdateEvent}}
|
||||||
on_game_complete, push_application_update, QueueUpdateEvent,
|
;
|
||||||
QueueUpdateEventQueueData, StatsUpdateEvent,
|
|
||||||
}, state::GameStatusManager, DB
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{application_download_error::ApplicationDownloadError, download_manager::{DownloadManager, DownloadManagerSignal, DownloadManagerStatus}, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, downloadable::Downloadable, downloadable_metadata::DownloadableMetadata, progress_object::ProgressObject, queue::Queue};
|
use super::{application_download_error::ApplicationDownloadError, download_manager::{DownloadManager, DownloadManagerSignal, DownloadManagerStatus}, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, downloadable::Downloadable, downloadable_metadata::DownloadableMetadata, progress_object::ProgressObject, queue::Queue};
|
||||||
|
|
||||||
pub struct DownloadableQueueStandin {
|
|
||||||
pub id: String,
|
|
||||||
pub status: Mutex<DownloadStatus>,
|
|
||||||
pub progress: Arc<ProgressObject>,
|
|
||||||
}
|
|
||||||
pub type DownloadAgent = Arc<Box<dyn Downloadable + Send + Sync>>;
|
pub type DownloadAgent = Arc<Box<dyn Downloadable + Send + Sync>>;
|
||||||
pub type CurrentProgressObject = Arc<Mutex<Option<Arc<ProgressObject>>>>;
|
pub type CurrentProgressObject = Arc<Mutex<Option<Arc<ProgressObject>>>>;
|
||||||
|
|
||||||
@ -163,12 +155,12 @@ impl DownloadManagerBuilder {
|
|||||||
DownloadManagerSignal::Error(e) => {
|
DownloadManagerSignal::Error(e) => {
|
||||||
self.manage_error_signal(e);
|
self.manage_error_signal(e);
|
||||||
}
|
}
|
||||||
//DownloadManagerSignal::UpdateUIQueue => {
|
DownloadManagerSignal::UpdateUIQueue => {
|
||||||
// self.push_ui_queue_update();
|
self.push_ui_queue_update();
|
||||||
//}
|
}
|
||||||
//DownloadManagerSignal::UpdateUIStats(kbs, time) => {
|
DownloadManagerSignal::UpdateUIStats(kbs, time) => {
|
||||||
// self.push_ui_stats_update(kbs, time);
|
self.push_ui_stats_update(kbs, time);
|
||||||
//}
|
}
|
||||||
DownloadManagerSignal::Finish => {
|
DownloadManagerSignal::Finish => {
|
||||||
self.stop_and_wait_current_download();
|
self.stop_and_wait_current_download();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -239,8 +231,7 @@ impl DownloadManagerBuilder {
|
|||||||
download_agent.on_incomplete(&app_handle);
|
download_agent.on_incomplete(&app_handle);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
download_agent.on_error(&app_handle);
|
download_agent.on_error(&app_handle, e.clone());
|
||||||
error!("error while managing download: {}", e);
|
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -272,7 +263,7 @@ impl DownloadManagerBuilder {
|
|||||||
info!("Got signal Error");
|
info!("Got signal Error");
|
||||||
let current_agent = self.current_download_agent.clone().unwrap();
|
let current_agent = self.current_download_agent.clone().unwrap();
|
||||||
|
|
||||||
current_agent.on_error(&self.app_handle);
|
current_agent.on_error(&self.app_handle, error.clone());
|
||||||
|
|
||||||
self.stop_and_wait_current_download();
|
self.stop_and_wait_current_download();
|
||||||
self.remove_and_cleanup_front_download(¤t_agent.metadata());
|
self.remove_and_cleanup_front_download(¤t_agent.metadata());
|
||||||
@ -310,6 +301,28 @@ impl DownloadManagerBuilder {
|
|||||||
self.manage_cancel_signal(meta);
|
self.manage_cancel_signal(meta);
|
||||||
download_agent.on_uninstall(&self.app_handle);
|
download_agent.on_uninstall(&self.app_handle);
|
||||||
}
|
}
|
||||||
|
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
|
||||||
|
let event_data = StatsUpdateEvent { speed: kbs, time };
|
||||||
|
|
||||||
|
self.app_handle.emit("update_stats", event_data);
|
||||||
|
}
|
||||||
|
fn push_ui_queue_update(&self) {
|
||||||
|
let registry = &self.download_agent_registry;
|
||||||
|
let queue_objs = registry
|
||||||
|
.iter()
|
||||||
|
.map(|(key, val)| QueueUpdateEventQueueData {
|
||||||
|
meta: DownloadableMetadata::clone(&key),
|
||||||
|
status: val.status(),
|
||||||
|
progress: val.progress().get_progress()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let event_data = QueueUpdateEvent {
|
||||||
|
queue: queue_objs,
|
||||||
|
status: self.status.lock().unwrap().clone(),
|
||||||
|
};
|
||||||
|
self.app_handle.emit("update_queue", event_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
// Refactored to consolidate this type. It's a monster.
|
// Refactored to consolidate this type. It's a monster.
|
||||||
@ -317,6 +330,33 @@ pub type DownloadAgent = Arc<Mutex<Box<dyn Downloadable + Send + Sync>>>;
|
|||||||
|
|
||||||
|
|
||||||
impl DownloadManagerBuilder {
|
impl DownloadManagerBuilder {
|
||||||
|
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
|
||||||
|
let event_data = StatsUpdateEvent { speed: kbs, time };
|
||||||
|
|
||||||
|
self.app_handle.emit("update_stats", event_data).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_ui_queue_update(&self) {
|
||||||
|
let queue = self.download_queue.read();
|
||||||
|
let queue_objs: Vec<QueueUpdateEventQueueData> = queue
|
||||||
|
.iter()
|
||||||
|
.map(|interface| QueueUpdateEventQueueData {
|
||||||
|
id: interface.id.clone(),
|
||||||
|
status: interface.status.lock().unwrap().clone(),
|
||||||
|
progress: interface.progress.get_progress(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let status_handle = self.status.lock().unwrap();
|
||||||
|
let status = status_handle.clone();
|
||||||
|
drop(status_handle);
|
||||||
|
|
||||||
|
let event_data = QueueUpdateEvent {
|
||||||
|
queue: queue_objs,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
self.app_handle.emit("update_queue", event_data).unwrap();
|
||||||
|
}
|
||||||
fn uninstall_application(&mut self, id: String) {
|
fn uninstall_application(&mut self, id: String) {
|
||||||
// Removes the download if it's in the queue
|
// Removes the download if it's in the queue
|
||||||
self.manage_remove_download_from_queue(id.clone());
|
self.manage_remove_download_from_queue(id.clone());
|
||||||
@ -606,34 +646,6 @@ impl DownloadManagerBuilder {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
|
|
||||||
let event_data = StatsUpdateEvent { speed: kbs, time };
|
|
||||||
|
|
||||||
self.app_handle.emit("update_stats", event_data).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_ui_queue_update(&self) {
|
|
||||||
let queue = self.download_queue.read();
|
|
||||||
let queue_objs: Vec<QueueUpdateEventQueueData> = queue
|
|
||||||
.iter()
|
|
||||||
.map(|interface| QueueUpdateEventQueueData {
|
|
||||||
id: interface.id.clone(),
|
|
||||||
status: interface.status.lock().unwrap().clone(),
|
|
||||||
progress: interface.progress.get_progress(),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let status_handle = self.status.lock().unwrap();
|
|
||||||
let status = status_handle.clone();
|
|
||||||
drop(status_handle);
|
|
||||||
|
|
||||||
let event_data = QueueUpdateEvent {
|
|
||||||
queue: queue_objs,
|
|
||||||
status,
|
|
||||||
};
|
|
||||||
self.app_handle.emit("update_queue", event_data).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_and_wait_current_download(&self) {
|
fn stop_and_wait_current_download(&self) {
|
||||||
self.set_status(DownloadManagerStatus::Paused);
|
self.set_status(DownloadManagerStatus::Paused);
|
||||||
if let Some(current_flag) = &self.active_control_flag {
|
if let Some(current_flag) = &self.active_control_flag {
|
||||||
@ -816,4 +828,4 @@ impl DownloadManagerBuilder {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,18 +1,19 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::{mpsc::Sender, Arc};
|
||||||
|
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
application_download_error::ApplicationDownloadError, download_thread_control_flag::DownloadThreadControl, downloadable_metadata::DownloadableMetadata, progress_object::ProgressObject
|
application_download_error::ApplicationDownloadError, download_manager::{DownloadManagerSignal, DownloadStatus}, download_thread_control_flag::DownloadThreadControl, downloadable_metadata::DownloadableMetadata, progress_object::ProgressObject
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait Downloadable: Send + Sync {
|
pub trait Downloadable: Send + Sync {
|
||||||
fn download(&self) -> Result<bool, ApplicationDownloadError>;
|
fn download(&self) -> Result<bool, ApplicationDownloadError>;
|
||||||
fn progress(&self) -> Arc<ProgressObject>;
|
fn progress(&self) -> Arc<ProgressObject>;
|
||||||
fn control_flag(&self) -> DownloadThreadControl;
|
fn control_flag(&self) -> DownloadThreadControl;
|
||||||
|
fn status(&self) -> DownloadStatus;
|
||||||
fn metadata(&self) -> Arc<DownloadableMetadata>;
|
fn metadata(&self) -> Arc<DownloadableMetadata>;
|
||||||
fn on_initialised(&self, app_handle: &AppHandle);
|
fn on_initialised(&self, app_handle: &AppHandle);
|
||||||
fn on_error(&self, app_handle: &AppHandle);
|
fn on_error(&self, app_handle: &AppHandle, error: ApplicationDownloadError);
|
||||||
fn on_complete(&self, app_handle: &AppHandle);
|
fn on_complete(&self, app_handle: &AppHandle);
|
||||||
fn on_incomplete(&self, app_handle: &AppHandle);
|
fn on_incomplete(&self, app_handle: &AppHandle);
|
||||||
fn on_cancelled(&self, app_handle: &AppHandle);
|
fn on_cancelled(&self, app_handle: &AppHandle);
|
||||||
|
|||||||
@ -1,4 +1,25 @@
|
|||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
use serde::{Deserialize, Serialize};
|
||||||
pub struct DownloadableMetadata {
|
|
||||||
id: String,
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone)]
|
||||||
|
pub enum DownloadType {
|
||||||
|
Game,
|
||||||
|
Tool,
|
||||||
|
DLC,
|
||||||
|
Mod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct DownloadableMetadata {
|
||||||
|
pub id: String,
|
||||||
|
pub version: String,
|
||||||
|
pub download_type: DownloadType
|
||||||
|
}
|
||||||
|
impl DownloadableMetadata {
|
||||||
|
pub fn new(id: String, version: String, download_type: DownloadType) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
version,
|
||||||
|
download_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,11 +34,11 @@ impl Queue {
|
|||||||
}
|
}
|
||||||
/// Either inserts `interface` at the specified index, or appends to
|
/// Either inserts `interface` at the specified index, or appends to
|
||||||
/// the back of the deque if index is greater than the length of the deque
|
/// the back of the deque if index is greater than the length of the deque
|
||||||
pub fn insert(&self, interface: DownloadableMetadata, index: usize) {
|
pub fn insert(&self, interface: Arc<DownloadableMetadata>, index: usize) {
|
||||||
if self.read().len() > index {
|
if self.read().len() > index {
|
||||||
self.append(interface);
|
self.append(interface);
|
||||||
} else {
|
} else {
|
||||||
self.edit().insert(index, Arc::new(interface));
|
self.edit().insert(index, interface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn append(&self, interface: Arc<DownloadableMetadata>) {
|
pub fn append(&self, interface: Arc<DownloadableMetadata>) {
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
use crate::auth::generate_authorization_header;
|
use crate::auth::generate_authorization_header;
|
||||||
use crate::db::DatabaseImpls;
|
use crate::db::{set_game_status, DatabaseImpls};
|
||||||
use crate::download_manager::application_download_error::ApplicationDownloadError;
|
use crate::download_manager::application_download_error::ApplicationDownloadError;
|
||||||
use crate::download_manager::download_manager::DownloadManagerSignal;
|
use crate::download_manager::download_manager::DownloadManagerSignal;
|
||||||
use crate::download_manager::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag};
|
use crate::download_manager::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag};
|
||||||
use crate::download_manager::downloadable::Downloadable;
|
use crate::download_manager::downloadable::Downloadable;
|
||||||
|
use crate::download_manager::downloadable_metadata::DownloadableMetadata;
|
||||||
use crate::download_manager::progress_object::{ProgressHandle, ProgressObject};
|
use crate::download_manager::progress_object::{ProgressHandle, ProgressObject};
|
||||||
use crate::downloads::manifest::{DropDownloadContext, DropManifest};
|
use crate::downloads::manifest::{DropDownloadContext, DropManifest};
|
||||||
use crate::remote::RemoteAccessError;
|
use crate::remote::RemoteAccessError;
|
||||||
use crate::DB;
|
use crate::DB;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use rayon::ThreadPoolBuilder;
|
use rayon::ThreadPoolBuilder;
|
||||||
|
use tauri::Emitter;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs::{create_dir_all, File};
|
use std::fs::{create_dir_all, File};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@ -85,18 +87,18 @@ impl GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Blocking
|
// Blocking
|
||||||
pub fn download(&self) -> Result<(), ApplicationDownloadError> {
|
pub fn download(&self) -> Result<bool, ApplicationDownloadError> {
|
||||||
self.setup_download()?;
|
self.setup_download()?;
|
||||||
self.set_progress_object_params();
|
self.set_progress_object_params();
|
||||||
let timer = Instant::now();
|
let timer = Instant::now();
|
||||||
self.run().map_err(|_| ApplicationDownloadError::DownloadError)?;
|
let res = self.run().map_err(|_| ApplicationDownloadError::DownloadError);
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"{} took {}ms to download",
|
"{} took {}ms to download",
|
||||||
self.id,
|
self.id,
|
||||||
timer.elapsed().as_millis()
|
timer.elapsed().as_millis()
|
||||||
);
|
);
|
||||||
Ok(())
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_manifest_exists(&self) -> Result<(), ApplicationDownloadError> {
|
pub fn ensure_manifest_exists(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
@ -226,7 +228,7 @@ impl GameDownloadAgent {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self) -> Result<(), ()> {
|
pub fn run(&self) -> Result<bool, ()> {
|
||||||
info!("downloading game: {}", self.id);
|
info!("downloading game: {}", self.id);
|
||||||
const DOWNLOAD_MAX_THREADS: usize = 1;
|
const DOWNLOAD_MAX_THREADS: usize = 1;
|
||||||
|
|
||||||
@ -290,45 +292,69 @@ impl GameDownloadAgent {
|
|||||||
info!("Setting completed contexts");
|
info!("Setting completed contexts");
|
||||||
self.stored_manifest.write();
|
self.stored_manifest.write();
|
||||||
info!("Wrote completed contexts");
|
info!("Wrote completed contexts");
|
||||||
return Ok(());
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've completed
|
// We've completed
|
||||||
self.sender
|
self.sender
|
||||||
.send(DownloadManagerSignal::Completed(self.id.clone()))
|
.send(DownloadManagerSignal::Completed(self.metadata()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Downloadable for GameDownloadAgent {
|
impl Downloadable for GameDownloadAgent {
|
||||||
|
fn download(&self) -> Result<bool, ApplicationDownloadError> {
|
||||||
fn download(&self) -> Result<(), ApplicationDownloadError> {
|
|
||||||
self.download()
|
self.download()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn progress(&self) -> Arc<ProgressObject> {
|
fn progress(&self) -> Arc<ProgressObject> {
|
||||||
self.progress.clone()
|
self.progress.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn control_flag(&self) -> DownloadThreadControl {
|
fn control_flag(&self) -> DownloadThreadControl {
|
||||||
self.control_flag.clone()
|
self.control_flag.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> crate::download_manager::downloadable_metadata::DownloadableMetadata {
|
fn metadata(&self) -> Arc<DownloadableMetadata> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_initialised(&self, _app_handle: &tauri::AppHandle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_error(&self, app_handle: &tauri::AppHandle, error: ApplicationDownloadError) {
|
||||||
|
app_handle
|
||||||
|
.emit("download_error", error.to_string())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
error!("error while managing download: {}", error);
|
||||||
|
|
||||||
|
set_game_status(app_handle, self.metadata(), |db_handle, meta| {
|
||||||
|
db_handle.applications.transient_statuses.remove(meta);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_incomplete(&self, app_handle: &tauri::AppHandle) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_cancelled(&self, app_handle: &tauri::AppHandle) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_uninstall(&self, app_handle: &tauri::AppHandle) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_error(&self) {
|
fn status(&self) -> crate::download_manager::download_manager::DownloadStatus {
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_complete(&self, app_handle: &AppHandle<Wry<EventLoopMessage>>) {
|
|
||||||
on_game_complete(id, version, install_dir, app_handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_initialised(&self, app_handle: &tauri::AppHandle) {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::{download_manager::{downloadable::Downloadable, downloadable_metadata::DownloadableMetadata}, AppState};
|
||||||
|
|
||||||
|
use super::download_agent::GameDownloadAgent;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn download_game(
|
pub fn download_game(
|
||||||
@ -9,11 +11,15 @@ pub fn download_game(
|
|||||||
install_dir: usize,
|
install_dir: usize,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
|
let sender = state.lock().unwrap().download_manager.get_sender();
|
||||||
|
let game_download_agent = Arc::new(
|
||||||
|
Box::new(GameDownloadAgent::new(game_id, game_version, install_dir, sender)) as Box<dyn Downloadable + Send + Sync>
|
||||||
|
);
|
||||||
state
|
state
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download_manager
|
.download_manager
|
||||||
.queue_download(game_id, game_version, install_dir)
|
.queue_download(game_download_agent)
|
||||||
.map_err(|_| "An error occurred while communicating with the download manager.".to_string())
|
.map_err(|_| "An error occurred while communicating with the download manager.".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,8 +47,8 @@ pub fn move_game_in_queue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, game_id: String) {
|
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, game_id: DownloadableMetadata) {
|
||||||
state.lock().unwrap().download_manager.cancel(game_id)
|
state.lock().unwrap().download_manager.cancel(Arc::new(game_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -27,6 +27,7 @@ use db::{
|
|||||||
};
|
};
|
||||||
use download_manager::download_manager::DownloadManager;
|
use download_manager::download_manager::DownloadManager;
|
||||||
use download_manager::download_manager_builder::DownloadManagerBuilder;
|
use download_manager::download_manager_builder::DownloadManagerBuilder;
|
||||||
|
use download_manager::downloadable_metadata::DownloadableMetadata;
|
||||||
use debug::fetch_system_data;
|
use debug::fetch_system_data;
|
||||||
use downloads::download_commands::*;
|
use downloads::download_commands::*;
|
||||||
use http::Response;
|
use http::Response;
|
||||||
@ -81,7 +82,7 @@ pub struct User {
|
|||||||
pub struct AppState<'a> {
|
pub struct AppState<'a> {
|
||||||
status: AppStatus,
|
status: AppStatus,
|
||||||
user: Option<User>,
|
user: Option<User>,
|
||||||
games: HashMap<String, Game>,
|
games: HashMap<DownloadableMetadata, Game>,
|
||||||
|
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
download_manager: Arc<DownloadManager>,
|
download_manager: Arc<DownloadManager>,
|
||||||
@ -181,7 +182,7 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
db_handle
|
db_handle
|
||||||
.applications
|
.applications
|
||||||
.statuses
|
.statuses
|
||||||
.entry(game_id.to_string())
|
.entry(game_id)
|
||||||
.and_modify(|v| *v = ApplicationStatus::Remote {});
|
.and_modify(|v| *v = ApplicationStatus::Remote {});
|
||||||
}
|
}
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -10,6 +10,7 @@ use crate::db::DatabaseImpls;
|
|||||||
use crate::db::ApplicationVersion;
|
use crate::db::ApplicationVersion;
|
||||||
use crate::db::ApplicationStatus;
|
use crate::db::ApplicationStatus;
|
||||||
use crate::download_manager::download_manager::{DownloadManagerStatus, DownloadStatus};
|
use crate::download_manager::download_manager::{DownloadManagerStatus, DownloadStatus};
|
||||||
|
use crate::download_manager::downloadable_metadata::DownloadableMetadata;
|
||||||
use crate::process::process_manager::Platform;
|
use crate::process::process_manager::Platform;
|
||||||
use crate::remote::{DropServerError, RemoteAccessError};
|
use crate::remote::{DropServerError, RemoteAccessError};
|
||||||
use crate::state::{GameStatusManager, GameStatusWithTransient};
|
use crate::state::{GameStatusManager, GameStatusWithTransient};
|
||||||
@ -24,7 +25,7 @@ pub struct FetchGameStruct {
|
|||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
id: String,
|
meta: DownloadableMetadata,
|
||||||
m_name: String,
|
m_name: String,
|
||||||
m_short_description: String,
|
m_short_description: String,
|
||||||
m_description: String,
|
m_description: String,
|
||||||
@ -43,7 +44,7 @@ pub struct GameUpdateEvent {
|
|||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct QueueUpdateEventQueueData {
|
pub struct QueueUpdateEventQueueData {
|
||||||
pub id: String,
|
pub meta: DownloadableMetadata,
|
||||||
pub status: DownloadStatus,
|
pub status: DownloadStatus,
|
||||||
pub progress: f64,
|
pub progress: f64,
|
||||||
}
|
}
|
||||||
@ -98,12 +99,12 @@ fn fetch_library_logic(app: AppHandle) -> Result<Vec<Game>, RemoteAccessError> {
|
|||||||
let mut db_handle = DB.borrow_data_mut().unwrap();
|
let mut db_handle = DB.borrow_data_mut().unwrap();
|
||||||
|
|
||||||
for game in games.iter() {
|
for game in games.iter() {
|
||||||
handle.games.insert(game.id.clone(), game.clone());
|
handle.games.insert(game.meta.clone(), game.clone());
|
||||||
if !db_handle.applications.statuses.contains_key(&game.id) {
|
if !db_handle.applications.statuses.contains_key(&game.meta) {
|
||||||
db_handle
|
db_handle
|
||||||
.applications
|
.applications
|
||||||
.statuses
|
.statuses
|
||||||
.insert(game.id.clone(), ApplicationStatus::Remote {});
|
.insert(game.meta.clone(), ApplicationStatus::Remote {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,15 +119,15 @@ pub fn fetch_library(app: AppHandle) -> Result<Vec<Game>, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_game_logic(
|
fn fetch_game_logic(
|
||||||
id: String,
|
meta: DownloadableMetadata,
|
||||||
app: tauri::AppHandle,
|
app: tauri::AppHandle,
|
||||||
) -> Result<FetchGameStruct, RemoteAccessError> {
|
) -> Result<FetchGameStruct, RemoteAccessError> {
|
||||||
let state = app.state::<Mutex<AppState>>();
|
let state = app.state::<Mutex<AppState>>();
|
||||||
let mut state_handle = state.lock().unwrap();
|
let mut state_handle = state.lock().unwrap();
|
||||||
|
|
||||||
let game = state_handle.games.get(&id);
|
let game = state_handle.games.get(&meta);
|
||||||
if let Some(game) = game {
|
if let Some(game) = game {
|
||||||
let status = GameStatusManager::fetch_state(&id);
|
let status = DownloadStatusManager::fetch_state(&meta);
|
||||||
|
|
||||||
let data = FetchGameStruct {
|
let data = FetchGameStruct {
|
||||||
game: game.clone(),
|
game: game.clone(),
|
||||||
@ -138,7 +139,7 @@ fn fetch_game_logic(
|
|||||||
|
|
||||||
let base_url = DB.fetch_base_url();
|
let base_url = DB.fetch_base_url();
|
||||||
|
|
||||||
let endpoint = base_url.join(&format!("/api/v1/game/{}", id))?;
|
let endpoint = base_url.join(&format!("/api/v1/game/{}", meta.id))?;
|
||||||
let header = generate_authorization_header();
|
let header = generate_authorization_header();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
@ -157,18 +158,18 @@ fn fetch_game_logic(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let game = response.json::<Game>()?;
|
let game = response.json::<Game>()?;
|
||||||
state_handle.games.insert(id.clone(), game.clone());
|
state_handle.games.insert(meta.clone(), game.clone());
|
||||||
|
|
||||||
let mut db_handle = DB.borrow_data_mut().unwrap();
|
let mut db_handle = DB.borrow_data_mut().unwrap();
|
||||||
|
|
||||||
db_handle
|
db_handle
|
||||||
.applications
|
.applications
|
||||||
.statuses
|
.statuses
|
||||||
.entry(id.clone())
|
.entry(meta.clone())
|
||||||
.or_insert(ApplicationStatus::Remote {});
|
.or_insert(ApplicationStatus::Remote {});
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
|
||||||
let status = GameStatusManager::fetch_state(&id);
|
let status = DownloadStatusManager::fetch_state(&meta);
|
||||||
|
|
||||||
let data = FetchGameStruct {
|
let data = FetchGameStruct {
|
||||||
game: game.clone(),
|
game: game.clone(),
|
||||||
@ -179,7 +180,7 @@ fn fetch_game_logic(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn fetch_game(id: String, app: tauri::AppHandle) -> Result<FetchGameStruct, String> {
|
pub fn fetch_game(id: DownloadableMetadata, app: tauri::AppHandle) -> Result<FetchGameStruct, String> {
|
||||||
let result = fetch_game_logic(id, app);
|
let result = fetch_game_logic(id, app);
|
||||||
|
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
@ -190,20 +191,20 @@ pub fn fetch_game(id: String, app: tauri::AppHandle) -> Result<FetchGameStruct,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn fetch_game_status(id: String) -> Result<GameStatusWithTransient, String> {
|
pub fn fetch_game_status(meta: Arc<DownloadableMetadata>) -> Result<ApplicationStatusWithTransient, String> {
|
||||||
let status = GameStatusManager::fetch_state(&id);
|
let status = DownloadStatusManager::fetch_state(&meta);
|
||||||
|
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_game_verion_options_logic<'a>(
|
fn fetch_game_verion_options_logic<'a>(
|
||||||
game_id: String,
|
meta: DownloadableMetadata,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<Vec<GameVersionOption>, RemoteAccessError> {
|
) -> Result<Vec<GameVersionOption>, RemoteAccessError> {
|
||||||
let base_url = DB.fetch_base_url();
|
let base_url = DB.fetch_base_url();
|
||||||
|
|
||||||
let endpoint =
|
let endpoint =
|
||||||
base_url.join(format!("/api/v1/client/metadata/versions?id={}", game_id).as_str())?;
|
base_url.join(format!("/api/v1/client/metadata/versions?id={}", meta.id).as_str())?;
|
||||||
let header = generate_authorization_header();
|
let header = generate_authorization_header();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
@ -234,7 +235,7 @@ fn fetch_game_verion_options_logic<'a>(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn fetch_game_verion_options<'a>(
|
pub fn fetch_game_verion_options<'a>(
|
||||||
game_id: String,
|
game_id: DownloadableMetadata,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<Vec<GameVersionOption>, String> {
|
) -> Result<Vec<GameVersionOption>, String> {
|
||||||
fetch_game_verion_options_logic(game_id, state).map_err(|e| e.to_string())
|
fetch_game_verion_options_logic(game_id, state).map_err(|e| e.to_string())
|
||||||
@ -242,7 +243,7 @@ pub fn fetch_game_verion_options<'a>(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn uninstall_game(
|
pub fn uninstall_game(
|
||||||
game_id: String,
|
game_id: Arc<DownloadableMetadata>,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let state_lock = state.lock().unwrap();
|
let state_lock = state.lock().unwrap();
|
||||||
@ -252,12 +253,12 @@ pub fn uninstall_game(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_application_update(app_handle: &AppHandle, id: String, status: GameStatusWithTransient) {
|
pub fn push_game_update(app_handle: &AppHandle, meta: DownloadableMetadata, status: ApplicationStatusWithTransient) {
|
||||||
app_handle
|
app_handle
|
||||||
.emit(
|
.emit(
|
||||||
&format!("update_game/{}", id),
|
&format!("update_game/{}", meta.id),
|
||||||
GameUpdateEvent {
|
GameUpdateEvent {
|
||||||
game_id: id,
|
game_id: meta.id,
|
||||||
status,
|
status,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -265,8 +266,7 @@ pub fn push_application_update(app_handle: &AppHandle, id: String, status: GameS
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_game_complete(
|
pub fn on_game_complete(
|
||||||
game_id: String,
|
meta: Arc<DownloadableMetadata>,
|
||||||
version_name: String,
|
|
||||||
install_dir: String,
|
install_dir: String,
|
||||||
app_handle: &AppHandle,
|
app_handle: &AppHandle,
|
||||||
) -> Result<(), RemoteAccessError> {
|
) -> Result<(), RemoteAccessError> {
|
||||||
@ -276,8 +276,8 @@ pub fn on_game_complete(
|
|||||||
let endpoint = base_url.join(
|
let endpoint = base_url.join(
|
||||||
format!(
|
format!(
|
||||||
"/api/v1/client/metadata/version?id={}&version={}",
|
"/api/v1/client/metadata/version?id={}&version={}",
|
||||||
game_id,
|
meta.id,
|
||||||
encode(&version_name)
|
encode(&meta.version)
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
)?;
|
)?;
|
||||||
@ -303,20 +303,20 @@ pub fn on_game_complete(
|
|||||||
handle
|
handle
|
||||||
.applications
|
.applications
|
||||||
.versions
|
.versions
|
||||||
.entry(game_id.clone())
|
.entry(*meta.clone())
|
||||||
.or_default()
|
.or_default()
|
||||||
.insert(version_name.clone(), data.clone());
|
.insert(meta.version.clone(), data.clone());
|
||||||
drop(handle);
|
drop(handle);
|
||||||
DB.save().unwrap();
|
DB.save().unwrap();
|
||||||
|
|
||||||
let status = if data.setup_command.is_empty() {
|
let status = if data.setup_command.is_empty() {
|
||||||
ApplicationStatus::Installed {
|
ApplicationStatus::Installed {
|
||||||
version_name,
|
version_name: (*meta.version.clone()).to_string(),
|
||||||
install_dir,
|
install_dir,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ApplicationStatus::SetupRequired {
|
ApplicationStatus::SetupRequired {
|
||||||
version_name,
|
version_name: (*meta.version.clone()).to_string(),
|
||||||
install_dir,
|
install_dir,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -325,14 +325,14 @@ pub fn on_game_complete(
|
|||||||
db_handle
|
db_handle
|
||||||
.applications
|
.applications
|
||||||
.statuses
|
.statuses
|
||||||
.insert(game_id.clone(), status.clone());
|
.insert(*meta.clone(), status.clone());
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
DB.save().unwrap();
|
DB.save().unwrap();
|
||||||
app_handle
|
app_handle
|
||||||
.emit(
|
.emit(
|
||||||
&format!("update_game/{}", game_id),
|
&format!("update_game/{}", meta.id),
|
||||||
GameUpdateEvent {
|
GameUpdateEvent {
|
||||||
game_id,
|
game_id: (*meta.id.clone()).to_string(),
|
||||||
status: (Some(status), None),
|
status: (Some(status), None),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::{download_manager::downloadable_metadata::DownloadableMetadata, AppState};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn launch_game(
|
pub fn launch_game(
|
||||||
game_id: String,
|
game_id: DownloadableMetadata,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let state_lock = state.lock().unwrap();
|
let state_lock = state.lock().unwrap();
|
||||||
@ -20,7 +20,7 @@ pub fn launch_game(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn kill_game(
|
pub fn kill_game(
|
||||||
game_id: String,
|
game_id: DownloadableMetadata,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let state_lock = state.lock().unwrap();
|
let state_lock = state.lock().unwrap();
|
||||||
|
|||||||
@ -15,16 +15,13 @@ use tauri::{AppHandle, Manager};
|
|||||||
use umu_wrapper_lib::command_builder::UmuCommandBuilder;
|
use umu_wrapper_lib::command_builder::UmuCommandBuilder;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{ApplicationStatus, ApplicationTransientStatus, DATA_ROOT_DIR},
|
db::{ApplicationStatus, ApplicationTransientStatus, DATA_ROOT_DIR}, download_manager::{downloadable::Downloadable, downloadable_metadata::DownloadableMetadata}, library::push_game_update, state::DownloadStatusManager, AppState, DB
|
||||||
library::push_application_update,
|
|
||||||
state::GameStatusManager,
|
|
||||||
AppState, DB,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ProcessManager<'a> {
|
pub struct ProcessManager<'a> {
|
||||||
current_platform: Platform,
|
current_platform: Platform,
|
||||||
log_output_dir: PathBuf,
|
log_output_dir: PathBuf,
|
||||||
processes: HashMap<String, Arc<SharedChild>>,
|
processes: HashMap<DownloadableMetadata, Arc<SharedChild>>,
|
||||||
app_handle: AppHandle,
|
app_handle: AppHandle,
|
||||||
game_launchers: HashMap<(Platform, Platform), &'a (dyn ProcessHandler + Sync + Send + 'static)>,
|
game_launchers: HashMap<(Platform, Platform), &'a (dyn ProcessHandler + Sync + Send + 'static)>,
|
||||||
}
|
}
|
||||||
@ -82,8 +79,8 @@ impl ProcessManager<'_> {
|
|||||||
*/
|
*/
|
||||||
(absolute_exe, Vec::new())
|
(absolute_exe, Vec::new())
|
||||||
}
|
}
|
||||||
pub fn kill_game(&mut self, game_id: String) -> Result<(), io::Error> {
|
pub fn kill_game(&mut self, meta: DownloadableMetadata) -> Result<(), io::Error> {
|
||||||
return match self.processes.get(&game_id) {
|
return match self.processes.get(&meta) {
|
||||||
Some(child) => {
|
Some(child) => {
|
||||||
child.kill()?;
|
child.kill()?;
|
||||||
child.wait()?;
|
child.wait()?;
|
||||||
@ -96,20 +93,20 @@ impl ProcessManager<'_> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_process_finish(&mut self, game_id: String, result: Result<ExitStatus, std::io::Error>) {
|
fn on_process_finish(&mut self, meta: DownloadableMetadata, result: Result<ExitStatus, std::io::Error>) {
|
||||||
if !self.processes.contains_key(&game_id) {
|
if !self.processes.contains_key(&meta) {
|
||||||
warn!("process on_finish was called, but game_id is no longer valid. finished with result: {:?}", result);
|
warn!("process on_finish was called, but game_id is no longer valid. finished with result: {:?}", result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("process for {} exited with {:?}", game_id, result);
|
info!("process for {:?} exited with {:?}", meta, result);
|
||||||
|
|
||||||
self.processes.remove(&game_id);
|
self.processes.remove(&meta);
|
||||||
|
|
||||||
let mut db_handle = DB.borrow_data_mut().unwrap();
|
let mut db_handle = DB.borrow_data_mut().unwrap();
|
||||||
db_handle.applications.transient_statuses.remove(&game_id);
|
db_handle.applications.transient_statuses.remove(&meta);
|
||||||
|
|
||||||
let current_state = db_handle.applications.statuses.get(&game_id).cloned();
|
let current_state = db_handle.applications.statuses.get(&meta).cloned();
|
||||||
if let Some(saved_state) = current_state {
|
if let Some(saved_state) = current_state {
|
||||||
if let ApplicationStatus::SetupRequired {
|
if let ApplicationStatus::SetupRequired {
|
||||||
version_name,
|
version_name,
|
||||||
@ -119,7 +116,7 @@ impl ProcessManager<'_> {
|
|||||||
if let Ok(exit_code) = result {
|
if let Ok(exit_code) = result {
|
||||||
if exit_code.success() {
|
if exit_code.success() {
|
||||||
db_handle.applications.statuses.insert(
|
db_handle.applications.statuses.insert(
|
||||||
game_id.clone(),
|
meta.clone(),
|
||||||
ApplicationStatus::Installed {
|
ApplicationStatus::Installed {
|
||||||
version_name: version_name.to_string(),
|
version_name: version_name.to_string(),
|
||||||
install_dir: install_dir.to_string(),
|
install_dir: install_dir.to_string(),
|
||||||
@ -131,9 +128,9 @@ impl ProcessManager<'_> {
|
|||||||
}
|
}
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
|
||||||
let status = GameStatusManager::fetch_state(&game_id);
|
let status = DownloadStatusManager::fetch_state(&meta);
|
||||||
|
|
||||||
push_application_update(&self.app_handle, game_id.clone(), status);
|
push_game_update(&self.app_handle, meta.clone(), status);
|
||||||
|
|
||||||
// TODO better management
|
// TODO better management
|
||||||
}
|
}
|
||||||
@ -145,8 +142,8 @@ impl ProcessManager<'_> {
|
|||||||
.contains_key(&(current.clone(), platform.clone())))
|
.contains_key(&(current.clone(), platform.clone())))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launch_process(&mut self, game_id: String) -> Result<(), String> {
|
pub fn launch_process(&mut self, meta: DownloadableMetadata) -> Result<(), String> {
|
||||||
if self.processes.contains_key(&game_id) {
|
if self.processes.contains_key(&meta) {
|
||||||
return Err("Game or setup is already running.".to_owned());
|
return Err("Game or setup is already running.".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +151,7 @@ impl ProcessManager<'_> {
|
|||||||
let game_status = db_lock
|
let game_status = db_lock
|
||||||
.applications
|
.applications
|
||||||
.statuses
|
.statuses
|
||||||
.get(&game_id)
|
.get(&meta)
|
||||||
.ok_or("Game not installed")?;
|
.ok_or("Game not installed")?;
|
||||||
|
|
||||||
let status_metadata: Option<(&String, &String)> = match game_status {
|
let status_metadata: Option<(&String, &String)> = match game_status {
|
||||||
@ -178,7 +175,7 @@ impl ProcessManager<'_> {
|
|||||||
let game_version = db_lock
|
let game_version = db_lock
|
||||||
.applications
|
.applications
|
||||||
.versions
|
.versions
|
||||||
.get(&game_id)
|
.get(&meta)
|
||||||
.ok_or("Invalid game ID".to_owned())?
|
.ok_or("Invalid game ID".to_owned())?
|
||||||
.get(version_name)
|
.get(version_name)
|
||||||
.ok_or("Invalid version name".to_owned())?;
|
.ok_or("Invalid version name".to_owned())?;
|
||||||
@ -213,7 +210,7 @@ impl ProcessManager<'_> {
|
|||||||
.create(true)
|
.create(true)
|
||||||
.open(
|
.open(
|
||||||
self.log_output_dir
|
self.log_output_dir
|
||||||
.join(format!("{}-{}.log", game_id, current_time.timestamp())),
|
.join(format!("{}-{}-{}.log", meta.id, meta.version, current_time.timestamp())),
|
||||||
)
|
)
|
||||||
.map_err(|v| v.to_string())?;
|
.map_err(|v| v.to_string())?;
|
||||||
|
|
||||||
@ -223,8 +220,9 @@ impl ProcessManager<'_> {
|
|||||||
.read(true)
|
.read(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(self.log_output_dir.join(format!(
|
.open(self.log_output_dir.join(format!(
|
||||||
"{}-{}-error.log",
|
"{}-{}-{}-error.log",
|
||||||
game_id,
|
meta.id,
|
||||||
|
meta.version,
|
||||||
current_time.timestamp()
|
current_time.timestamp()
|
||||||
)))
|
)))
|
||||||
.map_err(|v| v.to_string())?;
|
.map_err(|v| v.to_string())?;
|
||||||
@ -239,8 +237,7 @@ impl ProcessManager<'_> {
|
|||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let launch_process = game_launcher.launch_process(
|
let launch_process = game_launcher.launch_process(
|
||||||
&game_id,
|
&meta,
|
||||||
version_name,
|
|
||||||
command.to_str().unwrap().to_owned(),
|
command.to_str().unwrap().to_owned(),
|
||||||
args,
|
args,
|
||||||
&target_current_dir.to_string(),
|
&target_current_dir.to_string(),
|
||||||
@ -254,17 +251,17 @@ impl ProcessManager<'_> {
|
|||||||
db_lock
|
db_lock
|
||||||
.applications
|
.applications
|
||||||
.transient_statuses
|
.transient_statuses
|
||||||
.insert(game_id.clone(), ApplicationTransientStatus::Running {});
|
.insert(meta.clone(), ApplicationTransientStatus::Running {});
|
||||||
|
|
||||||
push_application_update(
|
push_game_update(
|
||||||
&self.app_handle,
|
&self.app_handle,
|
||||||
game_id.clone(),
|
meta.clone(),
|
||||||
(None, Some(ApplicationTransientStatus::Running {})),
|
(None, Some(ApplicationTransientStatus::Running {})),
|
||||||
);
|
);
|
||||||
|
|
||||||
let wait_thread_handle = launch_process_handle.clone();
|
let wait_thread_handle = launch_process_handle.clone();
|
||||||
let wait_thread_apphandle = self.app_handle.clone();
|
let wait_thread_apphandle = self.app_handle.clone();
|
||||||
let wait_thread_game_id = game_id.clone();
|
let wait_thread_game_id = meta.clone();
|
||||||
|
|
||||||
spawn(move || {
|
spawn(move || {
|
||||||
let result: Result<ExitStatus, std::io::Error> = launch_process_handle.wait();
|
let result: Result<ExitStatus, std::io::Error> = launch_process_handle.wait();
|
||||||
@ -281,7 +278,7 @@ impl ProcessManager<'_> {
|
|||||||
drop(app_state_handle);
|
drop(app_state_handle);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.processes.insert(game_id, wait_thread_handle);
|
self.processes.insert(meta, wait_thread_handle);
|
||||||
|
|
||||||
info!("finished spawning process");
|
info!("finished spawning process");
|
||||||
|
|
||||||
@ -298,8 +295,7 @@ pub enum Platform {
|
|||||||
pub trait ProcessHandler: Send + 'static {
|
pub trait ProcessHandler: Send + 'static {
|
||||||
fn launch_process(
|
fn launch_process(
|
||||||
&self,
|
&self,
|
||||||
game_id: &String,
|
meta: &DownloadableMetadata,
|
||||||
version_name: &String,
|
|
||||||
command: String,
|
command: String,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
current_dir: &String,
|
current_dir: &String,
|
||||||
@ -312,8 +308,7 @@ struct NativeGameLauncher;
|
|||||||
impl ProcessHandler for NativeGameLauncher {
|
impl ProcessHandler for NativeGameLauncher {
|
||||||
fn launch_process(
|
fn launch_process(
|
||||||
&self,
|
&self,
|
||||||
game_id: &String,
|
meta: &DownloadableMetadata,
|
||||||
version_name: &String,
|
|
||||||
command: String,
|
command: String,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
current_dir: &String,
|
current_dir: &String,
|
||||||
@ -335,8 +330,7 @@ struct UMULauncher;
|
|||||||
impl ProcessHandler for UMULauncher {
|
impl ProcessHandler for UMULauncher {
|
||||||
fn launch_process(
|
fn launch_process(
|
||||||
&self,
|
&self,
|
||||||
game_id: &String,
|
meta: &DownloadableMetadata,
|
||||||
version_name: &String,
|
|
||||||
command: String,
|
command: String,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
current_dir: &String,
|
current_dir: &String,
|
||||||
@ -344,7 +338,7 @@ impl ProcessHandler for UMULauncher {
|
|||||||
error_file: File,
|
error_file: File,
|
||||||
) -> Result<Child, String> {
|
) -> Result<Child, String> {
|
||||||
UmuCommandBuilder::new(UMU_LAUNCHER_EXECUTABLE, command)
|
UmuCommandBuilder::new(UMU_LAUNCHER_EXECUTABLE, command)
|
||||||
.game_id(game_id.into())
|
.game_id(String::from("0"))
|
||||||
.launch_args(args)
|
.launch_args(args)
|
||||||
.build()
|
.build()
|
||||||
.spawn()
|
.spawn()
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{ApplicationStatus, ApplicationTransientStatus, Database},
|
db::{ApplicationStatus, ApplicationTransientStatus}, download_manager::downloadable_metadata::DownloadableMetadata, DB
|
||||||
DB,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type GameStatusWithTransient = (Option<ApplicationStatus>, Option<ApplicationTransientStatus>);
|
pub type GameStatusWithTransient = (Option<ApplicationStatus>, Option<ApplicationTransientStatus>);
|
||||||
pub struct GameStatusManager {}
|
pub struct GameStatusManager {}
|
||||||
|
|
||||||
impl GameStatusManager {
|
impl DownloadStatusManager {
|
||||||
pub fn fetch_state(game_id: &String) -> GameStatusWithTransient {
|
pub fn fetch_state(id: &DownloadableMetadata) -> ApplicationStatusWithTransient {
|
||||||
let db_lock = DB.borrow_data().unwrap();
|
let db_lock = DB.borrow_data().unwrap();
|
||||||
GameStatusManager::fetch_state_with_db(game_id, &db_lock)
|
GameStatusManager::fetch_state_with_db(game_id, &db_lock)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::download_manager::{download_thread_control_flag::DownloadThreadControl, downloadable::Downloadable, progress_object::ProgressObject};
|
use crate::download_manager::{application_download_error::ApplicationDownloadError, download_thread_control_flag::DownloadThreadControl, downloadable::Downloadable, downloadable_metadata::DownloadableMetadata, progress_object::ProgressObject};
|
||||||
|
|
||||||
pub struct ToolDownloadAgent {
|
pub struct ToolDownloadAgent {
|
||||||
id: String,
|
id: String,
|
||||||
@ -10,7 +10,7 @@ pub struct ToolDownloadAgent {
|
|||||||
progress: Arc<ProgressObject>,
|
progress: Arc<ProgressObject>,
|
||||||
}
|
}
|
||||||
impl Downloadable for ToolDownloadAgent {
|
impl Downloadable for ToolDownloadAgent {
|
||||||
fn download(&mut self) -> Result<(), crate::download_manager::application_download_error::ApplicationDownloadError> {
|
fn download(&self) -> Result<bool, ApplicationDownloadError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,15 +22,35 @@ impl Downloadable for ToolDownloadAgent {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> crate::download_manager::downloadable_metadata::DownloadableMetadata {
|
fn status(&self) -> crate::download_manager::download_manager::DownloadStatus {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_error(&self) {
|
fn metadata(&self) -> Arc<DownloadableMetadata> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_complete(&self) {
|
fn on_initialised(&self, app_handle: &tauri::AppHandle) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_error(&self, app_handle: &tauri::AppHandle, error: crate::download_manager::application_download_error::ApplicationDownloadError) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_incomplete(&self, app_handle: &tauri::AppHandle) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_cancelled(&self, app_handle: &tauri::AppHandle) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_uninstall(&self, app_handle: &tauri::AppHandle) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user