Compare commits
3 Commits
5e1f4b18c2
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b3e713657 | ||
|
|
c2bf6e6fd5 | ||
|
|
94bc7cb91d |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -330,7 +330,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shared",
|
||||
"sqlx",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -559,10 +558,9 @@ checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
[[package]]
|
||||
name = "coarsetime"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e58eb270476aa4fc7843849f8a35063e8743b4dbcdf6dd0f8ea0886980c204c2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"portable-atomic",
|
||||
"wasix",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -3652,12 +3650,14 @@ dependencies = [
|
||||
name = "shared"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"leptos",
|
||||
"leptos_axum",
|
||||
"leptos_router",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"utoipa",
|
||||
|
||||
@@ -16,3 +16,6 @@ strip = true
|
||||
# Artık (incremental) build'i kapat ki optimizasyon tam olsun
|
||||
incremental = false
|
||||
|
||||
[patch.crates-io]
|
||||
coarsetime = { path = "patches/coarsetime" }
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ utoipa-swagger-ui = { version = "9.0", features = ["axum"], optional = true }
|
||||
web-push = { version = "0.10", default-features = false, features = ["hyper-client"], optional = true }
|
||||
base64 = "0.22"
|
||||
openssl = { version = "0.10", features = ["vendored"], optional = true }
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
||||
bcrypt = "0.17.0"
|
||||
axum-extra = { version = "0.10", features = ["cookie"] }
|
||||
rand = "0.8"
|
||||
|
||||
@@ -49,21 +49,3 @@ pub async fn handle_timeout_error(err: BoxError) -> (StatusCode, &'static str) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "push-notifications")]
|
||||
pub async fn get_push_public_key_handler(
|
||||
axum::extract::State(state): axum::extract::State<crate::AppState>,
|
||||
) -> impl IntoResponse {
|
||||
let public_key = state.push_store.get_public_key();
|
||||
(StatusCode::OK, axum::extract::Json(serde_json::json!({ "publicKey": public_key }))).into_response()
|
||||
}
|
||||
|
||||
#[cfg(feature = "push-notifications")]
|
||||
pub async fn subscribe_push_handler(
|
||||
axum::extract::State(state): axum::extract::State<crate::AppState>,
|
||||
axum::extract::Json(subscription): axum::extract::Json<crate::push::PushSubscription>,
|
||||
) -> impl IntoResponse {
|
||||
tracing::info!("Received push subscription: {:?}", subscription);
|
||||
state.push_store.add_subscription(subscription).await;
|
||||
(StatusCode::OK, "Subscription saved").into_response()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mod db;
|
||||
mod diff;
|
||||
mod handlers;
|
||||
#[cfg(feature = "push-notifications")]
|
||||
@@ -42,7 +41,7 @@ pub struct AppState {
|
||||
pub tx: Arc<watch::Sender<Vec<Torrent>>>,
|
||||
pub event_bus: broadcast::Sender<AppEvent>,
|
||||
pub scgi_socket_path: String,
|
||||
pub db: db::Db,
|
||||
pub db: shared::db::Db,
|
||||
#[cfg(feature = "push-notifications")]
|
||||
pub push_store: push::PushSubscriptionStore,
|
||||
pub notify_poll: Arc<tokio::sync::Notify>,
|
||||
@@ -103,46 +102,6 @@ struct Args {
|
||||
}
|
||||
|
||||
#[cfg(feature = "swagger")]
|
||||
#[cfg(feature = "push-notifications")]
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
handlers::get_push_public_key_handler,
|
||||
handlers::subscribe_push_handler,
|
||||
handlers::auth::login_handler,
|
||||
handlers::auth::logout_handler,
|
||||
handlers::auth::check_auth_handler,
|
||||
handlers::setup::setup_handler,
|
||||
handlers::setup::get_setup_status_handler
|
||||
),
|
||||
components(
|
||||
schemas(
|
||||
shared::AddTorrentRequest,
|
||||
shared::TorrentActionRequest,
|
||||
shared::Torrent,
|
||||
shared::TorrentStatus,
|
||||
shared::TorrentFile,
|
||||
shared::TorrentPeer,
|
||||
shared::TorrentTracker,
|
||||
shared::SetFilePriorityRequest,
|
||||
shared::SetLabelRequest,
|
||||
shared::GlobalLimitRequest,
|
||||
push::PushSubscription,
|
||||
push::PushKeys,
|
||||
handlers::auth::LoginRequest,
|
||||
handlers::setup::SetupRequest,
|
||||
handlers::setup::SetupStatusResponse,
|
||||
handlers::auth::UserResponse
|
||||
)
|
||||
),
|
||||
tags(
|
||||
(name = "vibetorrent", description = "VibeTorrent API")
|
||||
)
|
||||
)]
|
||||
struct ApiDoc;
|
||||
|
||||
#[cfg(feature = "swagger")]
|
||||
#[cfg(not(feature = "push-notifications"))]
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
@@ -206,7 +165,7 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
let db: db::Db = match db::Db::new(&args.db_url).await {
|
||||
let db: shared::db::Db = match shared::db::Db::new(&args.db_url).await {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to connect to database: {}", e);
|
||||
@@ -470,6 +429,7 @@ async fn main() {
|
||||
|
||||
// Setup & Auth Routes (cookie-based, stay as REST)
|
||||
let scgi_path_for_ctx = args.socket.clone();
|
||||
let db_for_ctx = db.clone();
|
||||
let app = app
|
||||
.route("/api/setup/status", get(handlers::setup::get_setup_status_handler))
|
||||
.route("/api/setup", post(handlers::setup::setup_handler))
|
||||
@@ -484,12 +444,18 @@ async fn main() {
|
||||
.route("/api/events", get(sse::sse_handler))
|
||||
.route("/api/server_fns/{*fn_name}", post({
|
||||
let scgi_path = scgi_path_for_ctx.clone();
|
||||
let db = db_for_ctx.clone();
|
||||
move |req: Request<Body>| {
|
||||
let scgi_path = scgi_path.clone();
|
||||
let db = db.clone();
|
||||
leptos_axum::handle_server_fns_with_context(
|
||||
move || {
|
||||
leptos::context::provide_context(shared::ServerContext {
|
||||
scgi_socket_path: scgi_path.clone(),
|
||||
});
|
||||
leptos::context::provide_context(shared::DbContext {
|
||||
db: db.clone(),
|
||||
});
|
||||
},
|
||||
req,
|
||||
)
|
||||
@@ -497,11 +463,6 @@ async fn main() {
|
||||
}))
|
||||
.fallback(handlers::static_handler);
|
||||
|
||||
#[cfg(feature = "push-notifications")]
|
||||
let app = app
|
||||
.route("/api/push/public-key", get(handlers::get_push_public_key_handler))
|
||||
.route("/api/push/subscribe", post(handlers::subscribe_push_handler));
|
||||
|
||||
let app = app
|
||||
.layer(middleware::from_fn_with_state(app_state.clone(), auth_middleware))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
|
||||
@@ -7,7 +7,7 @@ use web_push::{
|
||||
};
|
||||
use futures::StreamExt;
|
||||
|
||||
use crate::db::Db;
|
||||
use shared::db::Db;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct PushSubscription {
|
||||
|
||||
@@ -142,25 +142,21 @@ pub mod settings {
|
||||
|
||||
pub mod push {
|
||||
use super::*;
|
||||
use crate::store::PushSubscriptionData;
|
||||
|
||||
pub async fn get_public_key() -> Result<String, ApiError> {
|
||||
let resp = Request::get(&format!("{}/push/public-key", base_url()))
|
||||
.send()
|
||||
shared::server_fns::push::get_public_key()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
let key = resp.text().await.map_err(|_| ApiError::Network)?;
|
||||
Ok(key)
|
||||
.map_err(|e| ApiError::ServerFn(e.to_string()))
|
||||
}
|
||||
|
||||
pub async fn subscribe(req: &PushSubscriptionData) -> Result<(), ApiError> {
|
||||
Request::post(&format!("{}/push/subscribe", base_url()))
|
||||
.json(req)
|
||||
.map_err(|_| ApiError::Network)?
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| ApiError::Network)?;
|
||||
Ok(())
|
||||
pub async fn subscribe(endpoint: &str, p256dh: &str, auth: &str) -> Result<(), ApiError> {
|
||||
shared::server_fns::push::subscribe_push(
|
||||
endpoint.to_string(),
|
||||
p256dh.to_string(),
|
||||
auth.to_string(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ApiError::ServerFn(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,18 +55,6 @@ pub fn get_action_messages(action: &str) -> (&'static str, &'static str) {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PushSubscriptionData {
|
||||
pub endpoint: String,
|
||||
pub keys: PushKeys,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PushKeys {
|
||||
pub p256dh: String,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum FilterStatus {
|
||||
All, Downloading, Seeding, Completed, Paused, Inactive, Active, Error,
|
||||
@@ -95,7 +83,6 @@ pub fn provide_torrent_store() {
|
||||
let store = TorrentStore { torrents, filter, search_query, global_stats, notifications, user };
|
||||
provide_context(store);
|
||||
|
||||
let user_for_sse = user;
|
||||
let notifications_for_sse = notifications;
|
||||
let global_stats_for_sse = global_stats;
|
||||
let torrents_for_sse = torrents;
|
||||
@@ -107,13 +94,6 @@ pub fn provide_torrent_store() {
|
||||
let mut disconnect_notified = false;
|
||||
|
||||
loop {
|
||||
let user_val = user_for_sse.get();
|
||||
log::debug!("SSE: user = {:?}", user_val);
|
||||
if user_val.is_none() {
|
||||
log::debug!("SSE: User not authenticated, waiting...");
|
||||
gloo_timers::future::TimeoutFuture::new(1000).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
log::debug!("SSE: Creating EventSource...");
|
||||
let es_result = EventSource::new("/api/events");
|
||||
|
||||
30
patches/coarsetime/Cargo.toml
Normal file
30
patches/coarsetime/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "coarsetime"
|
||||
version = "0.1.37"
|
||||
description = "Time and duration crate optimized for speed (patched for MIPS)"
|
||||
license = "BSD-2-Clause"
|
||||
|
||||
[features]
|
||||
wasi-abi2 = ["dep:wasi-abi2"]
|
||||
|
||||
[lib]
|
||||
name = "coarsetime"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
portable-atomic = { version = "1", default-features = false, features = ["fallback"] }
|
||||
|
||||
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies.wasm-bindgen]
|
||||
version = "0.2"
|
||||
|
||||
[target.'cfg(any(target_os = "wasix", target_os = "wasi"))'.dependencies.wasix]
|
||||
version = "0.13"
|
||||
|
||||
[target.'cfg(not(any(target_os = "wasix", target_os = "wasi")))'.dependencies.libc]
|
||||
version = "0.2"
|
||||
|
||||
[target.'cfg(target_os = "wasi")'.dependencies.wasi-abi2]
|
||||
version = "0.14.7"
|
||||
optional = true
|
||||
package = "wasi"
|
||||
90
patches/coarsetime/src/clock.rs
Normal file
90
patches/coarsetime/src/clock.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
#[cfg(not(all(
|
||||
any(target_arch = "wasm32", target_arch = "wasm64"),
|
||||
target_os = "unknown"
|
||||
)))]
|
||||
use std::time;
|
||||
|
||||
use portable_atomic::{AtomicU64, Ordering};
|
||||
|
||||
use super::Duration;
|
||||
|
||||
static RECENT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "wasm32", target_arch = "wasm64"),
|
||||
target_os = "unknown"
|
||||
))]
|
||||
mod js_imports {
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
pub type Date;
|
||||
|
||||
#[wasm_bindgen(static_method_of = Date)]
|
||||
pub fn now() -> f64;
|
||||
}
|
||||
}
|
||||
|
||||
/// System time
|
||||
#[derive(Debug)]
|
||||
pub struct Clock;
|
||||
|
||||
/// Alias for `Duration`.
|
||||
pub type UnixTimeStamp = Duration;
|
||||
|
||||
impl Clock {
|
||||
/// Returns the elapsed time since the UNIX epoch
|
||||
#[inline]
|
||||
pub fn now_since_epoch() -> UnixTimeStamp {
|
||||
Duration::from_u64(unix_ts())
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since the UNIX epoch, based on the latest
|
||||
/// explicit time update
|
||||
#[inline]
|
||||
pub fn recent_since_epoch() -> UnixTimeStamp {
|
||||
Duration::from_u64(RECENT.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Updates the cached system time.
|
||||
///
|
||||
/// This function should be called frequently, for example in an event loop
|
||||
/// or using an `Updater` task.
|
||||
#[inline]
|
||||
pub fn update() {
|
||||
let now = unix_ts();
|
||||
RECENT.store(now, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Sets the cached system time to the specified timestamp.
|
||||
/// This function is intended for testing purposes only.
|
||||
/// It should not be used in production code.
|
||||
pub fn set_recent_since_epoch(recent: UnixTimeStamp) {
|
||||
RECENT.store(recent.as_u64(), Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "wasm32", target_arch = "wasm64"),
|
||||
target_os = "unknown"
|
||||
))]
|
||||
#[inline]
|
||||
fn unix_ts() -> u64 {
|
||||
let unix_ts_now_sys = (js_imports::Date::now() / 1000.0).round() as u64;
|
||||
let unix_ts_now = Duration::from_secs(unix_ts_now_sys);
|
||||
unix_ts_now.as_u64()
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
any(target_arch = "wasm32", target_arch = "wasm64"),
|
||||
target_os = "unknown"
|
||||
)))]
|
||||
#[inline]
|
||||
fn unix_ts() -> u64 {
|
||||
let unix_ts_now_sys = time::SystemTime::now()
|
||||
.duration_since(time::UNIX_EPOCH)
|
||||
.expect("The system clock is not properly set");
|
||||
let unix_ts_now = Duration::from(unix_ts_now_sys);
|
||||
unix_ts_now.as_u64()
|
||||
}
|
||||
270
patches/coarsetime/src/duration.rs
Normal file
270
patches/coarsetime/src/duration.rs
Normal file
@@ -0,0 +1,270 @@
|
||||
use std::convert::From;
|
||||
use std::ops::*;
|
||||
use std::time;
|
||||
|
||||
use super::helpers::*;
|
||||
|
||||
/// A duration type to represent an approximate span of time
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, Eq, PartialOrd, PartialEq, Default)]
|
||||
pub struct Duration(u64);
|
||||
|
||||
impl Duration {
|
||||
/// Creates a new `Duration` from the specified number of seconds and
|
||||
/// additional nanosecond precision
|
||||
#[inline]
|
||||
pub const fn new(sec: u64, nanos: u32) -> Duration {
|
||||
Duration(_timespec_to_u64(sec, nanos))
|
||||
}
|
||||
|
||||
/// Creates a new Duration from the specified number of days
|
||||
#[inline]
|
||||
pub const fn from_days(days: u64) -> Duration {
|
||||
Duration(_sec_to_u64(days * 86400))
|
||||
}
|
||||
|
||||
/// Creates a new Duration from the specified number of hours
|
||||
#[inline]
|
||||
pub const fn from_hours(hours: u64) -> Duration {
|
||||
Duration(_sec_to_u64(hours * 3600))
|
||||
}
|
||||
|
||||
/// Creates a new Duration from the specified number of minutes
|
||||
#[inline]
|
||||
pub const fn from_mins(mins: u64) -> Duration {
|
||||
Duration(_sec_to_u64(mins * 60))
|
||||
}
|
||||
|
||||
/// Creates a new Duration from the specified number of seconds
|
||||
#[inline]
|
||||
pub const fn from_secs(secs: u64) -> Duration {
|
||||
Duration(_sec_to_u64(secs))
|
||||
}
|
||||
|
||||
/// Creates a new Duration from the specified number of milliseconds
|
||||
#[inline]
|
||||
pub const fn from_millis(millis: u64) -> Duration {
|
||||
Duration(_millis_to_u64(millis))
|
||||
}
|
||||
|
||||
/// Returns the number of days represented by this duration
|
||||
#[inline]
|
||||
pub const fn as_days(&self) -> u64 {
|
||||
self.as_secs() / 86400
|
||||
}
|
||||
|
||||
/// Returns the number of hours represented by this duration
|
||||
#[inline]
|
||||
pub const fn as_hours(&self) -> u64 {
|
||||
self.as_secs() / 3600
|
||||
}
|
||||
|
||||
/// Returns the number of minutes represented by this duration
|
||||
#[inline]
|
||||
pub const fn as_mins(&self) -> u64 {
|
||||
self.as_secs() / 60
|
||||
}
|
||||
|
||||
/// Returns the number of whole seconds represented by this duration
|
||||
#[inline]
|
||||
pub const fn as_secs(&self) -> u64 {
|
||||
self.0 >> 32
|
||||
}
|
||||
|
||||
/// Returns the number of whole milliseconds represented by this duration
|
||||
#[inline]
|
||||
pub const fn as_millis(&self) -> u64 {
|
||||
((self.0 as u128 * 125) >> 29) as u64
|
||||
}
|
||||
|
||||
/// Returns the number of whole microseconds represented by this duration
|
||||
#[inline]
|
||||
pub const fn as_micros(&self) -> u64 {
|
||||
((self.0 as u128 * 125_000) >> 29) as u64
|
||||
}
|
||||
|
||||
/// Returns the number of whole nanoseconds represented by this duration
|
||||
#[inline]
|
||||
pub const fn as_nanos(&self) -> u64 {
|
||||
((self.0 as u128 * 125_000_000) >> 29) as u64
|
||||
}
|
||||
|
||||
/// Returns the nanosecond precision represented by this duration
|
||||
#[inline]
|
||||
pub const fn subsec_nanos(&self) -> u32 {
|
||||
((self.0 as u32 as u64 * 125_000_000) >> 29) as u32
|
||||
}
|
||||
|
||||
/// Return this duration as a number of "ticks".
|
||||
///
|
||||
/// Note that length of a 'tick' is not guaranteed to represent
|
||||
/// the same amount of time across different platforms, or from
|
||||
/// one version of `coarsetime` to another.
|
||||
#[inline]
|
||||
pub const fn as_ticks(&self) -> u64 {
|
||||
self.as_u64()
|
||||
}
|
||||
|
||||
/// Creates a new Duration from the specified number of "ticks".
|
||||
///
|
||||
/// Note that length of a 'tick' is not guaranteed to represent
|
||||
/// the same amount of time across different platforms, or from
|
||||
/// one version of `coarsetime` to another.
|
||||
#[inline]
|
||||
pub const fn from_ticks(ticks: u64) -> Duration {
|
||||
Self::from_u64(ticks)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub const fn as_u64(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub const fn from_u64(ts: u64) -> Duration {
|
||||
Duration(ts)
|
||||
}
|
||||
|
||||
/// Returns the duration as a floating point number, representing the number
|
||||
/// of seconds
|
||||
#[inline]
|
||||
pub fn as_f64(&self) -> f64 {
|
||||
(self.0 as f64) / ((1u64 << 32) as f64)
|
||||
}
|
||||
|
||||
/// Returns the absolute difference between two `Duration`s
|
||||
#[inline]
|
||||
pub const fn abs_diff(&self, other: Duration) -> Duration {
|
||||
Duration(self.0.abs_diff(other.0))
|
||||
}
|
||||
|
||||
/// Add two durations, saturating on overflow
|
||||
#[inline]
|
||||
pub const fn saturating_add(self, rhs: Duration) -> Duration {
|
||||
Duration(self.0.saturating_add(rhs.0))
|
||||
}
|
||||
|
||||
/// Add two durations, returning `None` on overflow
|
||||
#[inline]
|
||||
pub fn checked_add(self, rhs: Duration) -> Option<Duration> {
|
||||
self.0.checked_add(rhs.0).map(Duration)
|
||||
}
|
||||
|
||||
/// Subtract two durations, saturating on underflow/overflow
|
||||
#[inline]
|
||||
pub const fn saturating_sub(self, rhs: Duration) -> Duration {
|
||||
Duration(self.0.saturating_sub(rhs.0))
|
||||
}
|
||||
|
||||
/// Subtract two durations, returning `None` on underflow/overflow
|
||||
#[inline]
|
||||
pub fn checked_sub(self, rhs: Duration) -> Option<Duration> {
|
||||
self.0.checked_sub(rhs.0).map(Duration)
|
||||
}
|
||||
|
||||
/// Multiply a duration by a scalar, saturating on overflow
|
||||
#[inline]
|
||||
pub const fn saturating_mul(self, rhs: u32) -> Duration {
|
||||
Duration(self.0.saturating_mul(rhs as u64))
|
||||
}
|
||||
|
||||
/// Multiply a duration by a scalar, returning `None` on overflow
|
||||
#[inline]
|
||||
pub fn checked_mul(self, rhs: u32) -> Option<Duration> {
|
||||
self.0.checked_mul(rhs as u64).map(Duration)
|
||||
}
|
||||
|
||||
/// Divide a duration by a scalar, returning `None` for division by zero
|
||||
#[inline]
|
||||
pub fn checked_div(self, rhs: u32) -> Option<Duration> {
|
||||
self.0.checked_div(rhs as u64).map(Duration)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl From<u64> for Duration {
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
fn from(ts: u64) -> Duration {
|
||||
Duration::from_u64(ts)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Duration) -> Duration {
|
||||
Duration(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Duration {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: Duration) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Duration) -> Duration {
|
||||
Duration(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Duration {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: Duration) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<u32> for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: u32) -> Duration {
|
||||
Duration(self.0 * rhs as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<u32> for Duration {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, rhs: u32) {
|
||||
*self = *self * rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<u32> for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
#[inline]
|
||||
fn div(self, rhs: u32) -> Duration {
|
||||
Duration(self.0 / rhs as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<u32> for Duration {
|
||||
#[inline]
|
||||
fn div_assign(&mut self, rhs: u32) {
|
||||
*self = *self / rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Duration> for time::Duration {
|
||||
#[inline]
|
||||
fn from(duration: Duration) -> time::Duration {
|
||||
time::Duration::new(duration.as_secs(), duration.subsec_nanos())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<time::Duration> for Duration {
|
||||
#[inline]
|
||||
fn from(duration_sys: time::Duration) -> Duration {
|
||||
Duration::new(duration_sys.as_secs(), duration_sys.subsec_nanos())
|
||||
}
|
||||
}
|
||||
26
patches/coarsetime/src/helpers.rs
Normal file
26
patches/coarsetime/src/helpers.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
#[inline]
|
||||
pub const fn _sec_to_u64(sec: u64) -> u64 {
|
||||
sec.saturating_mul(1 << 32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn _millis_to_u64(millis: u64) -> u64 {
|
||||
let secs = millis / 1_000;
|
||||
secs.saturating_mul(1 << 32) | ((millis - secs * 1_000) << 22)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn _nsecs_to_u64(nsecs: u64) -> u64 {
|
||||
let secs = nsecs / 1_000_000_000;
|
||||
_timespec_to_u64(secs, (nsecs - secs * 1_000_000_000) as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn _timespec_to_u64(tp_sec: u64, tp_nsec: u32) -> u64 {
|
||||
tp_sec.saturating_mul(1 << 32) | ((tp_nsec as u64 * 9_223_372_037) >> 31)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn _timeval_to_u64(tv_sec: u64, tv_usec: u32) -> u64 {
|
||||
tv_sec.saturating_mul(1 << 32) | ((tv_usec as u64 * 9_223_372_036_855) >> 31)
|
||||
}
|
||||
332
patches/coarsetime/src/instant.rs
Normal file
332
patches/coarsetime/src/instant.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
#[allow(unused_imports)]
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::*;
|
||||
#[allow(unused_imports)]
|
||||
use std::ptr::*;
|
||||
use portable_atomic::{AtomicU64, Ordering};
|
||||
|
||||
use super::duration::*;
|
||||
#[allow(unused_imports)]
|
||||
use super::helpers::*;
|
||||
|
||||
/// A measurement of a *monotonically* increasing clock.
|
||||
/// Opaque and useful only with `Duration`.
|
||||
///
|
||||
/// Resulting durations are actual durations; they do not get affected by
|
||||
/// clock adjustments, leap seconds, or similar.
|
||||
/// In order to get a measurement of the *wall clock*, use `Date` instead.
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, Eq, PartialOrd, PartialEq)]
|
||||
pub struct Instant(u64);
|
||||
|
||||
static RECENT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[cfg(windows)]
|
||||
extern "system" {
|
||||
pub fn GetTickCount64() -> libc::c_ulonglong;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||
#[allow(non_camel_case_types)]
|
||||
type clockid_t = libc::c_int;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
const CLOCK_MONOTONIC_RAW_APPROX: clockid_t = 5;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
extern "system" {
|
||||
fn clock_gettime_nsec_np(clk_id: clockid_t) -> u64;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
const CLOCK_MONOTONIC_FAST: clockid_t = 12;
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "wasm32", target_arch = "wasm64"),
|
||||
target_os = "unknown"
|
||||
))]
|
||||
mod js_imports {
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type performance;
|
||||
|
||||
#[wasm_bindgen(static_method_of = performance)]
|
||||
pub fn now() -> f64;
|
||||
}
|
||||
}
|
||||
|
||||
impl Instant {
|
||||
/// Returns an instant corresponding to "now"
|
||||
///
|
||||
/// This function also updates the stored instant.
|
||||
pub fn now() -> Instant {
|
||||
let now = Self::_now();
|
||||
Self::_update(now);
|
||||
Instant(now)
|
||||
}
|
||||
|
||||
/// Returns an instant corresponding to "now" without updating the cached value.
|
||||
/// After this, `recent()` will still return the old instant.
|
||||
///
|
||||
/// `now()` is generally preferred over this function.
|
||||
pub fn now_without_cache_update() -> Instant {
|
||||
let now = Self::_now();
|
||||
Instant(now)
|
||||
}
|
||||
|
||||
/// Returns an instant corresponding to the latest update
|
||||
pub fn recent() -> Instant {
|
||||
match Self::_recent() {
|
||||
0 => Instant::now(),
|
||||
recent => Instant(recent),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the stored instant
|
||||
///
|
||||
/// This function should be called frequently, for example in an event loop
|
||||
/// or using an `Updater` task.
|
||||
pub fn update() {
|
||||
let now = Self::_now();
|
||||
Self::_update(now);
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one
|
||||
#[inline]
|
||||
pub fn duration_since(&self, earlier: Instant) -> Duration {
|
||||
*self - earlier
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed between the this instant was created
|
||||
/// and the latest update
|
||||
#[inline]
|
||||
pub fn elapsed_since_recent(&self) -> Duration {
|
||||
Self::recent() - *self
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed since this instant was created
|
||||
///
|
||||
/// This function also updates the stored instant.
|
||||
#[inline]
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
Self::now() - *self
|
||||
}
|
||||
|
||||
/// Return a representation of this instant as a number of "ticks".
|
||||
///
|
||||
/// Note that length of a 'tick' is not guaranteed to represent
|
||||
/// the same amount of time across different platforms, or from
|
||||
/// one version of `coarsetime` to another.
|
||||
///
|
||||
/// Note also that the instant represented by "0" ticks is
|
||||
/// unspecified. It is not guaranteed to be the same time across
|
||||
/// different platforms, or from one version of `coarsetime` to
|
||||
/// another.
|
||||
///
|
||||
/// This API is mainly intended for applications that need to
|
||||
/// store the value of an `Instant` in an
|
||||
/// [`AtomicU64`](std::sync::atomic::AtomicU64).
|
||||
#[inline]
|
||||
pub const fn as_ticks(&self) -> u64 {
|
||||
self.as_u64()
|
||||
}
|
||||
|
||||
/// Create an `Instant` from a number of "ticks".
|
||||
///
|
||||
/// Note that length of a 'tick' is not guaranteed to represent
|
||||
/// the same amount of time across different platforms, or from
|
||||
/// one version of `coarsetime` to another.
|
||||
///
|
||||
/// Note also that the instant represented by "0" ticks is
|
||||
/// unspecified. It is not guaranteed to be the same time across
|
||||
/// different platforms, or from one version of `coarsetime` to
|
||||
/// another.
|
||||
#[inline]
|
||||
pub const fn from_ticks(ticks: u64) -> Instant {
|
||||
Self::from_u64(ticks)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub const fn as_u64(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub const fn from_u64(ts: u64) -> Instant {
|
||||
Instant(ts)
|
||||
}
|
||||
|
||||
/// Calculate an `Instant` that is a `Duration` later, saturating on overflow
|
||||
#[inline]
|
||||
pub const fn saturating_add(self, rhs: Duration) -> Instant {
|
||||
Instant(self.0.saturating_add(rhs.as_u64()))
|
||||
}
|
||||
|
||||
/// Calculate an `Instant` that is a `Duration` later, returning `None` on overflow
|
||||
#[inline]
|
||||
pub fn checked_add(self, rhs: Duration) -> Option<Instant> {
|
||||
self.0.checked_add(rhs.as_u64()).map(Instant)
|
||||
}
|
||||
|
||||
/// Calculate an `Instant` that is a `Duration` earlier, saturating on underflow
|
||||
#[inline]
|
||||
pub const fn saturating_sub(self, rhs: Duration) -> Instant {
|
||||
Instant(self.0.saturating_sub(rhs.as_u64()))
|
||||
}
|
||||
|
||||
/// Calculate an `Instant` that is a `Duration` earlier, returning `None` on underflow
|
||||
#[inline]
|
||||
pub fn checked_sub(self, rhs: Duration) -> Option<Instant> {
|
||||
self.0.checked_sub(rhs.as_u64()).map(Instant)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn _now() -> u64 {
|
||||
let mut tp = MaybeUninit::<libc::timespec>::uninit();
|
||||
let tp = unsafe {
|
||||
libc::clock_gettime(libc::CLOCK_MONOTONIC_COARSE, tp.as_mut_ptr());
|
||||
tp.assume_init()
|
||||
};
|
||||
_timespec_to_u64(tp.tv_sec as u64, tp.tv_nsec as u32)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn _now() -> u64 {
|
||||
let nsec = unsafe { clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW_APPROX) };
|
||||
_nsecs_to_u64(nsec)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
|
||||
fn _now() -> u64 {
|
||||
let mut tp = MaybeUninit::<libc::timespec>::uninit();
|
||||
let tp = unsafe {
|
||||
libc::clock_gettime(libc::CLOCK_MONOTONIC_FAST, tp.as_mut_ptr());
|
||||
tp.assume_init()
|
||||
};
|
||||
_timespec_to_u64(tp.tv_sec as u64, tp.tv_nsec as u32)
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(any(
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly"
|
||||
))
|
||||
))]
|
||||
fn _now() -> u64 {
|
||||
let mut tv = MaybeUninit::<libc::timeval>::uninit();
|
||||
let tv = unsafe {
|
||||
libc::gettimeofday(tv.as_mut_ptr(), null_mut());
|
||||
tv.assume_init()
|
||||
};
|
||||
_timeval_to_u64(tv.tv_sec as u64, tv.tv_usec as u32)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn _now() -> u64 {
|
||||
let tc = unsafe { GetTickCount64() } as u64;
|
||||
_millis_to_u64(tc)
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "wasi", not(feature = "wasi-abi2")))]
|
||||
fn _now() -> u64 {
|
||||
use wasix::{clock_time_get, CLOCKID_MONOTONIC, CLOCKID_REALTIME};
|
||||
let nsec = unsafe { clock_time_get(CLOCKID_MONOTONIC, 1_000_000) }
|
||||
.or_else(|_| unsafe { clock_time_get(CLOCKID_REALTIME, 1_000_000) })
|
||||
.expect("Clock not available");
|
||||
_nsecs_to_u64(nsec)
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "wasi", feature = "wasi-abi2"))]
|
||||
fn _now() -> u64 {
|
||||
let nsec = wasi_abi2::clocks::monotonic_clock::now();
|
||||
_nsecs_to_u64(nsec)
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "wasm32", target_arch = "wasm64"),
|
||||
target_os = "unknown"
|
||||
))]
|
||||
fn _now() -> u64 {
|
||||
_millis_to_u64(js_imports::performance::now() as u64)
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", target_env = "sgx", target_vendor = "fortanix"))]
|
||||
fn _now() -> u64 {
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
timestamp.as_secs() * 1_000_000_000 + (timestamp.subsec_nanos() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn _update(now: u64) {
|
||||
RECENT.store(now, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn _recent() -> u64 {
|
||||
let recent = RECENT.load(Ordering::Relaxed);
|
||||
if recent != 0 {
|
||||
recent
|
||||
} else {
|
||||
let now = Self::_now();
|
||||
Self::_update(now);
|
||||
Self::_recent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Instant {
|
||||
fn default() -> Instant {
|
||||
Self::now()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Instant> for Instant {
|
||||
type Output = Duration;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, other: Instant) -> Duration {
|
||||
Duration::from_u64(self.0.saturating_sub(other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Duration) -> Instant {
|
||||
Instant(self.0 - rhs.as_u64())
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Duration> for Instant {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: Duration) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Duration) -> Instant {
|
||||
Instant(self.0 + rhs.as_u64())
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Duration> for Instant {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: Duration) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
37
patches/coarsetime/src/lib.rs
Normal file
37
patches/coarsetime/src/lib.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
//! A crate to make time measurements that focuses on speed.
|
||||
//!
|
||||
//! This crate is a partial replacement for the `Time` and `Duration` structures
|
||||
//! from the standard library, with the following differences:
|
||||
//!
|
||||
//! * Speed is privileged over accuracy. In particular, `CLOCK_MONOTONIC_COARSE`
|
||||
//! is used to retrieve the clock value on Linux systems, and transformations avoid
|
||||
//! operations that can be slow on non-Intel systems.
|
||||
//! * The number of system calls can be kept to a minimum. The "most recent
|
||||
//! timestamp" is always kept in memory.
|
||||
//! It can be read with just a load operation, and can be
|
||||
//! updated only as frequently as necessary.
|
||||
//!
|
||||
//! # Installation
|
||||
//!
|
||||
//! `coarsetime` is available on [crates.io](https://crates.io/crates/coarsetime) and works on
|
||||
//! Rust stable, beta, and nightly.
|
||||
//!
|
||||
//! Windows and Unix-like systems are supported.
|
||||
|
||||
#![allow(clippy::trivially_copy_pass_by_ref)]
|
||||
|
||||
mod clock;
|
||||
mod duration;
|
||||
mod helpers;
|
||||
mod instant;
|
||||
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
||||
mod updater;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::clock::*;
|
||||
pub use self::duration::*;
|
||||
pub use self::instant::*;
|
||||
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
||||
pub use self::updater::*;
|
||||
58
patches/coarsetime/src/updater.rs
Normal file
58
patches/coarsetime/src/updater.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::io;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
use super::clock::*;
|
||||
use super::instant::*;
|
||||
|
||||
/// A service to periodically call `Instant::update()`
|
||||
#[derive(Debug)]
|
||||
pub struct Updater {
|
||||
period: time::Duration,
|
||||
running: Arc<AtomicBool>,
|
||||
th: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Updater {
|
||||
/// Spawns a background task to call `Instant::update()` periodically
|
||||
pub fn start(mut self) -> Result<Self, io::Error> {
|
||||
let period = self.period;
|
||||
let running = self.running.clone();
|
||||
running.store(true, Ordering::Relaxed);
|
||||
let th: thread::JoinHandle<()> = thread::Builder::new()
|
||||
.name("coarsetime".to_string())
|
||||
.spawn(move || {
|
||||
while running.load(Ordering::Relaxed) {
|
||||
thread::sleep(period);
|
||||
Instant::update();
|
||||
Clock::update();
|
||||
}
|
||||
})?;
|
||||
self.th = Some(th);
|
||||
Instant::update();
|
||||
Clock::update();
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Stops the periodic updates
|
||||
pub fn stop(mut self) -> Result<(), io::Error> {
|
||||
self.running.store(false, Ordering::Relaxed);
|
||||
self.th
|
||||
.take()
|
||||
.expect("updater is not running")
|
||||
.join()
|
||||
.map_err(|_| io::Error::other("failed to properly stop the updater"))
|
||||
}
|
||||
|
||||
/// Creates a new `Updater` with the specified update period, in
|
||||
/// milliseconds.
|
||||
pub fn new(period_millis: u64) -> Updater {
|
||||
Updater {
|
||||
period: time::Duration::from_millis(period_millis),
|
||||
running: Arc::new(AtomicBool::new(false)),
|
||||
th: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ ssr = [
|
||||
"dep:thiserror",
|
||||
"dep:quick-xml",
|
||||
"dep:leptos_axum",
|
||||
"dep:sqlx",
|
||||
"dep:anyhow",
|
||||
"leptos/ssr",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
@@ -29,4 +31,8 @@ leptos_axum = { version = "0.8.7", optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
bytes = { version = "1", optional = true }
|
||||
thiserror = { version = "2", optional = true }
|
||||
quick-xml = { version = "0.31", features = ["serde", "serialize"], optional = true }
|
||||
quick-xml = { version = "0.31", features = ["serde", "serialize"], optional = true }
|
||||
|
||||
# Database
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"], optional = true }
|
||||
anyhow = { version = "1.0", optional = true }
|
||||
@@ -7,6 +7,9 @@ pub mod scgi;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod xmlrpc;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod db;
|
||||
|
||||
pub mod server_fns;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -14,6 +17,12 @@ pub struct ServerContext {
|
||||
pub scgi_socket_path: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[derive(Clone)]
|
||||
pub struct DbContext {
|
||||
pub db: db::Db,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
|
||||
pub struct Torrent {
|
||||
pub hash: String,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod torrent;
|
||||
pub mod settings;
|
||||
pub mod push;
|
||||
|
||||
22
shared/src/server_fns/push.rs
Normal file
22
shared/src/server_fns/push.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[server(GetPushPublicKey, "/api/server_fns")]
|
||||
pub async fn get_public_key() -> Result<String, ServerFnError> {
|
||||
let key = std::env::var("VAPID_PUBLIC_KEY")
|
||||
.map_err(|_| ServerFnError::new("VAPID_PUBLIC_KEY not configured"))?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
#[server(SubscribePush, "/api/server_fns")]
|
||||
pub async fn subscribe_push(
|
||||
endpoint: String,
|
||||
p256dh: String,
|
||||
auth: String,
|
||||
) -> Result<(), ServerFnError> {
|
||||
let db_ctx = expect_context::<crate::DbContext>();
|
||||
db_ctx
|
||||
.db
|
||||
.save_push_subscription(&endpoint, &p256dh, &auth)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::new(format!("Failed to save subscription: {}", e)))
|
||||
}
|
||||
Reference in New Issue
Block a user