use crate::BUILTIN_BROWSER_APPID;
use crate::policy::extract_url_scheme;
use crate::tabs::{
browser_tab_exists, browser_tab_info, ensure_browser_lxapp, normalize_optional_string,
normalize_runtime_tab_id,
};
use lingxia_platform::traits::app_runtime::AppRuntime;
use lingxia_transfer as transfer;
use lingxia_webview::DownloadRequest;
use lxapp::{LxApp, LxAppError, publish_app_event};
use std::path::PathBuf;
use std::sync::Arc;
use uuid::Uuid;
fn publish_browser_download_event(event_name: &str, payload: serde_json::Value) {
let payload_str = Some(payload.to_string());
let _ = publish_app_event(BUILTIN_BROWSER_APPID, event_name, payload_str);
}
pub(crate) async fn browser_download_resource(
owner: Arc<LxApp>,
tab_id: String,
request: DownloadRequest,
) {
let task_id = Uuid::new_v4().to_string();
let cancel_rx = transfer::runtime::register_active_download(&task_id);
let task = transfer::runtime::DownloadTask::for_browser(
request,
transfer::runtime::browser_download_root(&owner.runtime.app_data_dir()),
Some(rong::get_user_agent()),
)
.with_browser_persistence(owner.runtime.app_data_dir(), task_id.clone());
let tab_id_for_event = tab_id.clone();
let result = transfer::runtime::run_browser_download_task(
task,
&task_id,
&tab_id_for_event,
cancel_rx,
|event_name, payload| {
if let Err(err) = transfer::runtime::record_bridge_event(
&owner.runtime.app_data_dir(),
event_name,
&payload,
) {
lxapp::warn!(
"[InternalBrowser] failed to record download event task_id={} event={} error={}",
task_id,
event_name,
err
);
}
publish_browser_download_event(event_name, payload);
},
)
.await;
transfer::runtime::unregister_active_download(&task_id);
if let Err(err) = result {
if err.error == "Download paused" {
return;
}
lxapp::warn!(
"[InternalBrowser] download task failed tab_id={} url={} reason={}",
tab_id,
err.url,
err.error
);
}
}
fn map_lxapp_error_to_downloads(err: LxAppError) -> transfer::DownloadsError {
match err {
LxAppError::InvalidParameter(message) => {
transfer::DownloadsError::InvalidParameter(message)
}
LxAppError::ResourceNotFound(message) => {
transfer::DownloadsError::ResourceNotFound(message)
}
LxAppError::UnsupportedOperation(message) => {
transfer::DownloadsError::UnsupportedOperation(message)
}
LxAppError::IoError(message)
| LxAppError::Runtime(message)
| LxAppError::ChannelError(message)
| LxAppError::ResourceExhausted(message)
| LxAppError::Bridge(message)
| LxAppError::RongJS(message)
| LxAppError::PluginNotConfigured(message)
| LxAppError::PluginDownloadFailed(message)
| LxAppError::InvalidJsonFile(message)
| LxAppError::WebView(message) => transfer::DownloadsError::Runtime(message),
LxAppError::RongJSHost { code, message, .. } => {
transfer::DownloadsError::Runtime(format!("{code}: {message}"))
}
}
}
pub(crate) fn retry_browser_owned_download(task_id: &str) -> transfer::Result<()> {
let owner = ensure_browser_lxapp().map_err(map_lxapp_error_to_downloads)?;
let app_data_dir = owner.runtime.app_data_dir();
let record = transfer::runtime::get_record(&app_data_dir, task_id)?.ok_or_else(|| {
transfer::DownloadsError::ResourceNotFound(format!("download not found: {task_id}"))
})?;
if !matches!(
record.status,
transfer::DownloadStatus::Failed | transfer::DownloadStatus::Paused
) {
return Err(transfer::DownloadsError::UnsupportedOperation(
"download is not retryable".to_string(),
));
}
if !record.retry {
return Err(transfer::DownloadsError::UnsupportedOperation(
"download cannot be retried".to_string(),
));
}
if transfer::runtime::has_active_download(task_id) {
return Err(transfer::DownloadsError::UnsupportedOperation(
"download is already active".to_string(),
));
}
let request_context = transfer::runtime::get_request_context(&app_data_dir, task_id)?
.ok_or_else(|| {
transfer::DownloadsError::UnsupportedOperation(
"download retry context is unavailable".to_string(),
)
})?;
if matches!(
record.owner.kind,
transfer::user_cache::DownloadOwnerKind::LxApp
) {
let task_id_owned = task_id.to_string();
let app_data_dir_clone = app_data_dir.clone();
let owner_appid = record.owner.appid.clone();
let url = record.url.clone();
let headers = request_context.headers.clone();
let user_agent = request_context.user_agent.clone();
let target_path = PathBuf::from(&record.target_path);
let behavior = request_context.behavior;
rong::RongExecutor::global().spawn(async move {
let persistence = transfer::user_cache::DownloadPersistence::new(
app_data_dir_clone.clone(),
task_id_owned.clone(),
transfer::user_cache::DownloadOwner {
kind: transfer::user_cache::DownloadOwnerKind::LxApp,
appid: owner_appid,
page_path: None,
tab_id: None,
},
true,
);
let result = transfer::user_cache::download_to_path_with_behavior(
Some(persistence),
target_path,
transfer::user_cache::UserCacheDownloadRequest { url, headers },
user_agent,
behavior,
|_| {},
)
.await;
if let Err(err) = result {
if err.error == "Download paused" {
return;
}
lxapp::warn!(
"[Downloads] retry download task failed task_id={} url={} reason={}",
task_id_owned,
err.url,
err.error
);
}
});
return Ok(());
}
let request = DownloadRequest {
url: record.url.clone(),
user_agent: request_context.user_agent.clone(),
content_disposition: None,
mime_type: record.mime_type.clone(),
content_length: record.total_bytes,
suggested_filename: request_context
.suggested_filename
.clone()
.or_else(|| Some(record.file_name.clone())),
source_page_url: request_context.source_page_url.clone(),
cookie: request_context.cookie.clone(),
};
let cancel_rx = transfer::runtime::register_active_download(task_id);
let task = transfer::runtime::DownloadTask::for_browser(
request,
transfer::runtime::browser_download_root(&app_data_dir),
Some(rong::get_user_agent()),
)
.with_target_path(PathBuf::from(&record.target_path))
.with_browser_persistence(app_data_dir.clone(), task_id.to_string())
.with_behavior(request_context.behavior);
let owner_clone = owner.clone();
let task_id_owned = task_id.to_string();
let tab_id = record.tab_id.clone();
rong::RongExecutor::global().spawn(async move {
let result = transfer::runtime::run_browser_download_task(
task,
&task_id_owned,
&tab_id,
cancel_rx,
|event_name, payload| {
if let Err(err) = transfer::runtime::record_bridge_event(
&owner_clone.runtime.app_data_dir(),
event_name,
&payload,
) {
lxapp::warn!(
"[InternalBrowser] failed to record retry download event task_id={} event={} error={}",
task_id_owned,
event_name,
err
);
}
publish_browser_download_event(event_name, payload);
},
)
.await;
transfer::runtime::unregister_active_download(&task_id_owned);
if let Err(err) = result {
if err.error == "Download paused" {
return;
}
lxapp::warn!(
"[InternalBrowser] retry download task failed task_id={} url={} reason={}",
task_id_owned,
err.url,
err.error
);
}
});
Ok(())
}
pub(crate) fn start_native_browser_download(
tab_id: &str,
url: &str,
user_agent: Option<&str>,
suggested_filename: Option<&str>,
source_page_url: Option<&str>,
cookie: Option<&str>,
) -> Result<(), LxAppError> {
let normalized_tab_id = normalize_runtime_tab_id(tab_id).ok_or_else(|| {
LxAppError::InvalidParameter("tab_id must be a valid runtime browser tab id".to_string())
})?;
let normalized_url = url.trim();
if normalized_url.is_empty() {
return Err(LxAppError::InvalidParameter("url is required".to_string()));
}
if !matches!(
extract_url_scheme(normalized_url).as_deref(),
Some("http" | "https")
) {
return Err(LxAppError::InvalidParameter(
"browser download url must be http(s)".to_string(),
));
}
let source_page_url = normalize_optional_string(source_page_url)
.or_else(|| browser_tab_info(&normalized_tab_id).and_then(|info| info.current_url));
if !browser_tab_exists(&normalized_tab_id) {
return Err(LxAppError::ResourceNotFound(format!(
"browser tab not found: {}",
normalized_tab_id
)));
}
let owner = ensure_browser_lxapp()?;
let request = DownloadRequest {
url: normalized_url.to_string(),
user_agent: normalize_optional_string(user_agent),
content_disposition: None,
mime_type: None,
content_length: None,
suggested_filename: normalize_optional_string(suggested_filename),
source_page_url,
cookie: normalize_optional_string(cookie),
};
rong::RongExecutor::global().spawn({
let owner = owner.clone();
let tab_id = normalized_tab_id.clone();
async move {
browser_download_resource(owner, tab_id, request).await;
}
});
Ok(())
}