Files
drop-app/src-tauri/remote/src/utils.rs
DecDuck 5ad005161f
Some checks failed
publish / publish-tauri (, ubuntu-22.04) (release) Failing after 14m38s
publish / publish-tauri (, windows-latest) (release) Has been cancelled
publish / publish-tauri (--target aarch64-apple-darwin, macos-14) (release) Has been cancelled
publish / publish-tauri (--target aarch64-unknown-linux-gnu, ubuntu-22.04-arm) (release) Has been cancelled
publish / publish-tauri (--target x86_64-apple-darwin, macos-14) (release) Has been cancelled
Depot API & executor launch (#173)
* feat: depot api downloads

* feat: frontend fixes and experimental webview store

* feat: sync downloader

* feat: cleanup and fixes

* feat: encrypted database and fixed resuming

* feat: launch option selector

* fix: autostart when no options

* fix: clippy

* fix: clippy x2

* feat: executor launch

* feat: executor launch

* feat: not installed error handling

* feat: better offline handling

* feat: dependency popup

* fix: cancelation and resuming issues

* feat: dedup by platform

* feat: new ui for additional components and fix dl manager clog

* feat: auto-queue dependencies

* feat: depot scanning and ranking

* feat: new library fetching stack

* In-app store page (Windows + macOS) (#176)

* feat: async store loading

* feat: fix overscroll behaviour

* fix: query params in server protocol

* fix: clippy
2026-01-20 11:40:48 +11:00

192 lines
6.8 KiB
Rust

use std::{
fs::{self, File},
io::Read,
sync::LazyLock,
time::Duration,
};
use client::{app_state::AppState, app_status::AppStatus};
use database::db::DATA_ROOT_DIR;
use http::Extensions;
use log::{debug, info, warn};
use reqwest::Certificate;
use reqwest_middleware::{
ClientBuilder, ClientWithMiddleware, Error, Middleware, Next, Result,
reqwest::{Request, Response},
};
use serde::Deserialize;
use tauri::{AppHandle, Emitter, Manager, async_runtime::Mutex};
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DropHealthcheck {
app_name: String,
}
impl DropHealthcheck {
pub fn app_name(&self) -> &String {
&self.app_name
}
}
static DROP_CERT_BUNDLE: LazyLock<Vec<Certificate>> = LazyLock::new(fetch_certificates);
pub static DROP_CLIENT_SYNC: LazyLock<reqwest::blocking::Client> = LazyLock::new(get_client_sync);
pub static DROP_CLIENT_ASYNC: LazyLock<ClientWithMiddleware> = LazyLock::new(get_client_async);
pub static DROP_CLIENT_WS_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(get_client_ws);
pub static DROP_APP_HANDLE: LazyLock<Mutex<Option<AppHandle>>> = LazyLock::new(|| Mutex::new(None));
struct AutoOfflineMiddleware;
#[async_trait::async_trait]
impl Middleware for AutoOfflineMiddleware {
async fn handle(
&self,
req: Request,
extensions: &mut Extensions,
next: Next<'_>,
) -> Result<Response> {
let res = next.run(req, extensions).await;
match res {
Ok(res) => {
tauri::async_runtime::spawn(async move {
let lock = DROP_APP_HANDLE.lock().await;
if let Some(app_handle) = &*lock {
let state = app_handle.state::<std::sync::nonpoison::Mutex<AppState>>();
let mut state_lock = state.lock();
if state_lock.status == AppStatus::Offline {
state_lock.status = AppStatus::SignedIn;
app_handle
.emit("update_state", &*state_lock)
.expect("failed to emit state update");
}
};
});
Ok(res)
}
Err(err) => match err {
Error::Middleware(error) => Err(Error::Middleware(error)),
Error::Reqwest(error) => {
if error.is_connect() {
// Spawn to defer this action - the state will most likely be locked
tauri::async_runtime::spawn(async move {
let lock = DROP_APP_HANDLE.lock().await;
if let Some(app_handle) = &*lock {
let state =
app_handle.state::<std::sync::nonpoison::Mutex<AppState>>();
let mut state_lock = state.lock();
state_lock.status = AppStatus::Offline;
app_handle
.emit("update_state", &*state_lock)
.expect("failed to emit state update");
};
});
};
Err(Error::Reqwest(error))
}
},
}
}
}
fn fetch_certificates() -> Vec<Certificate> {
let certificate_dir = DATA_ROOT_DIR.join("certificates");
let mut certs = Vec::new();
match fs::read_dir(certificate_dir) {
Ok(c) => {
for entry in c {
match entry {
Ok(c) => {
let mut buf = Vec::new();
match File::open(c.path()) {
Ok(f) => f,
Err(e) => {
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
),
}
}
Err(_) => todo!(),
}
}
}
Err(e) => {
debug!("not loading certificates due to error: {e}");
}
};
certs
}
pub fn get_client_sync() -> reqwest::blocking::Client {
let mut client = reqwest::blocking::ClientBuilder::new();
for cert in DROP_CERT_BUNDLE.iter() {
client = client.add_root_certificate(cert.clone());
}
client
.use_rustls_tls()
.user_agent("Drop Desktop Client")
.connect_timeout(Duration::from_millis(1500))
.build()
.expect("Failed to build synchronous client")
}
pub fn get_client_async() -> ClientWithMiddleware {
let mut client = reqwest::ClientBuilder::new();
for cert in DROP_CERT_BUNDLE.iter() {
client = client.add_root_certificate(cert.clone());
}
let normal_client = client
.use_rustls_tls()
.user_agent("Drop Desktop Client")
.build()
.expect("Failed to build asynchronous client");
ClientBuilder::new(normal_client)
.with(AutoOfflineMiddleware)
.build()
}
pub fn get_client_ws() -> reqwest::Client {
let mut client = reqwest::ClientBuilder::new();
for cert in DROP_CERT_BUNDLE.iter() {
client = client.add_root_certificate(cert.clone());
}
client
.use_rustls_tls()
.user_agent("Drop Desktop Client")
.http1_only()
.build()
.expect("Failed to build websocket client")
}