mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-12 07:42:44 +10:00
Compare commits
13 Commits
AdenMGB-sm
...
e8b9ec020d
| Author | SHA1 | Date | |
|---|---|---|---|
| e8b9ec020d | |||
| cc417d6d69 | |||
| f733fbba65 | |||
| 0d78375f54 | |||
| d6a9994f8b | |||
| dcb7455954 | |||
| 70cecdad19 | |||
| 463c5e6f3b | |||
| 83dc773b10 | |||
| ddde547c08 | |||
| be5500d29f | |||
| ea6fa551a2 | |||
| be4fc2d37a |
24
README.md
24
README.md
@ -1,29 +1,21 @@
|
|||||||
# Drop App
|
# Drop Desktop Client
|
||||||
|
|
||||||
Drop app is the companion app for [Drop](https://github.com/Drop-OSS/drop). It uses a Tauri base with Nuxt 3 + TailwindCSS on top of it, so we can re-use components from the web UI.
|
The Drop Desktop Client is the companion app for [Drop](https://github.com/Drop-OSS/drop). It is the official & intended way to download and play games on your Drop server.
|
||||||
|
|
||||||
## Running
|
## Internals
|
||||||
Before setting up the drop app, be sure that you have a server set up.
|
|
||||||
The instructions for this can be found on the [Drop Docs](https://docs.droposs.org/docs/guides/quickstart)
|
|
||||||
|
|
||||||
## Current features
|
It uses a Tauri base with Nuxt 3 + TailwindCSS on top of it, so we can re-use components from the web UI.
|
||||||
Currently supported are the following features:
|
|
||||||
- Signin (with custom server)
|
|
||||||
- Database registering & recovery
|
|
||||||
- Dynamic library fetching from server
|
|
||||||
- Installing & uninstalling games
|
|
||||||
- Download progress monitoring
|
|
||||||
- Launching / playing games
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
Before setting up a development environemnt, be sure that you have a server set up. The instructions for this can be found on the [Drop Docs](https://docs.droposs.org/docs/guides/quickstart).
|
||||||
|
|
||||||
Install dependencies with `yarn`
|
Then, install dependencies with `yarn`. This'll install the custom builder's dependencies. Then, check everything works properly with `yarn tauri build`.
|
||||||
|
|
||||||
Run the app in development with `yarn tauri dev`. NVIDIA users on Linux, use shell script `./nvidia-prop-dev.sh`
|
Run the app in development with `yarn tauri dev`. NVIDIA users on Linux, use shell script `./nvidia-prop-dev.sh`
|
||||||
|
|
||||||
To manually specify the logging level, add the environment variable `RUST_LOG=[debug, info, warn, error]` to `yarn tauri dev`:
|
To manually specify the logging level, add the environment variable `RUST_LOG=[debug, info, warn, error]` to `yarn tauri dev`:
|
||||||
|
|
||||||
e.g. `RUST_LOG=debug yarn tauri dev`
|
e.g. `RUST_LOG=debug yarn tauri dev`
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Check the original [Drop repo](https://github.com/Drop-OSS/drop/blob/main/CONTRIBUTING.md) for contributing guidelines.
|
Check out the contributing guide on our Developer Docs: [Drop Developer Docs - Contributing](https://developer.droposs.org/contributing).
|
||||||
|
|||||||
@ -14,7 +14,8 @@
|
|||||||
"@tauri-apps/plugin-os": "^2.3.0",
|
"@tauri-apps/plugin-os": "^2.3.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.3.0",
|
"@tauri-apps/plugin-shell": "^2.3.0",
|
||||||
"pino": "^9.7.0",
|
"pino": "^9.7.0",
|
||||||
"pino-pretty": "^13.1.1"
|
"pino-pretty": "^13.1.1",
|
||||||
|
"tauri": "^0.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.7.1"
|
"@tauri-apps/cli": "^2.7.1"
|
||||||
|
|||||||
12
src-tauri/Cargo.lock
generated
12
src-tauri/Cargo.lock
generated
@ -5671,9 +5671,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.2.2"
|
version = "2.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a33318fe222fc2a612961de8b0419e2982767f213f54a4d3a21b0d7b85c41df8"
|
checksum = "37e5858cc7b455a73ab4ea2ebc08b5be33682c00ff1bf4cad5537d4fb62499d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
@ -5689,9 +5689,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs"
|
name = "tauri-plugin-fs"
|
||||||
version = "2.3.0"
|
version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33ead0daec5d305adcefe05af9d970fc437bcc7996052d564e7393eb291252da"
|
checksum = "8c6ef84ee2f2094ce093e55106d90d763ba343fad57566992962e8f76d113f99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dunce",
|
"dunce",
|
||||||
@ -5751,9 +5751,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-shell"
|
name = "tauri-plugin-shell"
|
||||||
version = "2.2.1"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69d5eb3368b959937ad2aeaf6ef9a8f5d11e01ffe03629d3530707bbcb27ff5d"
|
checksum = "2b9ffadec5c3523f11e8273465cacb3d86ea7652a28e6e2a2e9b5c182f791d25"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"log",
|
"log",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::{lock, AppState};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn quit(app: tauri::AppHandle, state: tauri::State<'_, std::sync::Mutex<AppState<'_>>>) {
|
pub fn quit(app: tauri::AppHandle, state: tauri::State<'_, std::sync::Mutex<AppState<'_>>>) {
|
||||||
@ -10,7 +10,7 @@ pub fn quit(app: tauri::AppHandle, state: tauri::State<'_, std::sync::Mutex<AppS
|
|||||||
|
|
||||||
pub fn cleanup_and_exit(app: &AppHandle, state: &tauri::State<'_, std::sync::Mutex<AppState<'_>>>) {
|
pub fn cleanup_and_exit(app: &AppHandle, state: &tauri::State<'_, std::sync::Mutex<AppState<'_>>>) {
|
||||||
debug!("cleaning up and exiting application");
|
debug!("cleaning up and exiting application");
|
||||||
let download_manager = state.lock().unwrap().download_manager.clone();
|
let download_manager = lock!(state).download_manager.clone();
|
||||||
match download_manager.ensure_terminated() {
|
match download_manager.ensure_terminated() {
|
||||||
Ok(res) => match res {
|
Ok(res) => match res {
|
||||||
Ok(()) => debug!("download manager terminated correctly"),
|
Ok(()) => debug!("download manager terminated correctly"),
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
use crate::AppState;
|
use crate::{lock, AppState};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn fetch_state(
|
pub fn fetch_state(
|
||||||
state: tauri::State<'_, std::sync::Mutex<AppState<'_>>>,
|
state: tauri::State<'_, std::sync::Mutex<AppState<'_>>>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let guard = state.lock().unwrap();
|
let guard = lock!(state);
|
||||||
let cloned_state = serde_json::to_string(&guard.clone()).map_err(|e| e.to_string())?;
|
let cloned_state = serde_json::to_string(&guard.clone()).map_err(|e| e.to_string())?;
|
||||||
drop(guard);
|
drop(guard);
|
||||||
Ok(cloned_state)
|
Ok(cloned_state)
|
||||||
|
|||||||
@ -67,11 +67,15 @@ pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn update_settings(new_settings: Value) {
|
pub fn update_settings(new_settings: Value) {
|
||||||
let mut db_lock = borrow_db_mut_checked();
|
let mut db_lock = borrow_db_mut_checked();
|
||||||
let mut current_settings = serde_json::to_value(db_lock.settings.clone()).unwrap();
|
let mut current_settings = serde_json::to_value(db_lock.settings.clone()).expect("Failed to parse existing settings");
|
||||||
for (key, value) in new_settings.as_object().unwrap() {
|
let values = match new_settings.as_object() {
|
||||||
|
Some(values) => values,
|
||||||
|
None => { panic!("Could not parse settings values"); },
|
||||||
|
};
|
||||||
|
for (key, value) in values {
|
||||||
current_settings[key] = value.clone();
|
current_settings[key] = value.clone();
|
||||||
}
|
}
|
||||||
let new_settings: Settings = serde_json::from_value(current_settings).unwrap();
|
let new_settings: Settings = serde_json::from_value(current_settings).unwrap_or_else(|e| panic!("Failed to parse settings with error {}", e));
|
||||||
db_lock.settings = new_settings;
|
db_lock.settings = new_settings;
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@ -21,8 +21,13 @@ static DATA_ROOT_PREFIX: &'static str = "drop";
|
|||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
static DATA_ROOT_PREFIX: &str = "drop-debug";
|
static DATA_ROOT_PREFIX: &str = "drop-debug";
|
||||||
|
|
||||||
pub static DATA_ROOT_DIR: LazyLock<Arc<PathBuf>> =
|
pub static DATA_ROOT_DIR: LazyLock<Arc<PathBuf>> = LazyLock::new(|| {
|
||||||
LazyLock::new(|| Arc::new(dirs::data_dir().unwrap().join(DATA_ROOT_PREFIX)));
|
Arc::new(
|
||||||
|
dirs::data_dir()
|
||||||
|
.expect("Failed to get data dir")
|
||||||
|
.join(DATA_ROOT_PREFIX),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
// Custom JSON serializer to support everything we need
|
// Custom JSON serializer to support everything we need
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
@ -63,13 +68,49 @@ impl DatabaseImpls for DatabaseInterface {
|
|||||||
let pfx_dir = DATA_ROOT_DIR.join("pfx");
|
let pfx_dir = DATA_ROOT_DIR.join("pfx");
|
||||||
|
|
||||||
debug!("creating data directory at {DATA_ROOT_DIR:?}");
|
debug!("creating data directory at {DATA_ROOT_DIR:?}");
|
||||||
create_dir_all(DATA_ROOT_DIR.as_path()).unwrap();
|
create_dir_all(DATA_ROOT_DIR.as_path()).unwrap_or_else(|e| {
|
||||||
create_dir_all(&games_base_dir).unwrap();
|
panic!(
|
||||||
create_dir_all(&logs_root_dir).unwrap();
|
"Failed to create directory {} with error {}",
|
||||||
create_dir_all(&cache_dir).unwrap();
|
DATA_ROOT_DIR.display(),
|
||||||
create_dir_all(&pfx_dir).unwrap();
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
create_dir_all(&games_base_dir).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Failed to create directory {} with error {}",
|
||||||
|
games_base_dir.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
create_dir_all(&logs_root_dir).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Failed to create directory {} with error {}",
|
||||||
|
logs_root_dir.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
create_dir_all(&cache_dir).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Failed to create directory {} with error {}",
|
||||||
|
cache_dir.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
create_dir_all(&pfx_dir).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Failed to create directory {} with error {}",
|
||||||
|
pfx_dir.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let exists = fs::exists(db_path.clone()).unwrap();
|
let exists = fs::exists(db_path.clone()).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Failed to find if {} exists with error {}",
|
||||||
|
db_path.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
match PathDatabase::load_from_path(db_path.clone()) {
|
match PathDatabase::load_from_path(db_path.clone()) {
|
||||||
@ -78,21 +119,19 @@ impl DatabaseImpls for DatabaseInterface {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let default = Database::new(games_base_dir, None, cache_dir);
|
let default = Database::new(games_base_dir, None, cache_dir);
|
||||||
debug!(
|
debug!("Creating database at path {}", db_path.display());
|
||||||
"Creating database at path {}",
|
|
||||||
db_path.as_os_str().to_str().unwrap()
|
|
||||||
);
|
|
||||||
PathDatabase::create_at_path(db_path, default).expect("Database could not be created")
|
PathDatabase::create_at_path(db_path, default).expect("Database could not be created")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn database_is_set_up(&self) -> bool {
|
fn database_is_set_up(&self) -> bool {
|
||||||
!self.borrow_data().unwrap().base_url.is_empty()
|
!borrow_db_checked().base_url.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_base_url(&self) -> Url {
|
fn fetch_base_url(&self) -> Url {
|
||||||
let handle = self.borrow_data().unwrap();
|
let handle = borrow_db_checked();
|
||||||
Url::parse(&handle.base_url).unwrap()
|
Url::parse(&handle.base_url)
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to parse base url {}", handle.base_url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,13 +150,16 @@ fn handle_invalid_database(
|
|||||||
base
|
base
|
||||||
};
|
};
|
||||||
info!("old database stored at: {}", new_path.to_string_lossy());
|
info!("old database stored at: {}", new_path.to_string_lossy());
|
||||||
fs::rename(&db_path, &new_path).unwrap();
|
fs::rename(&db_path, &new_path).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"Could not rename database {} to {} with error {}",
|
||||||
|
db_path.display(),
|
||||||
|
new_path.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let db = Database::new(
|
let db = Database::new(games_base_dir, Some(new_path), cache_dir);
|
||||||
games_base_dir.into_os_string().into_string().unwrap(),
|
|
||||||
Some(new_path),
|
|
||||||
cache_dir,
|
|
||||||
);
|
|
||||||
|
|
||||||
PathDatabase::create_at_path(db_path, db).expect("Database could not be created")
|
PathDatabase::create_at_path(db_path, db).expect("Database could not be created")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,11 +24,11 @@ pub fn scan_install_dirs() {
|
|||||||
if !drop_data_file.exists() {
|
if !drop_data_file.exists() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let game_id = game.file_name().into_string().unwrap();
|
let game_id = game.file_name().display().to_string();
|
||||||
let Ok(drop_data) = DropData::read(&game.path()) else {
|
let Ok(drop_data) = DropData::read(&game.path()) else {
|
||||||
warn!(
|
warn!(
|
||||||
".dropdata exists for {}, but couldn't read it. is it corrupted?",
|
".dropdata exists for {}, but couldn't read it. is it corrupted?",
|
||||||
game.file_name().into_string().unwrap()
|
game.file_name().display()
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use crate::{database::models::data::DownloadableMetadata, AppState};
|
use crate::{AppState, database::models::data::DownloadableMetadata, lock};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn pause_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
pub fn pause_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||||
state.lock().unwrap().download_manager.pause_downloads();
|
lock!(state).download_manager.pause_downloads();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn resume_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
pub fn resume_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||||
state.lock().unwrap().download_manager.resume_downloads();
|
lock!(state).download_manager.resume_downloads();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -18,14 +18,12 @@ pub fn move_download_in_queue(
|
|||||||
old_index: usize,
|
old_index: usize,
|
||||||
new_index: usize,
|
new_index: usize,
|
||||||
) {
|
) {
|
||||||
state
|
lock!(state)
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.download_manager
|
.download_manager
|
||||||
.rearrange(old_index, new_index);
|
.rearrange(old_index, new_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, meta: DownloadableMetadata) {
|
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, meta: DownloadableMetadata) {
|
||||||
state.lock().unwrap().download_manager.cancel(meta);
|
lock!(state).download_manager.cancel(meta);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,10 +11,7 @@ use log::{debug, error, info, warn};
|
|||||||
use tauri::{AppHandle, Emitter};
|
use tauri::{AppHandle, Emitter};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::models::data::DownloadableMetadata,
|
app_emit, database::models::data::DownloadableMetadata, download_manager::download_manager_frontend::DownloadStatus, error::application_download_error::ApplicationDownloadError, games::library::{QueueUpdateEvent, QueueUpdateEventQueueData, StatsUpdateEvent}, lock, send
|
||||||
download_manager::download_manager_frontend::DownloadStatus,
|
|
||||||
error::application_download_error::ApplicationDownloadError,
|
|
||||||
games::library::{QueueUpdateEvent, QueueUpdateEventQueueData, StatsUpdateEvent},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -105,7 +102,7 @@ impl DownloadManagerBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_status(&self, status: DownloadManagerStatus) {
|
fn set_status(&self, status: DownloadManagerStatus) {
|
||||||
*self.status.lock().unwrap() = status;
|
*lock!(self.status) = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_and_cleanup_front_download(&mut self, meta: &DownloadableMetadata) -> DownloadAgent {
|
fn remove_and_cleanup_front_download(&mut self, meta: &DownloadableMetadata) -> DownloadAgent {
|
||||||
@ -119,9 +116,9 @@ impl DownloadManagerBuilder {
|
|||||||
// Make sure the download thread is terminated
|
// Make sure the download thread is terminated
|
||||||
fn cleanup_current_download(&mut self) {
|
fn cleanup_current_download(&mut self) {
|
||||||
self.active_control_flag = None;
|
self.active_control_flag = None;
|
||||||
*self.progress.lock().unwrap() = None;
|
*lock!(self.progress) = None;
|
||||||
|
|
||||||
let mut download_thread_lock = self.current_download_thread.lock().unwrap();
|
let mut download_thread_lock = lock!(self.current_download_thread);
|
||||||
|
|
||||||
if let Some(unfinished_thread) = download_thread_lock.take()
|
if let Some(unfinished_thread) = download_thread_lock.take()
|
||||||
&& !unfinished_thread.is_finished()
|
&& !unfinished_thread.is_finished()
|
||||||
@ -137,7 +134,7 @@ impl DownloadManagerBuilder {
|
|||||||
current_flag.set(DownloadThreadControlFlag::Stop);
|
current_flag.set(DownloadThreadControlFlag::Stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut download_thread_lock = self.current_download_thread.lock().unwrap();
|
let mut download_thread_lock = lock!(self.current_download_thread);
|
||||||
if let Some(current_download_thread) = download_thread_lock.take() {
|
if let Some(current_download_thread) = download_thread_lock.take() {
|
||||||
return current_download_thread.join().is_ok();
|
return current_download_thread.join().is_ok();
|
||||||
};
|
};
|
||||||
@ -199,9 +196,7 @@ impl DownloadManagerBuilder {
|
|||||||
self.download_queue.append(meta.clone());
|
self.download_queue.append(meta.clone());
|
||||||
self.download_agent_registry.insert(meta, download_agent);
|
self.download_agent_registry.insert(meta, download_agent);
|
||||||
|
|
||||||
self.sender
|
send!(self.sender, DownloadManagerSignal::UpdateUIQueue);
|
||||||
.send(DownloadManagerSignal::UpdateUIQueue)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manage_go_signal(&mut self) {
|
fn manage_go_signal(&mut self) {
|
||||||
@ -247,7 +242,7 @@ impl DownloadManagerBuilder {
|
|||||||
|
|
||||||
let sender = self.sender.clone();
|
let sender = self.sender.clone();
|
||||||
|
|
||||||
let mut download_thread_lock = self.current_download_thread.lock().unwrap();
|
let mut download_thread_lock = lock!(self.current_download_thread);
|
||||||
let app_handle = self.app_handle.clone();
|
let app_handle = self.app_handle.clone();
|
||||||
|
|
||||||
*download_thread_lock = Some(spawn(move || {
|
*download_thread_lock = Some(spawn(move || {
|
||||||
@ -258,7 +253,7 @@ impl DownloadManagerBuilder {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
||||||
download_agent.on_error(&app_handle, &e);
|
download_agent.on_error(&app_handle, &e);
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
send!(sender, DownloadManagerSignal::Error(e));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -282,7 +277,7 @@ impl DownloadManagerBuilder {
|
|||||||
&e
|
&e
|
||||||
);
|
);
|
||||||
download_agent.on_error(&app_handle, &e);
|
download_agent.on_error(&app_handle, &e);
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
send!(sender, DownloadManagerSignal::Error(e));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -293,10 +288,8 @@ impl DownloadManagerBuilder {
|
|||||||
|
|
||||||
if validate_result {
|
if validate_result {
|
||||||
download_agent.on_complete(&app_handle);
|
download_agent.on_complete(&app_handle);
|
||||||
sender
|
send!(sender, DownloadManagerSignal::Completed(download_agent.metadata()));
|
||||||
.send(DownloadManagerSignal::Completed(download_agent.metadata()))
|
send!(sender, DownloadManagerSignal::UpdateUIQueue);
|
||||||
.unwrap();
|
|
||||||
sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,7 +316,7 @@ impl DownloadManagerBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.push_ui_queue_update();
|
self.push_ui_queue_update();
|
||||||
self.sender.send(DownloadManagerSignal::Go).unwrap();
|
send!(self.sender, DownloadManagerSignal::Go);
|
||||||
}
|
}
|
||||||
fn manage_error_signal(&mut self, error: ApplicationDownloadError) {
|
fn manage_error_signal(&mut self, error: ApplicationDownloadError) {
|
||||||
debug!("got signal Error");
|
debug!("got signal Error");
|
||||||
@ -361,7 +354,7 @@ impl DownloadManagerBuilder {
|
|||||||
let index = self.download_queue.get_by_meta(meta);
|
let index = self.download_queue.get_by_meta(meta);
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
download_agent.on_cancelled(&self.app_handle);
|
download_agent.on_cancelled(&self.app_handle);
|
||||||
let _ = self.download_queue.edit().remove(index).unwrap();
|
let _ = self.download_queue.edit().remove(index);
|
||||||
let removed = self.download_agent_registry.remove(meta);
|
let removed = self.download_agent_registry.remove(meta);
|
||||||
debug!(
|
debug!(
|
||||||
"removed {:?} from queue {:?}",
|
"removed {:?} from queue {:?}",
|
||||||
@ -376,7 +369,7 @@ impl DownloadManagerBuilder {
|
|||||||
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
|
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
|
||||||
let event_data = StatsUpdateEvent { speed: kbs, time };
|
let event_data = StatsUpdateEvent { speed: kbs, time };
|
||||||
|
|
||||||
self.app_handle.emit("update_stats", event_data).unwrap();
|
app_emit!(self.app_handle, "update_stats", event_data);
|
||||||
}
|
}
|
||||||
fn push_ui_queue_update(&self) {
|
fn push_ui_queue_update(&self) {
|
||||||
let queue = &self.download_queue.read();
|
let queue = &self.download_queue.read();
|
||||||
@ -395,6 +388,6 @@ impl DownloadManagerBuilder {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let event_data = QueueUpdateEvent { queue: queue_objs };
|
let event_data = QueueUpdateEvent { queue: queue_objs };
|
||||||
self.app_handle.emit("update_queue", event_data).unwrap();
|
app_emit!(self.app_handle, "update_queue", event_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@ use std::{
|
|||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{SendError, Sender},
|
|
||||||
Mutex, MutexGuard,
|
Mutex, MutexGuard,
|
||||||
|
mpsc::{SendError, Sender},
|
||||||
},
|
},
|
||||||
thread::JoinHandle,
|
thread::JoinHandle,
|
||||||
};
|
};
|
||||||
@ -14,7 +14,7 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::models::data::DownloadableMetadata,
|
database::models::data::DownloadableMetadata,
|
||||||
error::application_download_error::ApplicationDownloadError,
|
error::application_download_error::ApplicationDownloadError, lock, send,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -119,22 +119,18 @@ impl DownloadManager {
|
|||||||
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 = (*lock!(self.progress)).clone()?;
|
||||||
Some(progress_object.get_progress())
|
Some(progress_object.get_progress())
|
||||||
}
|
}
|
||||||
pub fn rearrange_string(&self, meta: &DownloadableMetadata, new_index: usize) {
|
pub fn rearrange_string(&self, meta: &DownloadableMetadata, new_index: usize) {
|
||||||
let mut queue = self.edit();
|
let mut queue = self.edit();
|
||||||
let current_index = get_index_from_id(&mut queue, meta).unwrap();
|
let current_index = get_index_from_id(&mut queue, meta).expect("Failed to get meta index from id");
|
||||||
let to_move = queue.remove(current_index).unwrap();
|
let to_move = queue.remove(current_index).expect("Failed to remove meta at index from queue");
|
||||||
queue.insert(new_index, to_move);
|
queue.insert(new_index, to_move);
|
||||||
self.command_sender
|
send!(self.command_sender, DownloadManagerSignal::UpdateUIQueue);
|
||||||
.send(DownloadManagerSignal::UpdateUIQueue)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
pub fn cancel(&self, meta: DownloadableMetadata) {
|
pub fn cancel(&self, meta: DownloadableMetadata) {
|
||||||
self.command_sender
|
send!(self.command_sender, DownloadManagerSignal::Cancel(meta));
|
||||||
.send(DownloadManagerSignal::Cancel(meta))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
pub fn rearrange(&self, current_index: usize, new_index: usize) {
|
pub fn rearrange(&self, current_index: usize, new_index: usize) {
|
||||||
if current_index == new_index {
|
if current_index == new_index {
|
||||||
@ -143,39 +139,31 @@ 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
|
send!(self.command_sender, DownloadManagerSignal::Stop);
|
||||||
.send(DownloadManagerSignal::Stop)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("moving download at index {current_index} to index {new_index}");
|
debug!("moving download at index {current_index} to index {new_index}");
|
||||||
|
|
||||||
let mut queue = self.edit();
|
let mut queue = self.edit();
|
||||||
let to_move = queue.remove(current_index).unwrap();
|
let to_move = queue.remove(current_index).expect("Failed to get");
|
||||||
queue.insert(new_index, to_move);
|
queue.insert(new_index, to_move);
|
||||||
drop(queue);
|
drop(queue);
|
||||||
|
|
||||||
if needs_pause {
|
if needs_pause {
|
||||||
self.command_sender.send(DownloadManagerSignal::Go).unwrap();
|
send!(self.command_sender, DownloadManagerSignal::Go);
|
||||||
}
|
}
|
||||||
self.command_sender
|
send!(self.command_sender, DownloadManagerSignal::UpdateUIQueue);
|
||||||
.send(DownloadManagerSignal::UpdateUIQueue)
|
send!(self.command_sender, DownloadManagerSignal::Go);
|
||||||
.unwrap();
|
|
||||||
self.command_sender.send(DownloadManagerSignal::Go).unwrap();
|
|
||||||
}
|
}
|
||||||
pub fn pause_downloads(&self) {
|
pub fn pause_downloads(&self) {
|
||||||
self.command_sender
|
send!(self.command_sender, DownloadManagerSignal::Stop);
|
||||||
.send(DownloadManagerSignal::Stop)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
pub fn resume_downloads(&self) {
|
pub fn resume_downloads(&self) {
|
||||||
self.command_sender.send(DownloadManagerSignal::Go).unwrap();
|
send!(self.command_sender, DownloadManagerSignal::Go);
|
||||||
}
|
}
|
||||||
pub fn ensure_terminated(&self) -> Result<Result<(), ()>, Box<dyn Any + Send>> {
|
pub fn ensure_terminated(&self) -> Result<Result<(), ()>, Box<dyn Any + Send>> {
|
||||||
self.command_sender
|
send!(self.command_sender, DownloadManagerSignal::Finish);
|
||||||
.send(DownloadManagerSignal::Finish)
|
let terminator = lock!(self.terminator).take();
|
||||||
.unwrap();
|
|
||||||
let terminator = self.terminator.lock().unwrap().take();
|
|
||||||
terminator.unwrap().join()
|
terminator.unwrap().join()
|
||||||
}
|
}
|
||||||
pub fn get_sender(&self) -> Sender<DownloadManagerSignal> {
|
pub fn get_sender(&self) -> Sender<DownloadManagerSignal> {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use std::{
|
|||||||
use atomic_instant_full::AtomicInstant;
|
use atomic_instant_full::AtomicInstant;
|
||||||
use throttle_my_fn::throttle;
|
use throttle_my_fn::throttle;
|
||||||
|
|
||||||
use crate::download_manager::download_manager_frontend::DownloadManagerSignal;
|
use crate::{download_manager::download_manager_frontend::DownloadManagerSignal, lock, send};
|
||||||
|
|
||||||
use super::rolling_progress_updates::RollingProgressWindow;
|
use super::rolling_progress_updates::RollingProgressWindow;
|
||||||
|
|
||||||
@ -74,12 +74,10 @@ impl ProgressObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_time_now(&self) {
|
pub fn set_time_now(&self) {
|
||||||
*self.start.lock().unwrap() = Instant::now();
|
*lock!(self.start) = Instant::now();
|
||||||
}
|
}
|
||||||
pub fn sum(&self) -> usize {
|
pub fn sum(&self) -> usize {
|
||||||
self.progress_instances
|
lock!(self.progress_instances)
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|instance| instance.load(Ordering::Acquire))
|
.map(|instance| instance.load(Ordering::Acquire))
|
||||||
.sum()
|
.sum()
|
||||||
@ -88,27 +86,25 @@ impl ProgressObject {
|
|||||||
self.set_time_now();
|
self.set_time_now();
|
||||||
self.bytes_last_update.store(0, Ordering::Release);
|
self.bytes_last_update.store(0, Ordering::Release);
|
||||||
self.rolling.reset();
|
self.rolling.reset();
|
||||||
self.progress_instances
|
lock!(self.progress_instances)
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|x| x.store(0, Ordering::SeqCst));
|
.for_each(|x| x.store(0, Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
pub fn get_max(&self) -> usize {
|
pub fn get_max(&self) -> usize {
|
||||||
*self.max.lock().unwrap()
|
*lock!(self.max)
|
||||||
}
|
}
|
||||||
pub fn set_max(&self, new_max: usize) {
|
pub fn set_max(&self, new_max: usize) {
|
||||||
*self.max.lock().unwrap() = new_max;
|
*lock!(self.max) = new_max;
|
||||||
}
|
}
|
||||||
pub fn set_size(&self, length: usize) {
|
pub fn set_size(&self, length: usize) {
|
||||||
*self.progress_instances.lock().unwrap() =
|
*lock!(self.progress_instances) =
|
||||||
(0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect();
|
(0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect();
|
||||||
}
|
}
|
||||||
pub fn get_progress(&self) -> f64 {
|
pub fn get_progress(&self) -> f64 {
|
||||||
self.sum() as f64 / self.get_max() as f64
|
self.sum() as f64 / self.get_max() as f64
|
||||||
}
|
}
|
||||||
pub fn get(&self, index: usize) -> Arc<AtomicUsize> {
|
pub fn get(&self, index: usize) -> Arc<AtomicUsize> {
|
||||||
self.progress_instances.lock().unwrap()[index].clone()
|
lock!(self.progress_instances)[index].clone()
|
||||||
}
|
}
|
||||||
fn update_window(&self, kilobytes_per_second: usize) {
|
fn update_window(&self, kilobytes_per_second: usize) {
|
||||||
self.rolling.update(kilobytes_per_second);
|
self.rolling.update(kilobytes_per_second);
|
||||||
@ -148,18 +144,12 @@ pub fn push_update(progress: &ProgressObject, bytes_remaining: usize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_ui(progress_object: &ProgressObject, kilobytes_per_second: usize, time_remaining: usize) {
|
fn update_ui(progress_object: &ProgressObject, kilobytes_per_second: usize, time_remaining: usize) {
|
||||||
progress_object
|
send!(
|
||||||
.sender
|
progress_object.sender,
|
||||||
.send(DownloadManagerSignal::UpdateUIStats(
|
DownloadManagerSignal::UpdateUIStats(kilobytes_per_second, time_remaining)
|
||||||
kilobytes_per_second,
|
);
|
||||||
time_remaining,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_queue(progress: &ProgressObject) {
|
fn update_queue(progress: &ProgressObject) {
|
||||||
progress
|
send!(progress.sender, DownloadManagerSignal::UpdateUIQueue)
|
||||||
.sender
|
|
||||||
.send(DownloadManagerSignal::UpdateUIQueue)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use std::{
|
|||||||
sync::{Arc, Mutex, MutexGuard},
|
sync::{Arc, Mutex, MutexGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::database::models::data::DownloadableMetadata;
|
use crate::{database::models::data::DownloadableMetadata, lock};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Queue {
|
pub struct Queue {
|
||||||
@ -24,10 +24,10 @@ impl Queue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn read(&self) -> VecDeque<DownloadableMetadata> {
|
pub fn read(&self) -> VecDeque<DownloadableMetadata> {
|
||||||
self.inner.lock().unwrap().clone()
|
lock!(self.inner).clone()
|
||||||
}
|
}
|
||||||
pub fn edit(&self) -> MutexGuard<'_, VecDeque<DownloadableMetadata>> {
|
pub fn edit(&self) -> MutexGuard<'_, VecDeque<DownloadableMetadata>> {
|
||||||
self.inner.lock().unwrap()
|
lock!(self.inner)
|
||||||
}
|
}
|
||||||
pub fn pop_front(&self) -> Option<DownloadableMetadata> {
|
pub fn pop_front(&self) -> Option<DownloadableMetadata> {
|
||||||
self.edit().pop_front()
|
self.edit().pop_front()
|
||||||
|
|||||||
@ -18,7 +18,7 @@ pub enum ApplicationDownloadError {
|
|||||||
Checksum,
|
Checksum,
|
||||||
Lock,
|
Lock,
|
||||||
IoError(Arc<io::Error>),
|
IoError(Arc<io::Error>),
|
||||||
DownloadError,
|
DownloadError(RemoteAccessError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ApplicationDownloadError {
|
impl Display for ApplicationDownloadError {
|
||||||
@ -40,10 +40,16 @@ impl Display for ApplicationDownloadError {
|
|||||||
write!(f, "checksum failed to validate for download")
|
write!(f, "checksum failed to validate for download")
|
||||||
}
|
}
|
||||||
ApplicationDownloadError::IoError(error) => write!(f, "io error: {error}"),
|
ApplicationDownloadError::IoError(error) => write!(f, "io error: {error}"),
|
||||||
ApplicationDownloadError::DownloadError => write!(
|
ApplicationDownloadError::DownloadError(error) => write!(
|
||||||
f,
|
f,
|
||||||
"Download failed. See Download Manager status for specific error"
|
"Download failed with error {error}"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for ApplicationDownloadError {
|
||||||
|
fn from(value: io::Error) -> Self {
|
||||||
|
ApplicationDownloadError::IoError(Arc::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src-tauri/src/error/cache_error.rs
Normal file
26
src-tauri/src/error/cache_error.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use http::{header::ToStrError, HeaderName};
|
||||||
|
use serde_with::SerializeDisplay;
|
||||||
|
|
||||||
|
use crate::error::remote_access_error::RemoteAccessError;
|
||||||
|
|
||||||
|
#[derive(Debug, SerializeDisplay)]
|
||||||
|
pub enum CacheError {
|
||||||
|
HeaderNotFound(HeaderName),
|
||||||
|
ParseError(ToStrError),
|
||||||
|
Remote(RemoteAccessError),
|
||||||
|
ConstructionError(http::Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CacheError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
CacheError::HeaderNotFound(header_name) => format!("Could not find header {header_name} in cache"),
|
||||||
|
CacheError::ParseError(to_str_error) => format!("Could not parse cache with error {to_str_error}"),
|
||||||
|
CacheError::Remote(remote_access_error) => format!("Cache got remote access error: {remote_access_error}"),
|
||||||
|
CacheError::ConstructionError(error) => format!("Could not construct cache body with error {error}"),
|
||||||
|
};
|
||||||
|
write!(f, "{s}")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,18 +1,21 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::{Display};
|
||||||
|
|
||||||
use serde_with::SerializeDisplay;
|
use serde_with::SerializeDisplay;
|
||||||
|
|
||||||
#[derive(SerializeDisplay)]
|
#[derive(SerializeDisplay)]
|
||||||
pub enum LibraryError {
|
pub enum LibraryError {
|
||||||
MetaNotFound(String),
|
MetaNotFound(String),
|
||||||
|
VersionNotFound(String),
|
||||||
}
|
}
|
||||||
impl Display for LibraryError {
|
impl Display for LibraryError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
write!(f, "{}", match self {
|
||||||
LibraryError::MetaNotFound(id) => write!(
|
LibraryError::MetaNotFound(id) => {
|
||||||
f,
|
format!("Could not locate any installed version of game ID {id} in the database")
|
||||||
"Could not locate any installed version of game ID {id} in the database"
|
}
|
||||||
),
|
LibraryError::VersionNotFound(game_id) => {
|
||||||
}
|
format!("Could not locate any installed version for game id {game_id} in the database")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,3 +4,4 @@ pub mod drop_server_error;
|
|||||||
pub mod library_error;
|
pub mod library_error;
|
||||||
pub mod process_error;
|
pub mod process_error;
|
||||||
pub mod remote_access_error;
|
pub mod remote_access_error;
|
||||||
|
pub mod cache_error;
|
||||||
@ -11,7 +11,8 @@ pub enum ProcessError {
|
|||||||
IOError(Error),
|
IOError(Error),
|
||||||
FormatError(String), // String errors supremacy
|
FormatError(String), // String errors supremacy
|
||||||
InvalidPlatform,
|
InvalidPlatform,
|
||||||
OpenerError(tauri_plugin_opener::Error)
|
OpenerError(tauri_plugin_opener::Error),
|
||||||
|
InvalidArguments(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ProcessError {
|
impl Display for ProcessError {
|
||||||
@ -23,8 +24,9 @@ impl Display for ProcessError {
|
|||||||
ProcessError::InvalidVersion => "Invalid game version",
|
ProcessError::InvalidVersion => "Invalid game version",
|
||||||
ProcessError::IOError(error) => &error.to_string(),
|
ProcessError::IOError(error) => &error.to_string(),
|
||||||
ProcessError::InvalidPlatform => "This game cannot be played on the current platform",
|
ProcessError::InvalidPlatform => "This game cannot be played on the current platform",
|
||||||
ProcessError::FormatError(e) => &format!("Failed to format template: {e}"),
|
ProcessError::FormatError(e) => &format!("Could not format template: {e}"),
|
||||||
ProcessError::OpenerError(error) => &format!("Failed to open directory: {error}"),
|
ProcessError::OpenerError(error) => &format!("Could not open directory: {error}"),
|
||||||
|
ProcessError::InvalidArguments(arguments) => &format!("Invalid arguments in command {arguments}"),
|
||||||
};
|
};
|
||||||
write!(f, "{s}")
|
write!(f, "{s}")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,8 +44,7 @@ impl Display for RemoteAccessError {
|
|||||||
error
|
error
|
||||||
.source()
|
.source()
|
||||||
.map(std::string::ToString::to_string)
|
.map(std::string::ToString::to_string)
|
||||||
.or_else(|| Some("Unknown error".to_string()))
|
.unwrap_or("Unknown error".to_string())
|
||||||
.unwrap()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RemoteAccessError::FetchErrorWS(error) => write!(
|
RemoteAccessError::FetchErrorWS(error) => write!(
|
||||||
@ -54,9 +53,8 @@ impl Display for RemoteAccessError {
|
|||||||
error,
|
error,
|
||||||
error
|
error
|
||||||
.source()
|
.source()
|
||||||
.map(|e| e.to_string())
|
.map(std::string::ToString::to_string)
|
||||||
.or_else(|| Some("Unknown error".to_string()))
|
.unwrap_or("Unknown error".to_string())
|
||||||
.unwrap()
|
|
||||||
),
|
),
|
||||||
RemoteAccessError::ParsingError(parse_error) => {
|
RemoteAccessError::ParsingError(parse_error) => {
|
||||||
write!(f, "{parse_error}")
|
write!(f, "{parse_error}")
|
||||||
|
|||||||
@ -5,13 +5,10 @@ use std::{
|
|||||||
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
|
||||||
database::{
|
database::{
|
||||||
db::borrow_db_checked,
|
db::borrow_db_checked,
|
||||||
models::data::GameDownloadStatus,
|
models::data::GameDownloadStatus,
|
||||||
},
|
}, download_manager::downloadable::Downloadable, error::application_download_error::ApplicationDownloadError, lock, AppState
|
||||||
download_manager::downloadable::Downloadable,
|
|
||||||
error::application_download_error::ApplicationDownloadError,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::download_agent::GameDownloadAgent;
|
use super::download_agent::GameDownloadAgent;
|
||||||
@ -23,16 +20,14 @@ pub async fn download_game(
|
|||||||
install_dir: usize,
|
install_dir: usize,
|
||||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
||||||
) -> Result<(), ApplicationDownloadError> {
|
) -> Result<(), ApplicationDownloadError> {
|
||||||
let sender = { state.lock().unwrap().download_manager.get_sender().clone() };
|
let sender = { lock!(state).download_manager.get_sender().clone() };
|
||||||
|
|
||||||
let game_download_agent =
|
let game_download_agent =
|
||||||
GameDownloadAgent::new_from_index(game_id.clone(), game_version.clone(), install_dir, sender).await?;
|
GameDownloadAgent::new_from_index(game_id.clone(), game_version.clone(), install_dir, sender).await?;
|
||||||
|
|
||||||
let game_download_agent =
|
let game_download_agent =
|
||||||
Arc::new(Box::new(game_download_agent) as Box<dyn Downloadable + Send + Sync>);
|
Arc::new(Box::new(game_download_agent) as Box<dyn Downloadable + Send + Sync>);
|
||||||
state
|
lock!(state)
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.download_manager
|
.download_manager
|
||||||
.queue_download(game_download_agent.clone())
|
.queue_download(game_download_agent.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -62,22 +57,20 @@ pub async fn resume_download(
|
|||||||
} => (version_name, install_dir),
|
} => (version_name, install_dir),
|
||||||
};
|
};
|
||||||
|
|
||||||
let sender = state.lock().unwrap().download_manager.get_sender();
|
let sender = lock!(state).download_manager.get_sender();
|
||||||
let parent_dir: PathBuf = install_dir.into();
|
let parent_dir: PathBuf = install_dir.into();
|
||||||
|
|
||||||
let game_download_agent = Arc::new(Box::new(
|
let game_download_agent = Arc::new(Box::new(
|
||||||
GameDownloadAgent::new(
|
GameDownloadAgent::new(
|
||||||
game_id,
|
game_id,
|
||||||
version_name.clone(),
|
version_name.clone(),
|
||||||
parent_dir.parent().unwrap().to_path_buf(),
|
parent_dir.parent().unwrap_or_else(|| panic!("Failed to get parent directry of {}", parent_dir.display())).to_path_buf(),
|
||||||
sender,
|
sender,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
) as Box<dyn Downloadable + Send + Sync>);
|
) as Box<dyn Downloadable + Send + Sync>);
|
||||||
|
|
||||||
state
|
lock!(state)
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.download_manager
|
.download_manager
|
||||||
.queue_download(game_download_agent)
|
.queue_download(game_download_agent)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@ -20,10 +20,12 @@ use crate::games::state::GameStatusManager;
|
|||||||
use crate::process::utils::get_disk_available;
|
use crate::process::utils::get_disk_available;
|
||||||
use crate::remote::requests::generate_url;
|
use crate::remote::requests::generate_url;
|
||||||
use crate::remote::utils::{DROP_CLIENT_ASYNC, DROP_CLIENT_SYNC};
|
use crate::remote::utils::{DROP_CLIENT_ASYNC, DROP_CLIENT_SYNC};
|
||||||
|
use crate::{app_emit, lock, send};
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use rayon::ThreadPoolBuilder;
|
use rayon::ThreadPoolBuilder;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fs::{OpenOptions, create_dir_all};
|
use std::fs::{OpenOptions, create_dir_all};
|
||||||
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@ -101,10 +103,8 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
result.ensure_manifest_exists().await?;
|
result.ensure_manifest_exists().await?;
|
||||||
|
|
||||||
let required_space = result
|
let required_space = lock!(result
|
||||||
.manifest
|
.manifest)
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.values()
|
.values()
|
||||||
@ -172,11 +172,11 @@ impl GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_manifest_exists(&self) -> bool {
|
pub fn check_manifest_exists(&self) -> bool {
|
||||||
self.manifest.lock().unwrap().is_some()
|
lock!(self.manifest).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ensure_manifest_exists(&self) -> Result<(), ApplicationDownloadError> {
|
pub async fn ensure_manifest_exists(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
if self.manifest.lock().unwrap().is_some() {
|
if lock!(self.manifest).is_some() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +207,10 @@ impl GameDownloadAgent {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let manifest_download: DropManifest = response.json().await.unwrap();
|
let manifest_download: DropManifest = response
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
|
||||||
|
|
||||||
if let Ok(mut manifest) = self.manifest.lock() {
|
if let Ok(mut manifest) = self.manifest.lock() {
|
||||||
*manifest = Some(manifest_download);
|
*manifest = Some(manifest_download);
|
||||||
@ -219,7 +222,7 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
// Sets it up for both download and validate
|
// Sets it up for both download and validate
|
||||||
fn setup_progress(&self) {
|
fn setup_progress(&self) {
|
||||||
let buckets = self.buckets.lock().unwrap();
|
let buckets = lock!(self.buckets);
|
||||||
|
|
||||||
let chunk_count = buckets.iter().map(|e| e.drops.len()).sum();
|
let chunk_count = buckets.iter().map(|e| e.drops.len()).sum();
|
||||||
|
|
||||||
@ -234,21 +237,23 @@ impl GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_buckets(&self) -> Result<(), ApplicationDownloadError> {
|
pub fn ensure_buckets(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
if self.buckets.lock().unwrap().is_empty() {
|
if lock!(self.buckets).is_empty() {
|
||||||
self.generate_buckets()?;
|
self.generate_buckets()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.context_map.lock().unwrap() = self.dropdata.get_contexts();
|
*lock!(self.context_map) = self.dropdata.get_contexts();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_buckets(&self) -> Result<(), ApplicationDownloadError> {
|
pub fn generate_buckets(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
let manifest = self.manifest.lock().unwrap().clone().unwrap();
|
let manifest = lock!(self.manifest)
|
||||||
|
.clone()
|
||||||
|
.ok_or(ApplicationDownloadError::NotInitialized)?;
|
||||||
let game_id = self.id.clone();
|
let game_id = self.id.clone();
|
||||||
|
|
||||||
let base_path = Path::new(&self.dropdata.base_path);
|
let base_path = Path::new(&self.dropdata.base_path);
|
||||||
create_dir_all(base_path).unwrap();
|
create_dir_all(base_path)?;
|
||||||
|
|
||||||
let mut buckets = Vec::new();
|
let mut buckets = Vec::new();
|
||||||
|
|
||||||
@ -258,8 +263,13 @@ impl GameDownloadAgent {
|
|||||||
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));
|
||||||
|
|
||||||
let container = path.parent().unwrap();
|
let container = path
|
||||||
create_dir_all(container).unwrap();
|
.parent()
|
||||||
|
.ok_or(ApplicationDownloadError::IoError(Arc::new(io::Error::new(
|
||||||
|
io::ErrorKind::NotFound,
|
||||||
|
"no parent directory",
|
||||||
|
))))?;
|
||||||
|
create_dir_all(container)?;
|
||||||
|
|
||||||
let already_exists = path.exists();
|
let already_exists = path.exists();
|
||||||
let file = OpenOptions::new()
|
let file = OpenOptions::new()
|
||||||
@ -267,8 +277,7 @@ impl GameDownloadAgent {
|
|||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.truncate(false)
|
.truncate(false)
|
||||||
.open(path.clone())
|
.open(&path)?;
|
||||||
.unwrap();
|
|
||||||
let mut file_running_offset = 0;
|
let mut file_running_offset = 0;
|
||||||
|
|
||||||
for (index, length) in chunk.lengths.iter().enumerate() {
|
for (index, length) in chunk.lengths.iter().enumerate() {
|
||||||
@ -352,7 +361,7 @@ impl GameDownloadAgent {
|
|||||||
.collect::<Vec<(String, bool)>>(),
|
.collect::<Vec<(String, bool)>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
*self.buckets.lock().unwrap() = buckets;
|
*lock!(self.buckets) = buckets;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -368,9 +377,11 @@ impl GameDownloadAgent {
|
|||||||
let pool = ThreadPoolBuilder::new()
|
let pool = ThreadPoolBuilder::new()
|
||||||
.num_threads(max_download_threads)
|
.num_threads(max_download_threads)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap_or_else(|_| {
|
||||||
|
panic!("failed to build thread pool with {max_download_threads} threads")
|
||||||
|
});
|
||||||
|
|
||||||
let buckets = self.buckets.lock().unwrap();
|
let buckets = lock!(self.buckets);
|
||||||
|
|
||||||
let mut download_contexts = HashMap::<String, DownloadContext>::new();
|
let mut download_contexts = HashMap::<String, DownloadContext>::new();
|
||||||
|
|
||||||
@ -389,7 +400,7 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
for version in versions {
|
for version in versions {
|
||||||
let download_context = DROP_CLIENT_SYNC
|
let download_context = DROP_CLIENT_SYNC
|
||||||
.post(generate_url(&["/api/v2/client/context"], &[]).unwrap())
|
.post(generate_url(&["/api/v2/client/context"], &[])?)
|
||||||
.json(&ManifestBody {
|
.json(&ManifestBody {
|
||||||
game: self.id.clone(),
|
game: self.id.clone(),
|
||||||
version: version.clone(),
|
version: version.clone(),
|
||||||
@ -412,7 +423,7 @@ impl GameDownloadAgent {
|
|||||||
let download_contexts = &download_contexts;
|
let download_contexts = &download_contexts;
|
||||||
|
|
||||||
pool.scope(|scope| {
|
pool.scope(|scope| {
|
||||||
let context_map = self.context_map.lock().unwrap();
|
let context_map = lock!(self.context_map);
|
||||||
for (index, bucket) in buckets.iter().enumerate() {
|
for (index, bucket) in buckets.iter().enumerate() {
|
||||||
let mut bucket = (*bucket).clone();
|
let mut bucket = (*bucket).clone();
|
||||||
let completed_contexts = completed_indexes_loop_arc.clone();
|
let completed_contexts = completed_indexes_loop_arc.clone();
|
||||||
@ -444,8 +455,7 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
let download_context = download_contexts
|
let download_context = download_contexts
|
||||||
.get(&bucket.version)
|
.get(&bucket.version)
|
||||||
.ok_or(RemoteAccessError::CorruptedState)
|
.unwrap_or_else(|| panic!("Could not get bucket version {}. Corrupted state.", bucket.version));
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
scope.spawn(move |_| {
|
scope.spawn(move |_| {
|
||||||
// 3 attempts
|
// 3 attempts
|
||||||
@ -477,7 +487,7 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
if i == RETRY_COUNT - 1 || !retry {
|
if i == RETRY_COUNT - 1 || !retry {
|
||||||
warn!("retry logic failed, not re-attempting.");
|
warn!("retry logic failed, not re-attempting.");
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
send!(sender, DownloadManagerSignal::Error(e));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -490,7 +500,7 @@ impl GameDownloadAgent {
|
|||||||
let newly_completed = completed_contexts.clone();
|
let newly_completed = completed_contexts.clone();
|
||||||
|
|
||||||
let completed_lock_len = {
|
let completed_lock_len = {
|
||||||
let mut context_map_lock = self.context_map.lock().unwrap();
|
let mut context_map_lock = lock!(self.context_map);
|
||||||
for (_, item) in newly_completed.iter() {
|
for (_, item) in newly_completed.iter() {
|
||||||
context_map_lock.insert(item.clone(), true);
|
context_map_lock.insert(item.clone(), true);
|
||||||
}
|
}
|
||||||
@ -498,7 +508,7 @@ impl GameDownloadAgent {
|
|||||||
context_map_lock.values().filter(|x| **x).count()
|
context_map_lock.values().filter(|x| **x).count()
|
||||||
};
|
};
|
||||||
|
|
||||||
let context_map_lock = self.context_map.lock().unwrap();
|
let context_map_lock = lock!(self.context_map);
|
||||||
let contexts = buckets
|
let contexts = buckets
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|x| x.drops.iter().map(|e| e.checksum.clone()))
|
.flat_map(|x| x.drops.iter().map(|e| e.checksum.clone()))
|
||||||
@ -547,7 +557,7 @@ impl GameDownloadAgent {
|
|||||||
pub fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
pub fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||||
self.setup_validate(app_handle);
|
self.setup_validate(app_handle);
|
||||||
|
|
||||||
let buckets = self.buckets.lock().unwrap();
|
let buckets = lock!(self.buckets);
|
||||||
let contexts: Vec<DropValidateContext> = buckets
|
let contexts: Vec<DropValidateContext> = buckets
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -559,7 +569,9 @@ impl GameDownloadAgent {
|
|||||||
let pool = ThreadPoolBuilder::new()
|
let pool = ThreadPoolBuilder::new()
|
||||||
.num_threads(max_download_threads)
|
.num_threads(max_download_threads)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap_or_else(|_| {
|
||||||
|
panic!("failed to build thread pool with {max_download_threads} threads")
|
||||||
|
});
|
||||||
|
|
||||||
let invalid_chunks = Arc::new(boxcar::Vec::new());
|
let invalid_chunks = Arc::new(boxcar::Vec::new());
|
||||||
pool.scope(|scope| {
|
pool.scope(|scope| {
|
||||||
@ -577,7 +589,7 @@ impl GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{e}");
|
error!("{e}");
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
send!(sender, DownloadManagerSignal::Error(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -604,7 +616,7 @@ impl GameDownloadAgent {
|
|||||||
// See docs on usage
|
// See docs on usage
|
||||||
set_partially_installed(
|
set_partially_installed(
|
||||||
&self.metadata(),
|
&self.metadata(),
|
||||||
self.dropdata.base_path.to_str().unwrap().to_string(),
|
self.dropdata.base_path.display().to_string(),
|
||||||
Some(app_handle),
|
Some(app_handle),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -614,12 +626,12 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
impl Downloadable for GameDownloadAgent {
|
impl Downloadable for GameDownloadAgent {
|
||||||
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||||
*self.status.lock().unwrap() = DownloadStatus::Downloading;
|
*lock!(self.status) = DownloadStatus::Downloading;
|
||||||
self.download(app_handle)
|
self.download(app_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||||
*self.status.lock().unwrap() = DownloadStatus::Validating;
|
*lock!(self.status) = DownloadStatus::Validating;
|
||||||
self.validate(app_handle)
|
self.validate(app_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -653,10 +665,8 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_error(&self, app_handle: &tauri::AppHandle, error: &ApplicationDownloadError) {
|
fn on_error(&self, app_handle: &tauri::AppHandle, error: &ApplicationDownloadError) {
|
||||||
*self.status.lock().unwrap() = DownloadStatus::Error;
|
*lock!(self.status) = DownloadStatus::Error;
|
||||||
app_handle
|
app_emit!(app_handle, "download_error", error.to_string());
|
||||||
.emit("download_error", error.to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
error!("error while managing download: {error:?}");
|
error!("error while managing download: {error:?}");
|
||||||
|
|
||||||
@ -675,12 +685,17 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
||||||
on_game_complete(
|
match on_game_complete(
|
||||||
&self.metadata(),
|
&self.metadata(),
|
||||||
self.dropdata.base_path.to_string_lossy().to_string(),
|
self.dropdata.base_path.to_string_lossy().to_string(),
|
||||||
app_handle,
|
app_handle,
|
||||||
)
|
) {
|
||||||
.unwrap();
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
error!("could not mark game as complete: {e}");
|
||||||
|
send!(self.sender, DownloadManagerSignal::Error(ApplicationDownloadError::DownloadError(e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_cancelled(&self, app_handle: &tauri::AppHandle) {
|
fn on_cancelled(&self, app_handle: &tauri::AppHandle) {
|
||||||
@ -689,6 +704,6 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn status(&self) -> DownloadStatus {
|
fn status(&self) -> DownloadStatus {
|
||||||
self.status.lock().unwrap().clone()
|
lock!(self.status).clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,11 +110,10 @@ impl<'a> DropDownloadPipeline<'a, Response, File> {
|
|||||||
let destination = self
|
let destination = self
|
||||||
.destination
|
.destination
|
||||||
.get_mut(index)
|
.get_mut(index)
|
||||||
.ok_or(io::Error::other("no destination"))
|
.ok_or(io::Error::other("no destination"))?;
|
||||||
.unwrap();
|
|
||||||
let mut remaining = drop.length;
|
let mut remaining = drop.length;
|
||||||
if drop.start != 0 {
|
if drop.start != 0 {
|
||||||
destination.seek(SeekFrom::Start(drop.start.try_into().unwrap()))?;
|
destination.seek(SeekFrom::Start(drop.start as u64))?;
|
||||||
}
|
}
|
||||||
let mut last_bump = 0;
|
let mut last_bump = 0;
|
||||||
loop {
|
loop {
|
||||||
@ -215,20 +214,39 @@ pub fn download_game_bucket(
|
|||||||
RemoteAccessError::UnparseableResponse("missing Content-Lengths header".to_owned()),
|
RemoteAccessError::UnparseableResponse("missing Content-Lengths header".to_owned()),
|
||||||
))?
|
))?
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap();
|
.map_err(|e| {
|
||||||
|
ApplicationDownloadError::Communication(RemoteAccessError::UnparseableResponse(
|
||||||
|
e.to_string(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
for (i, raw_length) in lengths.split(",").enumerate() {
|
for (i, raw_length) in lengths.split(",").enumerate() {
|
||||||
let length = raw_length.parse::<usize>().unwrap_or(0);
|
let length = raw_length.parse::<usize>().unwrap_or(0);
|
||||||
let Some(drop) = bucket.drops.get(i) else {
|
let Some(drop) = bucket.drops.get(i) else {
|
||||||
warn!("invalid number of Content-Lengths recieved: {i}, {lengths}");
|
warn!("invalid number of Content-Lengths recieved: {i}, {lengths}");
|
||||||
return Err(ApplicationDownloadError::DownloadError);
|
return Err(ApplicationDownloadError::DownloadError(
|
||||||
|
RemoteAccessError::InvalidResponse(DropServerError {
|
||||||
|
status_code: 400,
|
||||||
|
status_message: format!(
|
||||||
|
"invalid number of Content-Lengths recieved: {i}, {lengths}"
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
));
|
||||||
};
|
};
|
||||||
if drop.length != length {
|
if drop.length != length {
|
||||||
warn!(
|
warn!(
|
||||||
"for {}, expected {}, got {} ({})",
|
"for {}, expected {}, got {} ({})",
|
||||||
drop.filename, drop.length, raw_length, length
|
drop.filename, drop.length, raw_length, length
|
||||||
);
|
);
|
||||||
return Err(ApplicationDownloadError::DownloadError);
|
return Err(ApplicationDownloadError::DownloadError(
|
||||||
|
RemoteAccessError::InvalidResponse(DropServerError {
|
||||||
|
status_code: 400,
|
||||||
|
status_message: format!(
|
||||||
|
"for {}, expected {}, got {} ({})",
|
||||||
|
drop.filename, drop.length, raw_length, length
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ use std::{
|
|||||||
use log::error;
|
use log::error;
|
||||||
use native_model::{Decode, Encode};
|
use native_model::{Decode, Encode};
|
||||||
|
|
||||||
|
use crate::lock;
|
||||||
|
|
||||||
pub type DropData = v1::DropData;
|
pub type DropData = v1::DropData;
|
||||||
|
|
||||||
pub static DROP_DATA_PATH: &str = ".dropdata";
|
pub static DROP_DATA_PATH: &str = ".dropdata";
|
||||||
@ -49,7 +51,12 @@ impl DropData {
|
|||||||
let mut s = Vec::new();
|
let mut s = Vec::new();
|
||||||
file.read_to_end(&mut s)?;
|
file.read_to_end(&mut s)?;
|
||||||
|
|
||||||
Ok(native_model::rmp_serde_1_3::RmpSerde::decode(s).unwrap())
|
native_model::rmp_serde_1_3::RmpSerde::decode(s).map_err(|e| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!("Failed to decode drop data: {e}"),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
pub fn write(&self) {
|
pub fn write(&self) {
|
||||||
let manifest_raw = match native_model::rmp_serde_1_3::RmpSerde::encode(&self) {
|
let manifest_raw = match native_model::rmp_serde_1_3::RmpSerde::encode(&self) {
|
||||||
@ -71,12 +78,12 @@ impl DropData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_contexts(&self, completed_contexts: &[(String, bool)]) {
|
pub fn set_contexts(&self, completed_contexts: &[(String, bool)]) {
|
||||||
*self.contexts.lock().unwrap() = completed_contexts.iter().map(|s| (s.0.clone(), s.1)).collect();
|
*lock!(self.contexts) = completed_contexts.iter().map(|s| (s.0.clone(), s.1)).collect();
|
||||||
}
|
}
|
||||||
pub fn set_context(&self, context: String, state: bool) {
|
pub fn set_context(&self, context: String, state: bool) {
|
||||||
self.contexts.lock().unwrap().entry(context).insert_entry(state);
|
lock!(self.contexts).entry(context).insert_entry(state);
|
||||||
}
|
}
|
||||||
pub fn get_contexts(&self) -> HashMap<String, bool> {
|
pub fn get_contexts(&self) -> HashMap<String, bool> {
|
||||||
self.contexts.lock().unwrap().clone()
|
lock!(self.contexts).clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,14 +36,14 @@ pub fn validate_game_chunk(
|
|||||||
|
|
||||||
if ctx.offset != 0 {
|
if ctx.offset != 0 {
|
||||||
source
|
source
|
||||||
.seek(SeekFrom::Start(ctx.offset.try_into().unwrap()))
|
.seek(SeekFrom::Start(ctx.offset as u64))
|
||||||
.expect("Failed to seek to file offset");
|
.expect("Failed to seek to file offset");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut hasher = md5::Context::new();
|
let mut hasher = md5::Context::new();
|
||||||
|
|
||||||
let completed =
|
let completed =
|
||||||
validate_copy(&mut source, &mut hasher, ctx.length, control_flag, progress).unwrap();
|
validate_copy(&mut source, &mut hasher, ctx.length, control_flag, progress)?;
|
||||||
if !completed {
|
if !completed {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ use tauri::AppHandle;
|
|||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
use crate::app_emit;
|
||||||
use crate::database::db::{borrow_db_checked, borrow_db_mut_checked};
|
use crate::database::db::{borrow_db_checked, borrow_db_mut_checked};
|
||||||
use crate::database::models::data::Database;
|
use crate::database::models::data::Database;
|
||||||
use crate::database::models::data::{
|
use crate::database::models::data::{
|
||||||
@ -18,6 +19,7 @@ use crate::error::drop_server_error::DropServerError;
|
|||||||
use crate::error::library_error::LibraryError;
|
use crate::error::library_error::LibraryError;
|
||||||
use crate::error::remote_access_error::RemoteAccessError;
|
use crate::error::remote_access_error::RemoteAccessError;
|
||||||
use crate::games::state::{GameStatusManager, GameStatusWithTransient};
|
use crate::games::state::{GameStatusManager, GameStatusWithTransient};
|
||||||
|
use crate::lock;
|
||||||
use crate::remote::auth::generate_authorization_header;
|
use crate::remote::auth::generate_authorization_header;
|
||||||
use crate::remote::cache::cache_object_db;
|
use crate::remote::cache::cache_object_db;
|
||||||
use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db};
|
use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db};
|
||||||
@ -106,7 +108,7 @@ pub async fn fetch_library_logic(
|
|||||||
|
|
||||||
let mut games: Vec<Game> = response.json().await?;
|
let mut games: Vec<Game> = response.json().await?;
|
||||||
|
|
||||||
let mut handle = state.lock().unwrap();
|
let mut handle = lock!(state);
|
||||||
|
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
|
|
||||||
@ -172,7 +174,7 @@ pub async fn fetch_game_logic(
|
|||||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
||||||
) -> Result<FetchGameStruct, RemoteAccessError> {
|
) -> Result<FetchGameStruct, RemoteAccessError> {
|
||||||
let version = {
|
let version = {
|
||||||
let state_handle = state.lock().unwrap();
|
let state_handle = lock!(state);
|
||||||
|
|
||||||
let db_lock = borrow_db_checked();
|
let db_lock = borrow_db_checked();
|
||||||
|
|
||||||
@ -222,14 +224,14 @@ pub async fn fetch_game_logic(
|
|||||||
return Err(RemoteAccessError::GameNotFound(id));
|
return Err(RemoteAccessError::GameNotFound(id));
|
||||||
}
|
}
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
let err = response.json().await.unwrap();
|
let err = response.json().await?;
|
||||||
warn!("{err:?}");
|
warn!("{err:?}");
|
||||||
return Err(RemoteAccessError::InvalidResponse(err));
|
return Err(RemoteAccessError::InvalidResponse(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
let game: Game = response.json().await?;
|
let game: Game = response.json().await?;
|
||||||
|
|
||||||
let mut state_handle = state.lock().unwrap();
|
let mut state_handle = lock!(state);
|
||||||
state_handle.games.insert(id.clone(), game.clone());
|
state_handle.games.insert(id.clone(), game.clone());
|
||||||
|
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
@ -297,22 +299,18 @@ pub async fn fetch_game_version_options_logic(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
let err = response.json().await.unwrap();
|
let err = response.json().await?;
|
||||||
warn!("{err:?}");
|
warn!("{err:?}");
|
||||||
return Err(RemoteAccessError::InvalidResponse(err));
|
return Err(RemoteAccessError::InvalidResponse(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: Vec<GameVersion> = response.json().await?;
|
let data: Vec<GameVersion> = response.json().await?;
|
||||||
|
|
||||||
let state_lock = state.lock().unwrap();
|
let state_lock = lock!(state);
|
||||||
let process_manager_lock = state_lock.process_manager.lock().unwrap();
|
let process_manager_lock = lock!(state_lock.process_manager);
|
||||||
let data: Vec<GameVersion> = data
|
let data: Vec<GameVersion> = data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|v| {
|
.filter(|v| process_manager_lock.valid_platform(&v.platform, &state_lock))
|
||||||
process_manager_lock
|
|
||||||
.valid_platform(&v.platform, &state_lock)
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
drop(process_manager_lock);
|
drop(process_manager_lock);
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
@ -379,11 +377,13 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
|||||||
);
|
);
|
||||||
|
|
||||||
let previous_state = db_handle.applications.game_statuses.get(&meta.id).cloned();
|
let previous_state = db_handle.applications.game_statuses.get(&meta.id).cloned();
|
||||||
if previous_state.is_none() {
|
|
||||||
|
let previous_state = if let Some(state) = previous_state {
|
||||||
|
state
|
||||||
|
} else {
|
||||||
warn!("uninstall job doesn't have previous state, failing silently");
|
warn!("uninstall job doesn't have previous state, failing silently");
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
let previous_state = previous_state.unwrap();
|
|
||||||
|
|
||||||
if let Some((_, install_dir)) = match previous_state {
|
if let Some((_, install_dir)) = match previous_state {
|
||||||
GameDownloadStatus::Installed {
|
GameDownloadStatus::Installed {
|
||||||
@ -432,7 +432,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
|||||||
);
|
);
|
||||||
|
|
||||||
debug!("uninstalled game id {}", &meta.id);
|
debug!("uninstalled game id {}", &meta.id);
|
||||||
app_handle.emit("update_library", ()).unwrap();
|
app_emit!(app_handle, "update_library", ());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -505,17 +505,15 @@ pub fn on_game_complete(
|
|||||||
.game_statuses
|
.game_statuses
|
||||||
.insert(meta.id.clone(), status.clone());
|
.insert(meta.id.clone(), status.clone());
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
app_emit!(
|
||||||
app_handle
|
app_handle,
|
||||||
.emit(
|
&format!("update_game/{}", meta.id),
|
||||||
&format!("update_game/{}", meta.id),
|
GameUpdateEvent {
|
||||||
GameUpdateEvent {
|
game_id: meta.id.clone(),
|
||||||
game_id: meta.id.clone(),
|
status: (Some(status), None),
|
||||||
status: (Some(status), None),
|
version: Some(game_version),
|
||||||
version: Some(game_version),
|
}
|
||||||
},
|
);
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -533,16 +531,15 @@ pub fn push_game_update(
|
|||||||
panic!("pushed game for installed game that doesn't have version information");
|
panic!("pushed game for installed game that doesn't have version information");
|
||||||
}
|
}
|
||||||
|
|
||||||
app_handle
|
app_emit!(
|
||||||
.emit(
|
app_handle,
|
||||||
&format!("update_game/{game_id}"),
|
&format!("update_game/{game_id}"),
|
||||||
GameUpdateEvent {
|
GameUpdateEvent {
|
||||||
game_id: game_id.clone(),
|
game_id: game_id.clone(),
|
||||||
status,
|
status,
|
||||||
version,
|
version,
|
||||||
},
|
}
|
||||||
)
|
);
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -564,7 +561,7 @@ pub fn update_game_configuration(
|
|||||||
.ok_or(LibraryError::MetaNotFound(game_id))?;
|
.ok_or(LibraryError::MetaNotFound(game_id))?;
|
||||||
|
|
||||||
let id = installed_version.id.clone();
|
let id = installed_version.id.clone();
|
||||||
let version = installed_version.version.clone().unwrap();
|
let version = installed_version.version.clone().ok_or(LibraryError::VersionNotFound(id.clone()))?;
|
||||||
|
|
||||||
let mut existing_configuration = handle
|
let mut existing_configuration = handle
|
||||||
.applications
|
.applications
|
||||||
|
|||||||
@ -13,11 +13,14 @@ mod download_manager;
|
|||||||
mod error;
|
mod error;
|
||||||
mod process;
|
mod process;
|
||||||
mod remote;
|
mod remote;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use crate::database::scan::scan_install_dirs;
|
use crate::database::scan::scan_install_dirs;
|
||||||
use crate::process::commands::open_process_logs;
|
use crate::process::commands::open_process_logs;
|
||||||
use crate::process::process_handlers::UMU_LAUNCHER_EXECUTABLE;
|
use crate::process::process_handlers::UMU_LAUNCHER_EXECUTABLE;
|
||||||
use crate::remote::commands::auth_initiate_code;
|
use crate::remote::commands::auth_initiate_code;
|
||||||
|
use crate::remote::fetch_object::fetch_object_wrapper;
|
||||||
|
use crate::remote::server_proto::handle_server_proto_wrapper;
|
||||||
use crate::{database::db::DatabaseImpls, games::downloads::commands::resume_download};
|
use crate::{database::db::DatabaseImpls, games::downloads::commands::resume_download};
|
||||||
use bitcode::{Decode, Encode};
|
use bitcode::{Decode, Encode};
|
||||||
use client::commands::fetch_state;
|
use client::commands::fetch_state;
|
||||||
@ -58,8 +61,7 @@ use remote::commands::{
|
|||||||
auth_initiate, fetch_drop_object, gen_drop_url, manual_recieve_handshake, retry_connect,
|
auth_initiate, fetch_drop_object, gen_drop_url, manual_recieve_handshake, retry_connect,
|
||||||
sign_out, use_remote,
|
sign_out, use_remote,
|
||||||
};
|
};
|
||||||
use remote::fetch_object::fetch_object;
|
use remote::server_proto::handle_server_proto_offline_wrapper;
|
||||||
use remote::server_proto::{handle_server_proto, handle_server_proto_offline};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@ -137,7 +139,7 @@ async fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
)))
|
)))
|
||||||
.append(false)
|
.append(false)
|
||||||
.build(DATA_ROOT_DIR.join("./drop.log"))
|
.build(DATA_ROOT_DIR.join("./drop.log"))
|
||||||
.unwrap();
|
.expect("Failed to setup logfile");
|
||||||
|
|
||||||
let console = ConsoleAppender::builder()
|
let console = ConsoleAppender::builder()
|
||||||
.encoder(Box::new(PatternEncoder::new(
|
.encoder(Box::new(PatternEncoder::new(
|
||||||
@ -157,12 +159,12 @@ async fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
.appenders(vec!["logfile", "console"])
|
.appenders(vec!["logfile", "console"])
|
||||||
.build(LevelFilter::from_str(&log_level).expect("Invalid log level")),
|
.build(LevelFilter::from_str(&log_level).expect("Invalid log level")),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.expect("Failed to build config");
|
||||||
|
|
||||||
log4rs::init_config(config).unwrap();
|
log4rs::init_config(config).expect("Failed to initialise log4rs");
|
||||||
|
|
||||||
let games = HashMap::new();
|
let games = HashMap::new();
|
||||||
let download_manager = Arc::new(DownloadManagerBuilder::build(handle.clone()));
|
let download_manager = Arc::new(DownloadManagerBuilder::build(handle.clone()));
|
||||||
let process_manager = Arc::new(Mutex::new(ProcessManager::new(handle.clone())));
|
let process_manager = Arc::new(Mutex::new(ProcessManager::new(handle.clone())));
|
||||||
let compat_info = create_new_compat_info();
|
let compat_info = create_new_compat_info();
|
||||||
|
|
||||||
@ -370,42 +372,57 @@ pub fn run() {
|
|||||||
.shadow(false)
|
.shadow(false)
|
||||||
.data_directory(DATA_ROOT_DIR.join(".webview"))
|
.data_directory(DATA_ROOT_DIR.join(".webview"))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.expect("Failed to build main window");
|
||||||
|
|
||||||
app.deep_link().on_open_url(move |event| {
|
app.deep_link().on_open_url(move |event| {
|
||||||
debug!("handling drop:// url");
|
debug!("handling drop:// url");
|
||||||
let binding = event.urls();
|
let binding = event.urls();
|
||||||
let url = binding.first().unwrap();
|
let url = match binding.first() {
|
||||||
if url.host_str().unwrap() == "handshake" {
|
Some(url) => url,
|
||||||
tauri::async_runtime::spawn(recieve_handshake(
|
None => {
|
||||||
handle.clone(),
|
warn!("No value recieved from deep link. Is this a drop server?");
|
||||||
url.path().to_string(),
|
return;
|
||||||
));
|
}
|
||||||
|
};
|
||||||
|
if let Some("handshake") = url.host_str() {
|
||||||
|
tauri::async_runtime::spawn(recieve_handshake(
|
||||||
|
handle.clone(),
|
||||||
|
url.path().to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let open_menu_item = MenuItem::with_id(app, "open", "Open", true, None::<&str>).expect("Failed to generate open menu item");
|
||||||
|
|
||||||
|
let sep = PredefinedMenuItem::separator(app).expect("Failed to generate menu separator item");
|
||||||
|
|
||||||
|
let quit_menu_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).expect("Failed to generate quit menu item");
|
||||||
|
|
||||||
let menu = Menu::with_items(
|
let menu = Menu::with_items(
|
||||||
app,
|
app,
|
||||||
&[
|
&[
|
||||||
&MenuItem::with_id(app, "open", "Open", true, None::<&str>).unwrap(),
|
&open_menu_item,
|
||||||
&PredefinedMenuItem::separator(app).unwrap(),
|
&sep,
|
||||||
/*
|
/*
|
||||||
&MenuItem::with_id(app, "show_library", "Library", true, None::<&str>)?,
|
&MenuItem::with_id(app, "show_library", "Library", true, None::<&str>)?,
|
||||||
&MenuItem::with_id(app, "show_settings", "Settings", true, None::<&str>)?,
|
&MenuItem::with_id(app, "show_settings", "Settings", true, None::<&str>)?,
|
||||||
&PredefinedMenuItem::separator(app)?,
|
&PredefinedMenuItem::separator(app)?,
|
||||||
*/
|
*/
|
||||||
&MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap(),
|
&quit_menu_item,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.expect("Failed to generate menu");
|
||||||
|
|
||||||
run_on_tray(|| {
|
run_on_tray(|| {
|
||||||
TrayIconBuilder::new()
|
TrayIconBuilder::new()
|
||||||
.icon(app.default_window_icon().unwrap().clone())
|
.icon(app.default_window_icon().expect("Failed to get default window icon").clone())
|
||||||
.menu(&menu)
|
.menu(&menu)
|
||||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||||
"open" => {
|
"open" => {
|
||||||
app.webview_windows().get("main").unwrap().show().unwrap();
|
app.webview_windows()
|
||||||
|
.get("main")
|
||||||
|
.expect("Failed to get webview")
|
||||||
|
.show()
|
||||||
|
.expect("Failed to show window");
|
||||||
}
|
}
|
||||||
"quit" => {
|
"quit" => {
|
||||||
cleanup_and_exit(app, &app.state());
|
cleanup_and_exit(app, &app.state());
|
||||||
@ -422,15 +439,19 @@ pub fn run() {
|
|||||||
{
|
{
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
if let Some(original) = db_handle.prev_database.take() {
|
if let Some(original) = db_handle.prev_database.take() {
|
||||||
|
let canonicalised = match original.canonicalize() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => original,
|
||||||
|
};
|
||||||
warn!(
|
warn!(
|
||||||
"Database corrupted. Original file at {}",
|
"Database corrupted. Original file at {}",
|
||||||
original.canonicalize().unwrap().to_string_lossy()
|
canonicalised.display()
|
||||||
);
|
);
|
||||||
app.dialog()
|
app.dialog()
|
||||||
.message(
|
.message(format!(
|
||||||
"Database corrupted. A copy has been saved at: ".to_string()
|
"Database corrupted. A copy has been saved at: {}",
|
||||||
+ original.to_str().unwrap(),
|
canonicalised.display()
|
||||||
)
|
))
|
||||||
.title("Database corrupted")
|
.title("Database corrupted")
|
||||||
.show(|_| {});
|
.show(|_| {});
|
||||||
}
|
}
|
||||||
@ -441,7 +462,7 @@ pub fn run() {
|
|||||||
})
|
})
|
||||||
.register_asynchronous_uri_scheme_protocol("object", move |_ctx, request, responder| {
|
.register_asynchronous_uri_scheme_protocol("object", move |_ctx, request, responder| {
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
fetch_object(request, responder).await;
|
fetch_object_wrapper(request, responder).await;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.register_asynchronous_uri_scheme_protocol("server", |ctx, request, responder| {
|
.register_asynchronous_uri_scheme_protocol("server", |ctx, request, responder| {
|
||||||
@ -452,8 +473,8 @@ pub fn run() {
|
|||||||
|
|
||||||
offline!(
|
offline!(
|
||||||
state,
|
state,
|
||||||
handle_server_proto,
|
handle_server_proto_wrapper,
|
||||||
handle_server_proto_offline,
|
handle_server_proto_offline_wrapper,
|
||||||
request,
|
request,
|
||||||
responder
|
responder
|
||||||
)
|
)
|
||||||
@ -463,7 +484,7 @@ pub fn run() {
|
|||||||
.on_window_event(|window, event| {
|
.on_window_event(|window, event| {
|
||||||
if let WindowEvent::CloseRequested { api, .. } = event {
|
if let WindowEvent::CloseRequested { api, .. } = event {
|
||||||
run_on_tray(|| {
|
run_on_tray(|| {
|
||||||
window.hide().unwrap();
|
window.hide().expect("Failed to close window in tray");
|
||||||
api.prevent_close();
|
api.prevent_close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use crate::{error::process_error::ProcessError, AppState};
|
use crate::{error::process_error::ProcessError, lock, AppState};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn launch_game(
|
pub fn launch_game(
|
||||||
id: String,
|
id: String,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), ProcessError> {
|
) -> Result<(), ProcessError> {
|
||||||
let state_lock = state.lock().unwrap();
|
let state_lock = lock!(state);
|
||||||
let mut process_manager_lock = state_lock.process_manager.lock().unwrap();
|
let mut process_manager_lock = lock!(state_lock.process_manager);
|
||||||
|
|
||||||
//let meta = DownloadableMetadata {
|
//let meta = DownloadableMetadata {
|
||||||
// id,
|
// id,
|
||||||
@ -32,8 +32,8 @@ pub fn kill_game(
|
|||||||
game_id: String,
|
game_id: String,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), ProcessError> {
|
) -> Result<(), ProcessError> {
|
||||||
let state_lock = state.lock().unwrap();
|
let state_lock = lock!(state);
|
||||||
let mut process_manager_lock = state_lock.process_manager.lock().unwrap();
|
let mut process_manager_lock = lock!(state_lock.process_manager);
|
||||||
process_manager_lock
|
process_manager_lock
|
||||||
.kill_game(game_id)
|
.kill_game(game_id)
|
||||||
.map_err(ProcessError::IOError)
|
.map_err(ProcessError::IOError)
|
||||||
@ -44,7 +44,7 @@ pub fn open_process_logs(
|
|||||||
game_id: String,
|
game_id: String,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), ProcessError> {
|
) -> Result<(), ProcessError> {
|
||||||
let state_lock = state.lock().unwrap();
|
let state_lock = lock!(state);
|
||||||
let mut process_manager_lock = state_lock.process_manager.lock().unwrap();
|
let mut process_manager_lock = lock!(state_lock.process_manager);
|
||||||
process_manager_lock.open_process_logs(game_id)
|
process_manager_lock.open_process_logs(game_id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use log::{debug, info};
|
|||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
database::models::data::{Database, DownloadableMetadata, GameVersion},
|
database::models::data::{Database, DownloadableMetadata, GameVersion},
|
||||||
|
error::process_error::ProcessError,
|
||||||
process::process_manager::{Platform, ProcessHandler},
|
process::process_manager::{Platform, ProcessHandler},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,8 +23,8 @@ impl ProcessHandler for NativeGameLauncher {
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
_game_version: &GameVersion,
|
_game_version: &GameVersion,
|
||||||
_current_dir: &str,
|
_current_dir: &str,
|
||||||
) -> String {
|
) -> Result<String, ProcessError> {
|
||||||
format!("\"{}\" {}", launch_command, args.join(" "))
|
Ok(format!("\"{}\" {}", launch_command, args.join(" ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn valid_for_platform(&self, _db: &Database, _state: &AppState, _target: &Platform) -> bool {
|
fn valid_for_platform(&self, _db: &Database, _state: &AppState, _target: &Platform) -> bool {
|
||||||
@ -65,7 +66,7 @@ impl ProcessHandler for UMULauncher {
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
game_version: &GameVersion,
|
game_version: &GameVersion,
|
||||||
_current_dir: &str,
|
_current_dir: &str,
|
||||||
) -> String {
|
) -> Result<String, ProcessError> {
|
||||||
debug!("Game override: \"{:?}\"", &game_version.umu_id_override);
|
debug!("Game override: \"{:?}\"", &game_version.umu_id_override);
|
||||||
let game_id = match &game_version.umu_id_override {
|
let game_id = match &game_version.umu_id_override {
|
||||||
Some(game_override) => {
|
Some(game_override) => {
|
||||||
@ -77,12 +78,12 @@ impl ProcessHandler for UMULauncher {
|
|||||||
}
|
}
|
||||||
None => game_version.game_id.clone(),
|
None => game_version.game_id.clone(),
|
||||||
};
|
};
|
||||||
format!(
|
Ok(format!(
|
||||||
"GAMEID={game_id} {umu:?} \"{launch}\" {args}",
|
"GAMEID={game_id} {umu:?} \"{launch}\" {args}",
|
||||||
umu = UMU_LAUNCHER_EXECUTABLE.as_ref().unwrap(),
|
umu = UMU_LAUNCHER_EXECUTABLE.as_ref().expect("Failed to get UMU_LAUNCHER_EXECUTABLE as ref"),
|
||||||
launch = launch_command,
|
launch = launch_command,
|
||||||
args = args.join(" ")
|
args = args.join(" ")
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn valid_for_platform(&self, _db: &Database, state: &AppState, _target: &Platform) -> bool {
|
fn valid_for_platform(&self, _db: &Database, state: &AppState, _target: &Platform) -> bool {
|
||||||
@ -102,7 +103,7 @@ impl ProcessHandler for AsahiMuvmLauncher {
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
game_version: &GameVersion,
|
game_version: &GameVersion,
|
||||||
current_dir: &str,
|
current_dir: &str,
|
||||||
) -> String {
|
) -> Result<String, ProcessError> {
|
||||||
let umu_launcher = UMULauncher {};
|
let umu_launcher = UMULauncher {};
|
||||||
let umu_string = umu_launcher.create_launch_process(
|
let umu_string = umu_launcher.create_launch_process(
|
||||||
meta,
|
meta,
|
||||||
@ -110,15 +111,18 @@ impl ProcessHandler for AsahiMuvmLauncher {
|
|||||||
args,
|
args,
|
||||||
game_version,
|
game_version,
|
||||||
current_dir,
|
current_dir,
|
||||||
);
|
)?;
|
||||||
let mut args_cmd = umu_string
|
let mut args_cmd = umu_string
|
||||||
.split("umu-run")
|
.split("umu-run")
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.into_iter();
|
.into_iter();
|
||||||
let args = args_cmd.next().unwrap().trim();
|
let args = args_cmd
|
||||||
let cmd = format!("umu-run{}", args_cmd.next().unwrap());
|
.next()
|
||||||
|
.ok_or(ProcessError::InvalidArguments(umu_string.clone()))?
|
||||||
|
.trim();
|
||||||
|
let cmd = format!("umu-run{}", args_cmd.next().ok_or(ProcessError::InvalidArguments(umu_string.clone()))?);
|
||||||
|
|
||||||
format!("{args} muvm -- {cmd}")
|
Ok(format!("{args} muvm -- {cmd}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
|
|||||||
@ -19,7 +19,7 @@ use tauri::{AppHandle, Emitter, Manager};
|
|||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState, DB,
|
AppState,
|
||||||
database::{
|
database::{
|
||||||
db::{DATA_ROOT_DIR, borrow_db_checked, borrow_db_mut_checked},
|
db::{DATA_ROOT_DIR, borrow_db_checked, borrow_db_mut_checked},
|
||||||
models::data::{
|
models::data::{
|
||||||
@ -33,6 +33,7 @@ use crate::{
|
|||||||
format::DropFormatArgs,
|
format::DropFormatArgs,
|
||||||
process_handlers::{AsahiMuvmLauncher, NativeGameLauncher, UMULauncher},
|
process_handlers::{AsahiMuvmLauncher, NativeGameLauncher, UMULauncher},
|
||||||
},
|
},
|
||||||
|
lock,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RunningProcess {
|
pub struct RunningProcess {
|
||||||
@ -118,7 +119,7 @@ impl ProcessManager<'_> {
|
|||||||
let dir = self.get_log_dir(game_id);
|
let dir = self.get_log_dir(game_id);
|
||||||
self.app_handle
|
self.app_handle
|
||||||
.opener()
|
.opener()
|
||||||
.open_path(dir.to_str().unwrap(), None::<&str>)
|
.open_path(dir.display().to_string(), None::<&str>)
|
||||||
.map_err(ProcessError::OpenerError)?;
|
.map_err(ProcessError::OpenerError)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -133,7 +134,13 @@ impl ProcessManager<'_> {
|
|||||||
|
|
||||||
debug!("process for {:?} exited with {:?}", &game_id, result);
|
debug!("process for {:?} exited with {:?}", &game_id, result);
|
||||||
|
|
||||||
let process = self.processes.remove(&game_id).unwrap();
|
let process = match self.processes.remove(&game_id) {
|
||||||
|
Some(process) => process,
|
||||||
|
None => {
|
||||||
|
info!("Attempted to stop process {game_id} which didn't exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
let meta = db_handle
|
let meta = db_handle
|
||||||
@ -141,7 +148,7 @@ impl ProcessManager<'_> {
|
|||||||
.installed_game_version
|
.installed_game_version
|
||||||
.get(&game_id)
|
.get(&game_id)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap();
|
.unwrap_or_else(|| panic!("Could not get installed version of {}", &game_id));
|
||||||
db_handle.applications.transient_statuses.remove(&meta);
|
db_handle.applications.transient_statuses.remove(&meta);
|
||||||
|
|
||||||
let current_state = db_handle.applications.game_statuses.get(&game_id).cloned();
|
let current_state = db_handle.applications.game_statuses.get(&game_id).cloned();
|
||||||
@ -166,20 +173,17 @@ impl ProcessManager<'_> {
|
|||||||
// Or if the status isn't 0
|
// Or if the status isn't 0
|
||||||
// Or if it's an error
|
// Or if it's an error
|
||||||
if !process.manually_killed
|
if !process.manually_killed
|
||||||
&& (elapsed.as_secs() <= 2 || result.is_err() || !result.unwrap().success())
|
&& (elapsed.as_secs() <= 2 || result.map_or(true, |r| !r.success()))
|
||||||
{
|
{
|
||||||
warn!("drop detected that the game {game_id} may have failed to launch properly");
|
warn!("drop detected that the game {game_id} may have failed to launch properly");
|
||||||
let _ = self.app_handle.emit("launch_external_error", &game_id);
|
let _ = self.app_handle.emit("launch_external_error", &game_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is too many unwraps for me to be comfortable
|
let version_data = match db_handle.applications.game_versions.get(&game_id) {
|
||||||
let version_data = db_handle
|
// This unwrap here should be resolved by just making the hashmap accept an option rather than just a String
|
||||||
.applications
|
Some(res) => res.get(&meta.version.unwrap()).expect("Failed to get game version from installed game versions. Is the database corrupted?"),
|
||||||
.game_versions
|
None => todo!(),
|
||||||
.get(&game_id)
|
};
|
||||||
.unwrap()
|
|
||||||
.get(&meta.version.unwrap())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let status = GameStatusManager::fetch_state(&game_id, &db_handle);
|
let status = GameStatusManager::fetch_state(&game_id, &db_handle);
|
||||||
|
|
||||||
@ -210,10 +214,10 @@ impl ProcessManager<'_> {
|
|||||||
.1)
|
.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn valid_platform(&self, platform: &Platform, state: &AppState) -> Result<bool, String> {
|
pub fn valid_platform(&self, platform: &Platform, state: &AppState) -> bool {
|
||||||
let db_lock = borrow_db_checked();
|
let db_lock = borrow_db_checked();
|
||||||
let process_handler = self.fetch_process_handler(&db_lock, state, platform);
|
let process_handler = self.fetch_process_handler(&db_lock, state, platform);
|
||||||
Ok(process_handler.is_ok())
|
process_handler.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launch_process(
|
pub fn launch_process(
|
||||||
@ -225,9 +229,7 @@ impl ProcessManager<'_> {
|
|||||||
return Err(ProcessError::AlreadyRunning);
|
return Err(ProcessError::AlreadyRunning);
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = match DB
|
let version = match borrow_db_checked()
|
||||||
.borrow_data()
|
|
||||||
.unwrap()
|
|
||||||
.applications
|
.applications
|
||||||
.game_statuses
|
.game_statuses
|
||||||
.get(&game_id)
|
.get(&game_id)
|
||||||
@ -266,7 +268,7 @@ impl ProcessManager<'_> {
|
|||||||
debug!(
|
debug!(
|
||||||
"Launching process {:?} with version {:?}",
|
"Launching process {:?} with version {:?}",
|
||||||
&game_id,
|
&game_id,
|
||||||
db_lock.applications.game_versions.get(&game_id).unwrap()
|
db_lock.applications.game_versions.get(&game_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
let game_version = db_lock
|
let game_version = db_lock
|
||||||
@ -322,8 +324,9 @@ impl ProcessManager<'_> {
|
|||||||
GameDownloadStatus::Remote {} => unreachable!("Game registered as 'Remote'"),
|
GameDownloadStatus::Remote {} => unreachable!("Game registered as 'Remote'"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
let launch = PathBuf::from_str(install_dir).unwrap().join(launch);
|
let launch = PathBuf::from_str(install_dir).unwrap().join(launch);
|
||||||
let launch = launch.to_str().unwrap();
|
let launch = launch.display().to_string();
|
||||||
|
|
||||||
let launch_string = process_handler.create_launch_process(
|
let launch_string = process_handler.create_launch_process(
|
||||||
&meta,
|
&meta,
|
||||||
@ -331,7 +334,7 @@ impl ProcessManager<'_> {
|
|||||||
args.clone(),
|
args.clone(),
|
||||||
game_version,
|
game_version,
|
||||||
install_dir,
|
install_dir,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
let format_args = DropFormatArgs::new(
|
let format_args = DropFormatArgs::new(
|
||||||
launch_string,
|
launch_string,
|
||||||
@ -392,9 +395,12 @@ impl ProcessManager<'_> {
|
|||||||
let result: Result<ExitStatus, std::io::Error> = launch_process_handle.wait();
|
let result: Result<ExitStatus, std::io::Error> = launch_process_handle.wait();
|
||||||
|
|
||||||
let app_state = wait_thread_apphandle.state::<Mutex<AppState>>();
|
let app_state = wait_thread_apphandle.state::<Mutex<AppState>>();
|
||||||
let app_state_handle = app_state.lock().unwrap();
|
let app_state_handle = lock!(app_state);
|
||||||
|
|
||||||
let mut process_manager_handle = app_state_handle.process_manager.lock().unwrap();
|
let mut process_manager_handle = app_state_handle
|
||||||
|
.process_manager
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to lock onto process manager");
|
||||||
process_manager_handle.on_process_finish(wait_thread_game_id.id, result);
|
process_manager_handle.on_process_finish(wait_thread_game_id.id, result);
|
||||||
|
|
||||||
// As everything goes out of scope, they should get dropped
|
// As everything goes out of scope, they should get dropped
|
||||||
@ -468,7 +474,7 @@ pub trait ProcessHandler: Send + 'static {
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
game_version: &GameVersion,
|
game_version: &GameVersion,
|
||||||
current_dir: &str,
|
current_dir: &str,
|
||||||
) -> String;
|
) -> Result<String, ProcessError>;
|
||||||
|
|
||||||
fn valid_for_platform(&self, db: &Database, state: &AppState, target: &Platform) -> bool;
|
fn valid_for_platform(&self, db: &Database, state: &AppState, target: &Platform) -> bool;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,10 +9,10 @@ use tauri::{AppHandle, Emitter, Manager};
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::{
|
app_emit, database::{
|
||||||
db::{borrow_db_checked, borrow_db_mut_checked},
|
db::{borrow_db_checked, borrow_db_mut_checked},
|
||||||
models::data::DatabaseAuth,
|
models::data::DatabaseAuth,
|
||||||
}, error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError}, remote::{cache::clear_cached_object, requests::make_authenticated_get, utils::{DROP_CLIENT_ASYNC, DROP_CLIENT_SYNC}}, AppState, AppStatus, User
|
}, error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError}, lock, remote::{cache::clear_cached_object, requests::make_authenticated_get, utils::{DROP_CLIENT_ASYNC, DROP_CLIENT_SYNC}}, AppState, AppStatus, User
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -51,12 +51,13 @@ struct HandshakeResponse {
|
|||||||
pub fn generate_authorization_header() -> String {
|
pub fn generate_authorization_header() -> String {
|
||||||
let certs = {
|
let certs = {
|
||||||
let db = borrow_db_checked();
|
let db = borrow_db_checked();
|
||||||
db.auth.clone().unwrap()
|
db.auth.clone().expect("Authorisation not initialised")
|
||||||
};
|
};
|
||||||
|
|
||||||
let nonce = Utc::now().timestamp_millis().to_string();
|
let nonce = Utc::now().timestamp_millis().to_string();
|
||||||
|
|
||||||
let signature = sign_nonce(certs.private, nonce.clone()).unwrap();
|
let signature =
|
||||||
|
sign_nonce(certs.private, nonce.clone()).expect("Failed to generate authorisation header");
|
||||||
|
|
||||||
format!("Nonce {} {} {}", certs.client_id, nonce, signature)
|
format!("Nonce {} {} {}", certs.client_id, nonce, signature)
|
||||||
}
|
}
|
||||||
@ -83,7 +84,7 @@ pub async fn fetch_user() -> Result<User, RemoteAccessError> {
|
|||||||
async fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAccessError> {
|
async fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAccessError> {
|
||||||
let path_chunks: Vec<&str> = path.split('/').collect();
|
let path_chunks: Vec<&str> = path.split('/').collect();
|
||||||
if path_chunks.len() != 3 {
|
if path_chunks.len() != 3 {
|
||||||
app.emit("auth/failed", ()).unwrap();
|
app_emit!(app, "auth/failed", ());
|
||||||
return Err(RemoteAccessError::HandshakeFailed(
|
return Err(RemoteAccessError::HandshakeFailed(
|
||||||
"failed to parse token".to_string(),
|
"failed to parse token".to_string(),
|
||||||
));
|
));
|
||||||
@ -94,11 +95,15 @@ async fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), Re
|
|||||||
Url::parse(handle.base_url.as_str())?
|
Url::parse(handle.base_url.as_str())?
|
||||||
};
|
};
|
||||||
|
|
||||||
let client_id = path_chunks.get(1).unwrap();
|
let client_id = path_chunks
|
||||||
let token = path_chunks.get(2).unwrap();
|
.get(1)
|
||||||
|
.expect("Failed to get client id from path chunks");
|
||||||
|
let token = path_chunks
|
||||||
|
.get(2)
|
||||||
|
.expect("Failed to get token from path chunks");
|
||||||
let body = HandshakeRequestBody {
|
let body = HandshakeRequestBody {
|
||||||
client_id: (*client_id).to_string(),
|
client_id: (client_id).to_string(),
|
||||||
token: (*token).to_string(),
|
token: (token).to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let endpoint = base_url.join("/api/v1/client/auth/handshake")?;
|
let endpoint = base_url.join("/api/v1/client/auth/handshake")?;
|
||||||
@ -116,37 +121,34 @@ async fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), Re
|
|||||||
private: response_struct.private,
|
private: response_struct.private,
|
||||||
cert: response_struct.certificate,
|
cert: response_struct.certificate,
|
||||||
client_id: response_struct.id,
|
client_id: response_struct.id,
|
||||||
web_token: None, // gets created later
|
web_token: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let web_token = {
|
let web_token = {
|
||||||
let header = generate_authorization_header();
|
let header = generate_authorization_header();
|
||||||
let token = client
|
let token = client
|
||||||
.post(base_url.join("/api/v1/client/user/webtoken").unwrap())
|
.post(base_url.join("/api/v1/client/user/webtoken")?)
|
||||||
.header("Authorization", header)
|
.header("Authorization", header)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
token.text().await.unwrap()
|
token.text().await?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut handle = borrow_db_mut_checked();
|
let mut handle = borrow_db_mut_checked();
|
||||||
let mut_auth = handle.auth.as_mut().unwrap();
|
handle.auth.as_mut().unwrap().web_token = Some(web_token);
|
||||||
mut_auth.web_token = Some(web_token);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn recieve_handshake(app: AppHandle, path: String) {
|
pub async fn recieve_handshake(app: AppHandle, path: String) {
|
||||||
// Tell the app we're processing
|
// Tell the app we're processing
|
||||||
app.emit("auth/processing", ()).unwrap();
|
app_emit!(app, "auth/processing", ());
|
||||||
|
|
||||||
let handshake_result = recieve_handshake_logic(&app, path).await;
|
let handshake_result = recieve_handshake_logic(&app, path).await;
|
||||||
if let Err(e) = handshake_result {
|
if let Err(e) = handshake_result {
|
||||||
warn!("error with authentication: {e}");
|
warn!("error with authentication: {e}");
|
||||||
app.emit("auth/failed", e.to_string()).unwrap();
|
app_emit!(app, "auth/failed", e.to_string());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +156,7 @@ pub async fn recieve_handshake(app: AppHandle, path: String) {
|
|||||||
|
|
||||||
let (app_status, user) = setup().await;
|
let (app_status, user) = setup().await;
|
||||||
|
|
||||||
let mut state_lock = app_state.lock().unwrap();
|
let mut state_lock = lock!(app_state);
|
||||||
|
|
||||||
state_lock.status = app_status;
|
state_lock.status = app_status;
|
||||||
state_lock.user = user;
|
state_lock.user = user;
|
||||||
@ -164,7 +166,7 @@ pub async fn recieve_handshake(app: AppHandle, path: String) {
|
|||||||
|
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
|
|
||||||
app.emit("auth/finished", ()).unwrap();
|
app_emit!(app, "auth/finished", ());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auth_initiate_logic(mode: String) -> Result<String, RemoteAccessError> {
|
pub fn auth_initiate_logic(mode: String) -> Result<String, RemoteAccessError> {
|
||||||
@ -177,7 +179,7 @@ pub fn auth_initiate_logic(mode: String) -> Result<String, RemoteAccessError> {
|
|||||||
|
|
||||||
let endpoint = base_url.join("/api/v1/client/auth/initiate")?;
|
let endpoint = base_url.join("/api/v1/client/auth/initiate")?;
|
||||||
let body = InitiateRequestBody {
|
let body = InitiateRequestBody {
|
||||||
name: format!("{} (Desktop)", hostname.into_string().unwrap()),
|
name: format!("{} (Desktop)", hostname.display()),
|
||||||
platform: env::consts::OS.to_string(),
|
platform: env::consts::OS.to_string(),
|
||||||
capabilities: HashMap::from([
|
capabilities: HashMap::from([
|
||||||
("peerAPI".to_owned(), CapabilityConfiguration {}),
|
("peerAPI".to_owned(), CapabilityConfiguration {}),
|
||||||
@ -211,12 +213,14 @@ pub async fn setup() -> (AppStatus, Option<User>) {
|
|||||||
let user_result = match fetch_user().await {
|
let user_result = match fetch_user().await {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(RemoteAccessError::FetchError(_)) => {
|
Err(RemoteAccessError::FetchError(_)) => {
|
||||||
let user = get_cached_object::<User>("user").unwrap();
|
let user = get_cached_object::<User>("user").ok();
|
||||||
return (AppStatus::Offline, Some(user));
|
return (AppStatus::Offline, user);
|
||||||
}
|
}
|
||||||
Err(_) => return (AppStatus::SignedInNeedsReauth, None),
|
Err(_) => return (AppStatus::SignedInNeedsReauth, None),
|
||||||
};
|
};
|
||||||
cache_object("user", &user_result).unwrap();
|
if let Err(e) = cache_object("user", &user_result) {
|
||||||
|
warn!("Could not cache user object with error {e}");
|
||||||
|
}
|
||||||
return (AppStatus::SignedIn, Some(user_result));
|
return (AppStatus::SignedIn, Some(user_result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,16 +7,16 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::{db::borrow_db_checked, models::data::Database},
|
database::{db::borrow_db_checked, models::data::Database},
|
||||||
error::remote_access_error::RemoteAccessError,
|
error::{cache_error::CacheError, remote_access_error::RemoteAccessError},
|
||||||
};
|
};
|
||||||
use bitcode::{Decode, DecodeOwned, Encode};
|
use bitcode::{Decode, DecodeOwned, Encode};
|
||||||
use http::{Response, header::CONTENT_TYPE, response::Builder as ResponseBuilder};
|
use http::{header::{CONTENT_TYPE}, response::Builder as ResponseBuilder, Response};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! offline {
|
macro_rules! offline {
|
||||||
($var:expr, $func1:expr, $func2:expr, $( $arg:expr ),* ) => {
|
($var:expr, $func1:expr, $func2:expr, $( $arg:expr ),* ) => {
|
||||||
|
|
||||||
async move { if $crate::borrow_db_checked().settings.force_offline || $var.lock().unwrap().status == $crate::AppStatus::Offline {
|
async move { if $crate::borrow_db_checked().settings.force_offline || $crate::lock!($var).status == $crate::AppStatus::Offline {
|
||||||
$func2( $( $arg ), *).await
|
$func2( $( $arg ), *).await
|
||||||
} else {
|
} else {
|
||||||
$func1( $( $arg ), *).await
|
$func1( $( $arg ), *).await
|
||||||
@ -104,30 +104,36 @@ impl ObjectCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Response<Vec<u8>>> for ObjectCache {
|
impl TryFrom<Response<Vec<u8>>> for ObjectCache {
|
||||||
fn from(value: Response<Vec<u8>>) -> Self {
|
type Error = CacheError;
|
||||||
ObjectCache {
|
|
||||||
|
fn try_from(value: Response<Vec<u8>>) -> Result<Self, Self::Error> {
|
||||||
|
Ok(ObjectCache {
|
||||||
content_type: value
|
content_type: value
|
||||||
.headers()
|
.headers()
|
||||||
.get(CONTENT_TYPE)
|
.get(CONTENT_TYPE)
|
||||||
.unwrap()
|
.ok_or(CacheError::HeaderNotFound(CONTENT_TYPE))?
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap()
|
.map_err(CacheError::ParseError)?
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
body: value.body().clone(),
|
body: value.body().clone(),
|
||||||
expiry: get_sys_time_in_secs() + 60 * 60 * 24,
|
expiry: get_sys_time_in_secs() + 60 * 60 * 24,
|
||||||
}
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<ObjectCache> for Response<Vec<u8>> {
|
impl TryFrom<ObjectCache> for Response<Vec<u8>> {
|
||||||
fn from(value: ObjectCache) -> Self {
|
type Error = CacheError;
|
||||||
|
fn try_from(value: ObjectCache) -> Result<Self, Self::Error> {
|
||||||
let resp_builder = ResponseBuilder::new().header(CONTENT_TYPE, value.content_type);
|
let resp_builder = ResponseBuilder::new().header(CONTENT_TYPE, value.content_type);
|
||||||
resp_builder.body(value.body).unwrap()
|
resp_builder.body(value.body).map_err(CacheError::ConstructionError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<&ObjectCache> for Response<Vec<u8>> {
|
impl TryFrom<&ObjectCache> for Response<Vec<u8>> {
|
||||||
fn from(value: &ObjectCache) -> Self {
|
type Error = CacheError;
|
||||||
|
|
||||||
|
fn try_from(value: &ObjectCache) -> Result<Self, Self::Error> {
|
||||||
let resp_builder = ResponseBuilder::new().header(CONTENT_TYPE, value.content_type.clone());
|
let resp_builder = ResponseBuilder::new().header(CONTENT_TYPE, value.content_type.clone());
|
||||||
resp_builder.body(value.body.clone()).unwrap()
|
resp_builder.body(value.body.clone()).map_err(CacheError::ConstructionError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,14 +8,16 @@ use tauri::{AppHandle, Emitter, Manager};
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState, AppStatus,
|
AppState, AppStatus, app_emit,
|
||||||
database::db::{borrow_db_checked, borrow_db_mut_checked},
|
database::db::{borrow_db_checked, borrow_db_mut_checked},
|
||||||
error::remote_access_error::RemoteAccessError,
|
error::remote_access_error::RemoteAccessError,
|
||||||
|
lock,
|
||||||
remote::{
|
remote::{
|
||||||
auth::generate_authorization_header,
|
auth::generate_authorization_header,
|
||||||
requests::generate_url,
|
requests::generate_url,
|
||||||
utils::{DROP_CLIENT_SYNC, DROP_CLIENT_WS_CLIENT},
|
utils::{DROP_CLIENT_SYNC, DROP_CLIENT_WS_CLIENT},
|
||||||
},
|
},
|
||||||
|
utils::webbrowser_open::webbrowser_open,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -40,7 +42,7 @@ pub fn gen_drop_url(path: String) -> Result<String, RemoteAccessError> {
|
|||||||
Url::parse(&handle.base_url).map_err(RemoteAccessError::ParsingError)?
|
Url::parse(&handle.base_url).map_err(RemoteAccessError::ParsingError)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = base_url.join(&path).unwrap();
|
let url = base_url.join(&path)?;
|
||||||
|
|
||||||
Ok(url.to_string())
|
Ok(url.to_string())
|
||||||
}
|
}
|
||||||
@ -77,20 +79,20 @@ pub fn sign_out(app: AppHandle) {
|
|||||||
// Update app state
|
// Update app state
|
||||||
{
|
{
|
||||||
let app_state = app.state::<Mutex<AppState>>();
|
let app_state = app.state::<Mutex<AppState>>();
|
||||||
let mut app_state_handle = app_state.lock().unwrap();
|
let mut app_state_handle = lock!(app_state);
|
||||||
app_state_handle.status = AppStatus::SignedOut;
|
app_state_handle.status = AppStatus::SignedOut;
|
||||||
app_state_handle.user = None;
|
app_state_handle.user = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit event for frontend
|
// Emit event for frontend
|
||||||
app.emit("auth/signedout", ()).unwrap();
|
app_emit!(app, "auth/signedout", ());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn retry_connect(state: tauri::State<'_, Mutex<AppState<'_>>>) -> Result<(), ()> {
|
pub async fn retry_connect(state: tauri::State<'_, Mutex<AppState<'_>>>) -> Result<(), ()> {
|
||||||
let (app_status, user) = setup().await;
|
let (app_status, user) = setup().await;
|
||||||
|
|
||||||
let mut guard = state.lock().unwrap();
|
let mut guard = lock!(state);
|
||||||
guard.status = app_status;
|
guard.status = app_status;
|
||||||
guard.user = user;
|
guard.user = user;
|
||||||
drop(guard);
|
drop(guard);
|
||||||
@ -109,7 +111,7 @@ pub fn auth_initiate() -> Result<(), RemoteAccessError> {
|
|||||||
let complete_redir_url = base_url.join(&redir_url)?;
|
let complete_redir_url = base_url.join(&redir_url)?;
|
||||||
|
|
||||||
debug!("opening web browser to continue authentication");
|
debug!("opening web browser to continue authentication");
|
||||||
webbrowser::open(complete_redir_url.as_ref()).unwrap();
|
webbrowser_open(complete_redir_url.as_ref());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +126,7 @@ struct CodeWebsocketResponse {
|
|||||||
pub fn auth_initiate_code(app: AppHandle) -> Result<String, RemoteAccessError> {
|
pub fn auth_initiate_code(app: AppHandle) -> Result<String, RemoteAccessError> {
|
||||||
let base_url = {
|
let base_url = {
|
||||||
let db_lock = borrow_db_checked();
|
let db_lock = borrow_db_checked();
|
||||||
Url::parse(&db_lock.base_url.clone())?
|
Url::parse(&db_lock.base_url.clone())?.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let code = auth_initiate_logic("code".to_string())?;
|
let code = auth_initiate_logic("code".to_string())?;
|
||||||
@ -151,14 +153,13 @@ pub fn auth_initiate_code(app: AppHandle) -> Result<String, RemoteAccessError> {
|
|||||||
match response.response_type.as_str() {
|
match response.response_type.as_str() {
|
||||||
"token" => {
|
"token" => {
|
||||||
let recieve_app = app.clone();
|
let recieve_app = app.clone();
|
||||||
manual_recieve_handshake(recieve_app, response.value).await.unwrap();
|
manual_recieve_handshake(recieve_app, response.value).await;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => return Err(RemoteAccessError::HandshakeFailed(response.value)),
|
_ => return Err(RemoteAccessError::HandshakeFailed(response.value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(RemoteAccessError::HandshakeFailed(
|
Err(RemoteAccessError::HandshakeFailed(
|
||||||
"Failed to connect to websocket".to_string(),
|
"Failed to connect to websocket".to_string(),
|
||||||
))
|
))
|
||||||
@ -167,7 +168,7 @@ pub fn auth_initiate_code(app: AppHandle) -> Result<String, RemoteAccessError> {
|
|||||||
let result = load().await;
|
let result = load().await;
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
warn!("{err}");
|
warn!("{err}");
|
||||||
app.emit("auth/failed", err.to_string()).unwrap();
|
app_emit!(app, "auth/failed", err.to_string());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -175,8 +176,6 @@ pub fn auth_initiate_code(app: AppHandle) -> Result<String, RemoteAccessError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn manual_recieve_handshake(app: AppHandle, token: String) -> Result<(), ()> {
|
pub async fn manual_recieve_handshake(app: AppHandle, token: String) {
|
||||||
recieve_handshake(app, format!("handshake/{token}")).await;
|
recieve_handshake(app, format!("handshake/{token}")).await;
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,26 @@
|
|||||||
use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder};
|
use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder, Response};
|
||||||
use log::warn;
|
use log::{debug, warn};
|
||||||
use tauri::UriSchemeResponder;
|
use tauri::UriSchemeResponder;
|
||||||
|
|
||||||
use crate::{database::db::DatabaseImpls, remote::utils::DROP_CLIENT_ASYNC, DB};
|
use crate::{database::db::DatabaseImpls, error::cache_error::CacheError, remote::utils::DROP_CLIENT_ASYNC, DB};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
auth::generate_authorization_header,
|
auth::generate_authorization_header,
|
||||||
cache::{ObjectCache, cache_object, get_cached_object},
|
cache::{ObjectCache, cache_object, get_cached_object},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeResponder) {
|
pub async fn fetch_object_wrapper(request: http::Request<Vec<u8>>, responder: UriSchemeResponder) {
|
||||||
|
match fetch_object(request).await {
|
||||||
|
Ok(r) => responder.respond(r),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Cache error: {e}");
|
||||||
|
responder.respond(Response::builder().status(500).body(Vec::new()).expect("Failed to build error response"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_object(request: http::Request<Vec<u8>>) -> Result<Response<Vec<u8>>, CacheError>
|
||||||
|
{
|
||||||
// Drop leading /
|
// Drop leading /
|
||||||
let object_id = &request.uri().path()[1..];
|
let object_id = &request.uri().path()[1..];
|
||||||
|
|
||||||
@ -17,8 +28,7 @@ pub async fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeR
|
|||||||
if let Ok(cache_result) = &cache_result
|
if let Ok(cache_result) = &cache_result
|
||||||
&& !cache_result.has_expired()
|
&& !cache_result.has_expired()
|
||||||
{
|
{
|
||||||
responder.respond(cache_result.into());
|
return cache_result.try_into();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let header = generate_authorization_header();
|
let header = generate_authorization_header();
|
||||||
@ -26,26 +36,40 @@ pub async fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeR
|
|||||||
let url = format!("{}api/v1/client/object/{object_id}", DB.fetch_base_url());
|
let url = format!("{}api/v1/client/object/{object_id}", DB.fetch_base_url());
|
||||||
let response = client.get(url).header("Authorization", header).send().await;
|
let response = client.get(url).header("Authorization", header).send().await;
|
||||||
|
|
||||||
if response.is_err() {
|
match response {
|
||||||
match cache_result {
|
Ok(r) => {
|
||||||
Ok(cache_result) => responder.respond(cache_result.into()),
|
let resp_builder = ResponseBuilder::new().header(
|
||||||
Err(e) => {
|
CONTENT_TYPE,
|
||||||
warn!("{e}");
|
r.headers()
|
||||||
|
.get("Content-Type")
|
||||||
|
.expect("Failed get Content-Type header"),
|
||||||
|
);
|
||||||
|
let data = match r.bytes().await {
|
||||||
|
Ok(data) => Vec::from(data),
|
||||||
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
|
"Could not get data from cache object {object_id} with error {e}",
|
||||||
|
);
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let resp = resp_builder.body(data).expect("Failed to build object cache response body");
|
||||||
|
if cache_result.map_or(true, |x| x.has_expired()) {
|
||||||
|
cache_object::<ObjectCache>(object_id, &resp.clone().try_into()?)
|
||||||
|
.expect("Failed to create cached object");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!("Object fetch failed with error {e}. Attempting to download from cache");
|
||||||
|
match cache_result {
|
||||||
|
Ok(cache_result) => cache_result.try_into(),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{e}");
|
||||||
|
Err(CacheError::Remote(e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
let response = response.unwrap();
|
|
||||||
|
|
||||||
let resp_builder = ResponseBuilder::new().header(
|
|
||||||
CONTENT_TYPE,
|
|
||||||
response.headers().get("Content-Type").unwrap(),
|
|
||||||
);
|
|
||||||
let data = Vec::from(response.bytes().await.unwrap());
|
|
||||||
let resp = resp_builder.body(data).unwrap();
|
|
||||||
if cache_result.is_err() || cache_result.unwrap().has_expired() {
|
|
||||||
cache_object::<ObjectCache>(object_id, &resp.clone().into()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
responder.respond(resp);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,57 +1,91 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use http::{uri::PathAndQuery, Request, Response, StatusCode, Uri};
|
use http::{uri::PathAndQuery, Request, Response, StatusCode, Uri};
|
||||||
|
use log::{error, warn};
|
||||||
use tauri::UriSchemeResponder;
|
use tauri::UriSchemeResponder;
|
||||||
|
|
||||||
use crate::{database::db::borrow_db_checked, remote::utils::DROP_CLIENT_SYNC};
|
use crate::{database::db::borrow_db_checked, remote::utils::DROP_CLIENT_SYNC, utils::webbrowser_open::webbrowser_open};
|
||||||
|
|
||||||
pub async fn handle_server_proto_offline(_request: Request<Vec<u8>>, responder: UriSchemeResponder) {
|
pub async fn handle_server_proto_offline_wrapper(request: Request<Vec<u8>>, responder: UriSchemeResponder) {
|
||||||
let four_oh_four = Response::builder()
|
responder.respond(match handle_server_proto_offline(request).await {
|
||||||
.status(StatusCode::NOT_FOUND)
|
Ok(res) => res,
|
||||||
.body(Vec::new())
|
Err(_) => unreachable!()
|
||||||
.unwrap();
|
});
|
||||||
responder.respond(four_oh_four);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_server_proto(request: Request<Vec<u8>>, responder: UriSchemeResponder) {
|
pub async fn handle_server_proto_offline(_request: Request<Vec<u8>>) -> Result<Response<Vec<u8>>, StatusCode>{
|
||||||
|
Ok(Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.body(Vec::new())
|
||||||
|
.expect("Failed to build error response for proto offline"))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_server_proto_wrapper(request: Request<Vec<u8>>, responder: UriSchemeResponder) {
|
||||||
|
match handle_server_proto(request).await {
|
||||||
|
Ok(r) => responder.respond(r),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Cache error: {e}");
|
||||||
|
responder.respond(Response::builder().status(e).body(Vec::new()).expect("Failed to build error response"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_server_proto(request: Request<Vec<u8>>) -> Result<Response<Vec<u8>>, StatusCode> {
|
||||||
let db_handle = borrow_db_checked();
|
let db_handle = borrow_db_checked();
|
||||||
let web_token = match &db_handle.auth.as_ref().unwrap().web_token {
|
let auth = match db_handle.auth.as_ref() {
|
||||||
Some(e) => e,
|
Some(auth) => auth,
|
||||||
None => return,
|
None => {
|
||||||
|
error!("Could not find auth in database");
|
||||||
|
return Err(StatusCode::UNAUTHORIZED)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let remote_uri = db_handle.base_url.parse::<Uri>().unwrap();
|
let web_token = match &auth.web_token {
|
||||||
|
Some(token) => token,
|
||||||
|
None => return Err(StatusCode::UNAUTHORIZED),
|
||||||
|
};
|
||||||
|
let remote_uri = db_handle.base_url.parse::<Uri>().expect("Failed to parse base url");
|
||||||
|
|
||||||
let path = request.uri().path();
|
let path = request.uri().path();
|
||||||
|
|
||||||
let mut new_uri = request.uri().clone().into_parts();
|
let mut new_uri = request.uri().clone().into_parts();
|
||||||
new_uri.path_and_query =
|
new_uri.path_and_query =
|
||||||
Some(PathAndQuery::from_str(&format!("{path}?noWrapper=true")).unwrap());
|
Some(PathAndQuery::from_str(&format!("{path}?noWrapper=true")).expect("Failed to parse request path in proto"));
|
||||||
new_uri.authority = remote_uri.authority().cloned();
|
new_uri.authority = remote_uri.authority().cloned();
|
||||||
new_uri.scheme = remote_uri.scheme().cloned();
|
new_uri.scheme = remote_uri.scheme().cloned();
|
||||||
let new_uri = Uri::from_parts(new_uri).unwrap();
|
let err_msg = &format!("Failed to build new uri from parts {new_uri:?}");
|
||||||
|
let new_uri = Uri::from_parts(new_uri).expect(err_msg);
|
||||||
|
|
||||||
let whitelist_prefix = ["/store", "/api", "/_", "/fonts"];
|
let whitelist_prefix = ["/store", "/api", "/_", "/fonts"];
|
||||||
|
|
||||||
if whitelist_prefix.iter().all(|f| !path.starts_with(f)) {
|
if whitelist_prefix.iter().all(|f| !path.starts_with(f)) {
|
||||||
webbrowser::open(&new_uri.to_string()).unwrap();
|
webbrowser_open(new_uri.to_string());
|
||||||
return;
|
return Ok(Response::new(Vec::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = DROP_CLIENT_SYNC.clone();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = client
|
let response = match client
|
||||||
.request(request.method().clone(), new_uri.to_string())
|
.request(request.method().clone(), new_uri.to_string())
|
||||||
.header("Authorization", format!("Bearer {web_token}"))
|
.header("Authorization", format!("Bearer {web_token}"))
|
||||||
.headers(request.headers().clone())
|
.headers(request.headers().clone())
|
||||||
.send()
|
.send() {
|
||||||
.unwrap();
|
Ok(response) => response,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Could not send response. Got {e} when sending");
|
||||||
|
return Err(e.status().unwrap_or(StatusCode::BAD_REQUEST))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let response_status = response.status();
|
let response_status = response.status();
|
||||||
let response_body = response.bytes().unwrap();
|
let response_body = match response.bytes() {
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
Err(e) => return Err(e.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)),
|
||||||
|
};
|
||||||
|
|
||||||
let http_response = Response::builder()
|
let http_response = Response::builder()
|
||||||
.status(response_status)
|
.status(response_status)
|
||||||
.body(response_body.to_vec())
|
.body(response_body.to_vec())
|
||||||
.unwrap();
|
.expect("Failed to build server proto response");
|
||||||
|
|
||||||
responder.respond(http_response);
|
Ok(http_response)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,9 +11,7 @@ use serde::Deserialize;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState, AppStatus,
|
database::db::{borrow_db_mut_checked, DATA_ROOT_DIR}, error::remote_access_error::RemoteAccessError, lock, AppState, AppStatus
|
||||||
database::db::{DATA_ROOT_DIR, borrow_db_mut_checked},
|
|
||||||
error::remote_access_error::RemoteAccessError,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -37,16 +35,41 @@ fn fetch_certificates() -> Vec<Certificate> {
|
|||||||
match entry {
|
match entry {
|
||||||
Ok(c) => {
|
Ok(c) => {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
File::open(c.path()).unwrap().read_to_end(&mut buf).unwrap();
|
match File::open(c.path()) {
|
||||||
|
Ok(f) => f,
|
||||||
for cert in Certificate::from_pem_bundle(&buf).unwrap() {
|
Err(e) => {
|
||||||
certs.push(cert);
|
warn!(
|
||||||
|
"Failed to open file at {} with error {}",
|
||||||
|
c.path().display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.read_to_end(&mut buf)
|
||||||
|
.unwrap_or_else(|e| panic!(
|
||||||
|
"Failed to read to end of certificate file {} with error {}",
|
||||||
|
c.path().display(),
|
||||||
|
e
|
||||||
|
));
|
||||||
|
|
||||||
|
match Certificate::from_pem_bundle(&buf) {
|
||||||
|
Ok(certificates) => {
|
||||||
|
for cert in certificates {
|
||||||
|
certs.push(cert);
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
"added {} certificate(s) from {}",
|
||||||
|
certs.len(),
|
||||||
|
c.file_name().display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => warn!(
|
||||||
|
"Invalid certificate file {} with error {}",
|
||||||
|
c.path().display(),
|
||||||
|
e
|
||||||
|
),
|
||||||
}
|
}
|
||||||
info!(
|
|
||||||
"added {} certificate(s) from {}",
|
|
||||||
certs.len(),
|
|
||||||
c.file_name().into_string().unwrap()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Err(_) => todo!(),
|
Err(_) => todo!(),
|
||||||
}
|
}
|
||||||
@ -65,7 +88,7 @@ pub fn get_client_sync() -> reqwest::blocking::Client {
|
|||||||
for cert in DROP_CERT_BUNDLE.iter() {
|
for cert in DROP_CERT_BUNDLE.iter() {
|
||||||
client = client.add_root_certificate(cert.clone());
|
client = client.add_root_certificate(cert.clone());
|
||||||
}
|
}
|
||||||
client.use_rustls_tls().build().unwrap()
|
client.use_rustls_tls().build().expect("Failed to build synchronous client")
|
||||||
}
|
}
|
||||||
pub fn get_client_async() -> reqwest::Client {
|
pub fn get_client_async() -> reqwest::Client {
|
||||||
let mut client = reqwest::ClientBuilder::new();
|
let mut client = reqwest::ClientBuilder::new();
|
||||||
@ -73,7 +96,7 @@ pub fn get_client_async() -> reqwest::Client {
|
|||||||
for cert in DROP_CERT_BUNDLE.iter() {
|
for cert in DROP_CERT_BUNDLE.iter() {
|
||||||
client = client.add_root_certificate(cert.clone());
|
client = client.add_root_certificate(cert.clone());
|
||||||
}
|
}
|
||||||
client.use_rustls_tls().build().unwrap()
|
client.use_rustls_tls().build().expect("Failed to build asynchronous client")
|
||||||
}
|
}
|
||||||
pub fn get_client_ws() -> reqwest::Client {
|
pub fn get_client_ws() -> reqwest::Client {
|
||||||
let mut client = reqwest::ClientBuilder::new();
|
let mut client = reqwest::ClientBuilder::new();
|
||||||
@ -81,7 +104,11 @@ pub fn get_client_ws() -> reqwest::Client {
|
|||||||
for cert in DROP_CERT_BUNDLE.iter() {
|
for cert in DROP_CERT_BUNDLE.iter() {
|
||||||
client = client.add_root_certificate(cert.clone());
|
client = client.add_root_certificate(cert.clone());
|
||||||
}
|
}
|
||||||
client.use_rustls_tls().http1_only().build().unwrap()
|
client
|
||||||
|
.use_rustls_tls()
|
||||||
|
.http1_only()
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build websocket client")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn use_remote_logic(
|
pub async fn use_remote_logic(
|
||||||
@ -107,7 +134,7 @@ pub async fn use_remote_logic(
|
|||||||
return Err(RemoteAccessError::InvalidEndpoint);
|
return Err(RemoteAccessError::InvalidEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut app_state = state.lock().unwrap();
|
let mut app_state = lock!(state);
|
||||||
app_state.status = AppStatus::SignedOut;
|
app_state.status = AppStatus::SignedOut;
|
||||||
drop(app_state);
|
drop(app_state);
|
||||||
|
|
||||||
|
|||||||
6
src-tauri/src/utils/app_emit.rs
Normal file
6
src-tauri/src/utils/app_emit.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! app_emit {
|
||||||
|
($app:expr, $event:expr, $p:expr) => {
|
||||||
|
$app.emit($event, $p).expect(&format!("Failed to emit event {}", $event));
|
||||||
|
};
|
||||||
|
}
|
||||||
6
src-tauri/src/utils/download_manager_send.rs
Normal file
6
src-tauri/src/utils/download_manager_send.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! send {
|
||||||
|
($download_manager:expr, $signal:expr) => {
|
||||||
|
$download_manager.send($signal).unwrap_or_else(|_| panic!("Failed to send signal {} to the download manager", stringify!(signal)))
|
||||||
|
};
|
||||||
|
}
|
||||||
6
src-tauri/src/utils/lock.rs
Normal file
6
src-tauri/src/utils/lock.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! lock {
|
||||||
|
($mutex:expr) => {
|
||||||
|
$mutex.lock().unwrap_or_else(|_| panic!("Failed to lock onto {}", stringify!($mutex)))
|
||||||
|
};
|
||||||
|
}
|
||||||
4
src-tauri/src/utils/mod.rs
Normal file
4
src-tauri/src/utils/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
mod app_emit;
|
||||||
|
mod download_manager_send;
|
||||||
|
mod lock;
|
||||||
|
pub mod webbrowser_open;
|
||||||
7
src-tauri/src/utils/webbrowser_open.rs
Normal file
7
src-tauri/src/utils/webbrowser_open.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use log::warn;
|
||||||
|
|
||||||
|
pub fn webbrowser_open<T: AsRef<str>>(url: T) {
|
||||||
|
if let Err(e) = webbrowser::open(url.as_ref()) {
|
||||||
|
warn!("Could not open web browser to url {} with error {}", url.as_ref(), e);
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user