use std::sync::Arc; use std::time::Duration; use axum::response::{IntoResponse, Response}; use axum::{Extension, Json}; use serde::Serialize; use crate::auth::session::SessionState; use crate::config::AppConfig; use crate::db; use crate::error::AppError; use crate::imap::client::{ImapClient, ImapCredentials}; #[derive(Serialize)] struct QuotaResponse { usage_bytes: Option, limit_bytes: Option, } pub async fn get_quota( Extension(session): Extension, Extension(config): Extension>, Extension(imap_client): Extension>, ) -> Result { let creds = ImapCredentials { host: session.imap_host.clone(), port: session.imap_port, tls: session.imap_tls, tls_verify: session.imap_tls_verify, email: session.email.clone(), password: session.password.clone(), }; let quota = imap_client.get_quota(&creds).await.unwrap_or(None); let resp = match quota { Some(q) => QuotaResponse { usage_bytes: Some(q.usage_bytes), limit_bytes: if q.limit_bytes > 0 { Some(q.limit_bytes) } else { None }, }, None => { let database = db::pool::UserDb::new(&config.data_dir, &session.user_hash); if let Some(bytes) = database .run(db::messages::cached_storage_bytes) .await .map_err(AppError::InternalError)? { return Ok(Json(QuotaResponse { usage_bytes: Some(bytes), limit_bytes: None, }) .into_response()); } let data_dir = config.data_dir.clone(); let user_hash = session.user_hash.clone(); let folders: Vec = tokio::task::spawn_blocking(move || { let conn = db::pool::open_user_db(&data_dir, &user_hash)?; Ok::<_, String>( db::folders::get_all_folders(&conn) .unwrap_or_default() .into_iter() .map(|f| f.name) .collect(), ) }) .await .map_err(|e| AppError::InternalError(format!("Task error: {e}")))? .map_err(|e| AppError::InternalError(format!("Database error: {e}")))?; let total = tokio::time::timeout( Duration::from_secs(90), crate::imap::pool::background(async { let mut sum: u64 = 0; for folder in &folders { sum += imap_client.fetch_folder_size(&creds, folder).await?; } Ok::<_, crate::imap::error::ImapError>(sum) }), ) .await; let total = match total { Ok(Ok(total)) => Some(total), other => { tracing::warn!(error = ?other, "Quota folder-size fallback failed"); None } }; QuotaResponse { usage_bytes: total, limit_bytes: None, } } }; Ok(Json(resp).into_response()) }