@@ -17,6 +17,7 @@
|
|||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "^2.2.0",
|
"@tauri-apps/plugin-clipboard-manager": "^2.2.0",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
|
"@tauri-apps/plugin-window-state": "~2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"embla-carousel-autoplay": "^8.5.2",
|
"embla-carousel-autoplay": "^8.5.2",
|
||||||
|
|||||||
16
src-tauri/Cargo.lock
generated
16
src-tauri/Cargo.lock
generated
@@ -3753,6 +3753,7 @@ dependencies = [
|
|||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-clipboard-manager",
|
"tauri-plugin-clipboard-manager",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"tauri-plugin-window-state",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -4123,6 +4124,21 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-window-state"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35e344b512b0d99d9d06225f235d87d6c66d89496a3bf323d9b578d940596e6c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.8.0",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
|||||||
@@ -32,3 +32,6 @@ pinyin = "0.10.0"
|
|||||||
tauri-plugin-clipboard-manager = "2.2.0"
|
tauri-plugin-clipboard-manager = "2.2.0"
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
|
||||||
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
|
tauri-plugin-window-state = "2"
|
||||||
|
|||||||
14
src-tauri/capabilities/desktop.json
Normal file
14
src-tauri/capabilities/desktop.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"identifier": "desktop-capability",
|
||||||
|
"platforms": [
|
||||||
|
"macOS",
|
||||||
|
"windows",
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"windows": [
|
||||||
|
"main"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"window-state:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
use crate::{handlers::server::get_json_server_url, models::app::{AppDetail, AppItem}, utils::UA};
|
|
||||||
use crate::utils::format::format_icon_url;
|
|
||||||
use super::category::get_all_apps;
|
use super::category::get_all_apps;
|
||||||
|
use crate::utils::format::format_icon_url;
|
||||||
|
use crate::{
|
||||||
|
handlers::server::get_json_server_url,
|
||||||
|
models::app::{AppDetail, AppItem},
|
||||||
|
utils::UA,
|
||||||
|
};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_app_info(category: String, pkgname: String) -> Result<AppDetail, String> {
|
pub async fn get_app_info(category: String, pkgname: String) -> Result<AppDetail, String> {
|
||||||
@@ -10,7 +14,7 @@ pub async fn get_app_info(category: String, pkgname: String) -> Result<AppDetail
|
|||||||
// 获取服务器URL
|
// 获取服务器URL
|
||||||
let json_server_url = get_json_server_url();
|
let json_server_url = get_json_server_url();
|
||||||
let url = format!("{}{}/{}/app.json", json_server_url, category, pkgname);
|
let url = format!("{}{}/{}/app.json", json_server_url, category, pkgname);
|
||||||
|
|
||||||
// 创建HTTP客户端并发送请求
|
// 创建HTTP客户端并发送请求
|
||||||
let response = reqwest::Client::new()
|
let response = reqwest::Client::new()
|
||||||
.get(&url)
|
.get(&url)
|
||||||
@@ -21,25 +25,30 @@ pub async fn get_app_info(category: String, pkgname: String) -> Result<AppDetail
|
|||||||
.text()
|
.text()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("读取响应内容失败: {}", e))?;
|
.map_err(|e| format!("读取响应内容失败: {}", e))?;
|
||||||
|
|
||||||
// 直接解析JSON响应为AppDetail结构体
|
// 直接解析JSON响应为AppDetail结构体
|
||||||
let mut app_info: AppDetail = serde_json::from_str(&response)
|
let mut app_info: AppDetail =
|
||||||
.map_err(|e| format!("解析应用信息JSON失败: {}", e))?;
|
serde_json::from_str(&response).map_err(|e| format!("解析应用信息JSON失败: {}", e))?;
|
||||||
|
|
||||||
// 设置图标URL和下载次数
|
// 设置图标URL和下载次数
|
||||||
app_info.category = Some(category.clone());
|
app_info.category = Some(category.clone());
|
||||||
app_info.icon = Some(format_icon_url(&category.clone(), &app_info.pkgname));
|
app_info.icon = Some(format_icon_url(&category.clone(), &app_info.pkgname));
|
||||||
app_info.download_times = Some(get_download_times(category, pkgname)
|
app_info.download_times = Some(
|
||||||
.await
|
get_download_times(category, pkgname)
|
||||||
.map_err(|e| format!("获取下载次数失败: {}", e))?);
|
.await
|
||||||
|
.map_err(|e| format!("获取下载次数失败: {}", e))?,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(app_info)
|
Ok(app_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_download_times(category: String, pkgname: String) -> Result<i32, String> {
|
pub async fn get_download_times(category: String, pkgname: String) -> Result<i32, String> {
|
||||||
let json_server_url = get_json_server_url();
|
let json_server_url = get_json_server_url();
|
||||||
let url = format!("{}{}/{}/download-times.txt", json_server_url, category, pkgname);
|
let url = format!(
|
||||||
|
"{}{}/{}/download-times.txt",
|
||||||
|
json_server_url, category, pkgname
|
||||||
|
);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
@@ -47,7 +56,7 @@ pub async fn get_download_times(category: String, pkgname: String) -> Result<i32
|
|||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let times = response
|
let times = response
|
||||||
.text()
|
.text()
|
||||||
.await
|
.await
|
||||||
@@ -55,7 +64,7 @@ pub async fn get_download_times(category: String, pkgname: String) -> Result<i32
|
|||||||
.trim()
|
.trim()
|
||||||
.parse::<i32>()
|
.parse::<i32>()
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
Ok(times)
|
Ok(times)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,4 +72,4 @@ pub async fn get_download_times(category: String, pkgname: String) -> Result<i32
|
|||||||
pub async fn search_all_apps(query: String) -> Result<Vec<AppItem>, String> {
|
pub async fn search_all_apps(query: String) -> Result<Vec<AppItem>, String> {
|
||||||
let all_apps = get_all_apps().await?;
|
let all_apps = get_all_apps().await?;
|
||||||
Ok(crate::utils::search::search_apps(&all_apps, &query))
|
Ok(crate::utils::search::search_apps(&all_apps, &query))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
|
use super::server::get_json_server_url;
|
||||||
|
use crate::models::app::AppItem;
|
||||||
use crate::models::category::Category;
|
use crate::models::category::Category;
|
||||||
use crate::utils::format::format_icon_url;
|
use crate::utils::format::format_icon_url;
|
||||||
use crate::models::app::AppItem;
|
|
||||||
use crate::utils::UA;
|
use crate::utils::UA;
|
||||||
use super::server::get_json_server_url;
|
use futures::future::join_all;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use futures::future::join_all;
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref APPS_CACHE: Mutex<HashMap<String, Vec<AppItem>>> = Mutex::new(HashMap::new());
|
static ref APPS_CACHE: Mutex<HashMap<String, Vec<AppItem>>> = Mutex::new(HashMap::new());
|
||||||
@@ -23,7 +23,7 @@ pub async fn get_category_apps(category_id: String) -> Result<Vec<AppItem>, Stri
|
|||||||
// 如果缓存中没有,从服务器获取数据
|
// 如果缓存中没有,从服务器获取数据
|
||||||
let json_server_url = get_json_server_url();
|
let json_server_url = get_json_server_url();
|
||||||
let url = format!("{}{}/applist.json", json_server_url, category_id);
|
let url = format!("{}{}/applist.json", json_server_url, category_id);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
@@ -31,12 +31,9 @@ pub async fn get_category_apps(category_id: String) -> Result<Vec<AppItem>, Stri
|
|||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut apps: Vec<AppItem> = response
|
let mut apps: Vec<AppItem> = response.json().await.map_err(|e| e.to_string())?;
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
// 为每个应用设置图标URL
|
// 为每个应用设置图标URL
|
||||||
for app in &mut apps {
|
for app in &mut apps {
|
||||||
app.icon = Some(format_icon_url(&category_id, &app.pkgname));
|
app.icon = Some(format_icon_url(&category_id, &app.pkgname));
|
||||||
@@ -44,8 +41,11 @@ pub async fn get_category_apps(category_id: String) -> Result<Vec<AppItem>, Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新缓存
|
// 更新缓存
|
||||||
APPS_CACHE.lock().unwrap().insert(category_id.clone(), apps.clone());
|
APPS_CACHE
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(category_id.clone(), apps.clone());
|
||||||
|
|
||||||
Ok(apps)
|
Ok(apps)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,17 +56,17 @@ pub async fn get_all_apps() -> Result<Vec<AppItem>, String> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|category| get_category_apps(category.id.clone()))
|
.map(|category| get_category_apps(category.id.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let results = join_all(futures).await;
|
let results = join_all(futures).await;
|
||||||
let mut all_apps = Vec::new();
|
let mut all_apps = Vec::new();
|
||||||
|
|
||||||
for result in results {
|
for result in results {
|
||||||
match result {
|
match result {
|
||||||
Ok(apps) => all_apps.extend(apps),
|
Ok(apps) => all_apps.extend(apps),
|
||||||
Err(e) => eprintln!("Error fetching apps for category: {}", e)
|
Err(e) => eprintln!("Error fetching apps for category: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(all_apps)
|
Ok(all_apps)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ pub async fn get_all_categories() -> Result<Vec<Category>, String> {
|
|||||||
// 如果缓存中没有,从服务器获取数据
|
// 如果缓存中没有,从服务器获取数据
|
||||||
let json_server_url = get_json_server_url();
|
let json_server_url = get_json_server_url();
|
||||||
let url = format!("{}category.json", json_server_url);
|
let url = format!("{}category.json", json_server_url);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
@@ -88,14 +88,11 @@ pub async fn get_all_categories() -> Result<Vec<Category>, String> {
|
|||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let categories: Vec<Category> = response
|
let categories: Vec<Category> = response.json().await.map_err(|e| e.to_string())?;
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
// 更新缓存
|
// 更新缓存
|
||||||
*CATEGORIES_CACHE.lock().unwrap() = Some(categories.clone());
|
*CATEGORIES_CACHE.lock().unwrap() = Some(categories.clone());
|
||||||
|
|
||||||
Ok(categories)
|
Ok(categories)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub async fn check_launch_app(pkgname: String) -> Result<bool, String> {
|
|||||||
.output()
|
.output()
|
||||||
.map_err(|e| format!("启动应用失败: {}", e))?;
|
.map_err(|e| format!("启动应用失败: {}", e))?;
|
||||||
|
|
||||||
Ok(output.status.success())
|
Ok(output.status.success())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -30,4 +30,4 @@ pub async fn launch_launch_app(pkgname: String) -> Result<(), String> {
|
|||||||
.map_err(|e| format!("启动应用失败: {}", e))?;
|
.map_err(|e| format!("启动应用失败: {}", e))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,48 @@ use tauri::State;
|
|||||||
use crate::{models::download::DownloadTaskResponse, utils::download_manager::DownloadManager};
|
use crate::{models::download::DownloadTaskResponse, utils::download_manager::DownloadManager};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_downloads(manager: State<'_, DownloadManager>) -> Result<Vec<DownloadTaskResponse>, String> {
|
pub async fn get_downloads(
|
||||||
|
manager: State<'_, DownloadManager>,
|
||||||
|
) -> Result<Vec<DownloadTaskResponse>, String> {
|
||||||
manager.get_downloads().await
|
manager.get_downloads().await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn add_download(category: String, pkgname: String, filename: String, name: String, manager: State<'_, DownloadManager>) -> Result<(), String> {
|
pub async fn add_download(
|
||||||
manager.add_download(category, pkgname, filename, name).await
|
category: String,
|
||||||
|
pkgname: String,
|
||||||
|
filename: String,
|
||||||
|
name: String,
|
||||||
|
manager: State<'_, DownloadManager>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
manager
|
||||||
|
.add_download(category, pkgname, filename, name)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn pause_download(category: String, pkgname: String, manager: State<'_, DownloadManager>) -> Result<(), String> {
|
pub async fn pause_download(
|
||||||
|
category: String,
|
||||||
|
pkgname: String,
|
||||||
|
manager: State<'_, DownloadManager>,
|
||||||
|
) -> Result<(), String> {
|
||||||
manager.pause_download(category, pkgname).await
|
manager.pause_download(category, pkgname).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn resume_download(category: String, pkgname: String, manager: State<'_, DownloadManager>) -> Result<(), String> {
|
pub async fn resume_download(
|
||||||
|
category: String,
|
||||||
|
pkgname: String,
|
||||||
|
manager: State<'_, DownloadManager>,
|
||||||
|
) -> Result<(), String> {
|
||||||
manager.resume_download(category, pkgname).await
|
manager.resume_download(category, pkgname).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn cancel_download(category: String, pkgname: String, manager: State<'_, DownloadManager>) -> Result<(), String> {
|
pub async fn cancel_download(
|
||||||
|
category: String,
|
||||||
|
pkgname: String,
|
||||||
|
manager: State<'_, DownloadManager>,
|
||||||
|
) -> Result<(), String> {
|
||||||
manager.cancel_download(category, pkgname).await
|
manager.cancel_download(category, pkgname).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use std::fs;
|
|||||||
pub fn save_text_file(filename: String, content: String) -> Result<(), String> {
|
pub fn save_text_file(filename: String, content: String) -> Result<(), String> {
|
||||||
let config_dir = dirs::config_dir().ok_or("无法获取配置目录")?;
|
let config_dir = dirs::config_dir().ok_or("无法获取配置目录")?;
|
||||||
let dir = config_dir.join(env!("CARGO_PKG_NAME"));
|
let dir = config_dir.join(env!("CARGO_PKG_NAME"));
|
||||||
|
|
||||||
// 确保目录存在
|
// 确保目录存在
|
||||||
fs::create_dir_all(&dir).map_err(|e| format!("创建目录失败: {}", e))?;
|
fs::create_dir_all(&dir).map_err(|e| format!("创建目录失败: {}", e))?;
|
||||||
|
|
||||||
let file_path = dir.join(filename);
|
let file_path = dir.join(filename);
|
||||||
fs::write(file_path, content).map_err(|e| format!("写入文件失败: {}", e))
|
fs::write(file_path, content).map_err(|e| format!("写入文件失败: {}", e))
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,6 @@ pub fn read_text_file(filename: String) -> Result<String, String> {
|
|||||||
let config_dir = dirs::config_dir().ok_or("无法获取配置目录")?;
|
let config_dir = dirs::config_dir().ok_or("无法获取配置目录")?;
|
||||||
let dir = config_dir.join(env!("CARGO_PKG_NAME"));
|
let dir = config_dir.join(env!("CARGO_PKG_NAME"));
|
||||||
let file_path = dir.join(filename);
|
let file_path = dir.join(filename);
|
||||||
|
|
||||||
fs::read_to_string(file_path).map_err(|e| format!("读取文件失败: {}", e))
|
fs::read_to_string(file_path).map_err(|e| format!("读取文件失败: {}", e))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
|
use super::server::get_json_server_url;
|
||||||
use crate::handlers::server::get_img_server_url;
|
use crate::handlers::server::get_img_server_url;
|
||||||
use crate::models::home::{HomeLink, HomeList, HomeListApp};
|
use crate::models::home::{HomeLink, HomeList, HomeListApp};
|
||||||
use crate::utils::UA;
|
|
||||||
use crate::utils::format::format_icon_url;
|
use crate::utils::format::format_icon_url;
|
||||||
use super::server::get_json_server_url;
|
use crate::utils::UA;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_home_links() -> Result<Vec<HomeLink>, String> {
|
pub async fn get_home_links() -> Result<Vec<HomeLink>, String> {
|
||||||
let json_server_url = get_json_server_url();
|
let json_server_url = get_json_server_url();
|
||||||
let img_server_url = get_img_server_url();
|
let img_server_url = get_img_server_url();
|
||||||
let url = format!("{}/home/homelinks.json", json_server_url);
|
let url = format!("{}/home/homelinks.json", json_server_url);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
@@ -17,22 +17,19 @@ pub async fn get_home_links() -> Result<Vec<HomeLink>, String> {
|
|||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// 获取响应内容的文本形式
|
// 获取响应内容的文本形式
|
||||||
let response_text = response
|
let response_text = response.text().await.map_err(|e| e.to_string())?;
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
// 将文本解析为 HomeLink 向量
|
// 将文本解析为 HomeLink 向量
|
||||||
let mut links: Vec<HomeLink> = serde_json::from_str(&response_text)
|
let mut links: Vec<HomeLink> =
|
||||||
.map_err(|e| e.to_string())?;
|
serde_json::from_str(&response_text).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// 为每个 HomeLink 的 img_url 添加图片服务器前缀
|
// 为每个 HomeLink 的 img_url 添加图片服务器前缀
|
||||||
for link in &mut links {
|
for link in &mut links {
|
||||||
link.img_url = format!("{}{}", img_server_url, link.img_url);
|
link.img_url = format!("{}{}", img_server_url, link.img_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(links)
|
Ok(links)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +37,7 @@ pub async fn get_home_links() -> Result<Vec<HomeLink>, String> {
|
|||||||
pub async fn get_home_list_apps(json_url: String) -> Result<Vec<HomeListApp>, String> {
|
pub async fn get_home_list_apps(json_url: String) -> Result<Vec<HomeListApp>, String> {
|
||||||
let json_server_url = get_json_server_url();
|
let json_server_url = get_json_server_url();
|
||||||
let url = format!("{}{}", json_server_url, json_url);
|
let url = format!("{}{}", json_server_url, json_url);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
@@ -48,19 +45,16 @@ pub async fn get_home_list_apps(json_url: String) -> Result<Vec<HomeListApp>, St
|
|||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let response_text = response
|
let response_text = response.text().await.map_err(|e| e.to_string())?;
|
||||||
.text()
|
|
||||||
.await
|
let mut apps: Vec<HomeListApp> =
|
||||||
.map_err(|e| e.to_string())?;
|
serde_json::from_str(&response_text).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut apps: Vec<HomeListApp> = serde_json::from_str(&response_text)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
for app in &mut apps {
|
for app in &mut apps {
|
||||||
app.icon = Some(format_icon_url(&app.category, &app.pkgname));
|
app.icon = Some(format_icon_url(&app.category, &app.pkgname));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(apps)
|
Ok(apps)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +62,7 @@ pub async fn get_home_list_apps(json_url: String) -> Result<Vec<HomeListApp>, St
|
|||||||
pub async fn get_home_lists() -> Result<Vec<HomeList>, String> {
|
pub async fn get_home_lists() -> Result<Vec<HomeList>, String> {
|
||||||
let json_server_url = get_json_server_url();
|
let json_server_url = get_json_server_url();
|
||||||
let url = format!("{}/home/homelist.json", json_server_url);
|
let url = format!("{}/home/homelist.json", json_server_url);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
@@ -76,14 +70,10 @@ pub async fn get_home_lists() -> Result<Vec<HomeList>, String> {
|
|||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let response_text = response
|
let response_text = response.text().await.map_err(|e| e.to_string())?;
|
||||||
.text()
|
|
||||||
.await
|
let lists: Vec<HomeList> = serde_json::from_str(&response_text).map_err(|e| e.to_string())?;
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let lists: Vec<HomeList> = serde_json::from_str(&response_text)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
Ok(lists)
|
Ok(lists)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
pub mod category;
|
|
||||||
pub mod server;
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod home;
|
pub mod category;
|
||||||
pub mod file;
|
pub mod deb;
|
||||||
pub mod download;
|
pub mod download;
|
||||||
pub mod deb;
|
pub mod file;
|
||||||
|
pub mod home;
|
||||||
|
pub mod server;
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ pub fn get_json_server_url() -> String {
|
|||||||
pub fn get_img_server_url() -> String {
|
pub fn get_img_server_url() -> String {
|
||||||
let arch = get_target_arch_to_store();
|
let arch = get_target_arch_to_store();
|
||||||
return format!("https://spk-json.spark-app.store/{}/", arch);
|
return format!("https://spk-json.spark-app.store/{}/", arch);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use utils::download_manager::DownloadManager;
|
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
use utils::download_manager::DownloadManager;
|
||||||
|
|
||||||
mod models;
|
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
mod models;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_window_state::Builder::new().build())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
.manage(utils::download_manager::DownloadManager::new())
|
.manage(utils::download_manager::DownloadManager::new())
|
||||||
@@ -34,7 +35,7 @@ pub fn run() {
|
|||||||
handlers::deb::check_launch_app,
|
handlers::deb::check_launch_app,
|
||||||
handlers::deb::launch_launch_app,
|
handlers::deb::launch_launch_app,
|
||||||
utils::get_user_agent,
|
utils::get_user_agent,
|
||||||
])
|
])
|
||||||
.on_window_event(|window, event| match event {
|
.on_window_event(|window, event| match event {
|
||||||
tauri::WindowEvent::Destroyed => {
|
tauri::WindowEvent::Destroyed => {
|
||||||
// 获取 DownloadManager 实例并关闭 aria2
|
// 获取 DownloadManager 实例并关闭 aria2
|
||||||
@@ -57,7 +58,7 @@ pub fn run() {
|
|||||||
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ pub struct AppDetail {
|
|||||||
pub size: String,
|
pub size: String,
|
||||||
#[serde(rename = "DownloadTimes")]
|
#[serde(rename = "DownloadTimes")]
|
||||||
pub download_times: Option<i32>,
|
pub download_times: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize,Clone,Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Category {
|
pub struct Category {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub icon: String,
|
pub icon: String,
|
||||||
pub name_zh_cn: String,
|
pub name_zh_cn: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,20 +34,20 @@ pub struct DownloadTaskResponse {
|
|||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ResponseStatus {
|
pub enum ResponseStatus {
|
||||||
Downloading, // 下载中
|
Downloading, // 下载中
|
||||||
Queued, // 等待下载
|
Queued, // 等待下载
|
||||||
Paused, // 下载暂停
|
Paused, // 下载暂停
|
||||||
Completed, // 下载完成
|
Completed, // 下载完成
|
||||||
Error, // 错误
|
Error, // 错误
|
||||||
Installing, // 安装中
|
Installing, // 安装中
|
||||||
Installed, // 安装完成
|
Installed, // 安装完成
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum InstallStatus {
|
pub enum InstallStatus {
|
||||||
Queued, // 等待安装
|
Queued, // 等待安装
|
||||||
Error, // 错误
|
Error, // 错误
|
||||||
Installing, // 安装中
|
Installing, // 安装中
|
||||||
Installed, // 安装完成
|
Installed, // 安装完成
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,4 @@ pub struct HomeList {
|
|||||||
pub list_type: String,
|
pub list_type: String,
|
||||||
#[serde(rename = "jsonUrl")]
|
#[serde(rename = "jsonUrl")]
|
||||||
pub json_url: String,
|
pub json_url: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
pub mod category;
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
|
pub mod category;
|
||||||
|
pub mod download;
|
||||||
pub mod home;
|
pub mod home;
|
||||||
pub mod download;
|
|
||||||
@@ -45,17 +45,17 @@ impl Aria2Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 添加Metalink下载
|
/// 添加Metalink下载
|
||||||
///
|
///
|
||||||
/// 通过上传.metalink文件内容添加Metalink下载
|
/// 通过上传.metalink文件内容添加Metalink下载
|
||||||
///
|
///
|
||||||
/// # 参数
|
/// # 参数
|
||||||
///
|
///
|
||||||
/// * `metalink` - metalink文件的内容
|
/// * `metalink` - metalink文件的内容
|
||||||
/// * `options` - 可选的下载选项
|
/// * `options` - 可选的下载选项
|
||||||
/// * `position` - 可选的队列位置
|
/// * `position` - 可选的队列位置
|
||||||
///
|
///
|
||||||
/// # 返回
|
/// # 返回
|
||||||
///
|
///
|
||||||
/// 返回新注册下载的GID数组
|
/// 返回新注册下载的GID数组
|
||||||
pub async fn add_metalink(
|
pub async fn add_metalink(
|
||||||
&self,
|
&self,
|
||||||
@@ -65,31 +65,31 @@ impl Aria2Client {
|
|||||||
) -> Result<Vec<String>, Box<dyn Error>> {
|
) -> Result<Vec<String>, Box<dyn Error>> {
|
||||||
// Base64编码metalink内容
|
// Base64编码metalink内容
|
||||||
let encoded_metalink = general_purpose::STANDARD.encode(metalink);
|
let encoded_metalink = general_purpose::STANDARD.encode(metalink);
|
||||||
|
|
||||||
// 构建参数列表
|
// 构建参数列表
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
// 如果有密钥,添加到参数列表
|
// 如果有密钥,添加到参数列表
|
||||||
if let Some(secret) = &self.secret {
|
if let Some(secret) = &self.secret {
|
||||||
params.push(json!(secret));
|
params.push(json!(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加metalink内容
|
// 添加metalink内容
|
||||||
params.push(json!(encoded_metalink));
|
params.push(json!(encoded_metalink));
|
||||||
|
|
||||||
// 添加选项(如果有)
|
// 添加选项(如果有)
|
||||||
if let Some(opts) = options {
|
if let Some(opts) = options {
|
||||||
params.push(json!(opts));
|
params.push(json!(opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加位置(如果有)
|
// 添加位置(如果有)
|
||||||
if let Some(pos) = position {
|
if let Some(pos) = position {
|
||||||
params.push(json!(pos));
|
params.push(json!(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送RPC请求
|
// 发送RPC请求
|
||||||
let response = self.send_request("aria2.addMetalink", params).await?;
|
let response = self.send_request("aria2.addMetalink", params).await?;
|
||||||
|
|
||||||
// 解析结果
|
// 解析结果
|
||||||
if let Some(result) = response.result {
|
if let Some(result) = response.result {
|
||||||
if let Some(gids) = result.as_array() {
|
if let Some(gids) = result.as_array() {
|
||||||
@@ -100,14 +100,14 @@ impl Aria2Client {
|
|||||||
return Ok(gid_vec);
|
return Ok(gid_vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = response.error {
|
if let Some(error) = response.error {
|
||||||
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("无法解析Aria2响应".into())
|
Err("无法解析Aria2响应".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 发送JSON-RPC请求到aria2
|
/// 发送JSON-RPC请求到aria2
|
||||||
async fn send_request(
|
async fn send_request(
|
||||||
&self,
|
&self,
|
||||||
@@ -120,246 +120,255 @@ impl Aria2Client {
|
|||||||
method: method.to_string(),
|
method: method.to_string(),
|
||||||
params,
|
params,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = self.client
|
let response = self
|
||||||
|
.client
|
||||||
.post(&self.rpc_url)
|
.post(&self.rpc_url)
|
||||||
.json(&request)
|
.json(&request)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.json::<JsonRpcResponse>()
|
.json::<JsonRpcResponse>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取下载状态
|
/// 获取下载状态
|
||||||
pub async fn tell_status(&self, gid: &str) -> Result<Value, Box<dyn Error>> {
|
pub async fn tell_status(&self, gid: &str) -> Result<Value, Box<dyn Error>> {
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if let Some(secret) = &self.secret {
|
if let Some(secret) = &self.secret {
|
||||||
params.push(json!(secret));
|
params.push(json!(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
params.push(json!(gid));
|
params.push(json!(gid));
|
||||||
|
|
||||||
let response = self.send_request("aria2.tellStatus", params).await?;
|
let response = self.send_request("aria2.tellStatus", params).await?;
|
||||||
|
|
||||||
if let Some(result) = response.result {
|
if let Some(result) = response.result {
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = response.error {
|
if let Some(error) = response.error {
|
||||||
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("无法获取下载状态".into())
|
Err("无法获取下载状态".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取所有活动中的下载任务
|
/// 获取所有活动中的下载任务
|
||||||
///
|
///
|
||||||
/// 返回所有正在下载的任务的状态信息
|
/// 返回所有正在下载的任务的状态信息
|
||||||
///
|
///
|
||||||
/// # 返回
|
/// # 返回
|
||||||
///
|
///
|
||||||
/// 返回包含所有活动下载任务信息的数组
|
/// 返回包含所有活动下载任务信息的数组
|
||||||
pub async fn tell_active(&self) -> Result<Vec<Value>, Box<dyn Error>> {
|
pub async fn tell_active(&self) -> Result<Vec<Value>, Box<dyn Error>> {
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if let Some(secret) = &self.secret {
|
if let Some(secret) = &self.secret {
|
||||||
params.push(json!(secret));
|
params.push(json!(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = self.send_request("aria2.tellActive", params).await?;
|
let response = self.send_request("aria2.tellActive", params).await?;
|
||||||
|
|
||||||
if let Some(result) = response.result {
|
if let Some(result) = response.result {
|
||||||
if let Some(downloads) = result.as_array() {
|
if let Some(downloads) = result.as_array() {
|
||||||
return Ok(downloads.clone());
|
return Ok(downloads.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = response.error {
|
if let Some(error) = response.error {
|
||||||
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("无法获取活动下载任务".into())
|
Err("无法获取活动下载任务".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取等待中的下载任务
|
/// 获取等待中的下载任务
|
||||||
///
|
///
|
||||||
/// 返回等待队列中的下载任务状态信息
|
/// 返回等待队列中的下载任务状态信息
|
||||||
///
|
///
|
||||||
/// # 参数
|
/// # 参数
|
||||||
///
|
///
|
||||||
/// * `offset` - 从队列开始位置的偏移量
|
/// * `offset` - 从队列开始位置的偏移量
|
||||||
/// * `num` - 要获取的任务数量
|
/// * `num` - 要获取的任务数量
|
||||||
///
|
///
|
||||||
/// # 返回
|
/// # 返回
|
||||||
///
|
///
|
||||||
/// 返回包含等待中下载任务信息的数组
|
/// 返回包含等待中下载任务信息的数组
|
||||||
pub async fn tell_waiting(&self, offset: usize, num: usize) -> Result<Vec<Value>, Box<dyn Error>> {
|
pub async fn tell_waiting(
|
||||||
|
&self,
|
||||||
|
offset: usize,
|
||||||
|
num: usize,
|
||||||
|
) -> Result<Vec<Value>, Box<dyn Error>> {
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if let Some(secret) = &self.secret {
|
if let Some(secret) = &self.secret {
|
||||||
params.push(json!(secret));
|
params.push(json!(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
params.push(json!(offset));
|
params.push(json!(offset));
|
||||||
params.push(json!(num));
|
params.push(json!(num));
|
||||||
|
|
||||||
let response = self.send_request("aria2.tellWaiting", params).await?;
|
let response = self.send_request("aria2.tellWaiting", params).await?;
|
||||||
|
|
||||||
if let Some(result) = response.result {
|
if let Some(result) = response.result {
|
||||||
if let Some(downloads) = result.as_array() {
|
if let Some(downloads) = result.as_array() {
|
||||||
return Ok(downloads.clone());
|
return Ok(downloads.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = response.error {
|
if let Some(error) = response.error {
|
||||||
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("无法获取等待中的下载任务".into())
|
Err("无法获取等待中的下载任务".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取已停止的下载任务
|
/// 获取已停止的下载任务
|
||||||
///
|
///
|
||||||
/// 返回已完成/错误/已移除的下载任务状态信息
|
/// 返回已完成/错误/已移除的下载任务状态信息
|
||||||
///
|
///
|
||||||
/// # 参数
|
/// # 参数
|
||||||
///
|
///
|
||||||
/// * `offset` - 从队列开始位置的偏移量
|
/// * `offset` - 从队列开始位置的偏移量
|
||||||
/// * `num` - 要获取的任务数量
|
/// * `num` - 要获取的任务数量
|
||||||
///
|
///
|
||||||
/// # 返回
|
/// # 返回
|
||||||
///
|
///
|
||||||
/// 返回包含已停止下载任务信息的数组
|
/// 返回包含已停止下载任务信息的数组
|
||||||
pub async fn tell_stopped(&self, offset: usize, num: usize) -> Result<Vec<Value>, Box<dyn Error>> {
|
pub async fn tell_stopped(
|
||||||
|
&self,
|
||||||
|
offset: usize,
|
||||||
|
num: usize,
|
||||||
|
) -> Result<Vec<Value>, Box<dyn Error>> {
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if let Some(secret) = &self.secret {
|
if let Some(secret) = &self.secret {
|
||||||
params.push(json!(secret));
|
params.push(json!(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
params.push(json!(offset));
|
params.push(json!(offset));
|
||||||
params.push(json!(num));
|
params.push(json!(num));
|
||||||
|
|
||||||
let response = self.send_request("aria2.tellStopped", params).await?;
|
let response = self.send_request("aria2.tellStopped", params).await?;
|
||||||
|
|
||||||
if let Some(result) = response.result {
|
if let Some(result) = response.result {
|
||||||
if let Some(downloads) = result.as_array() {
|
if let Some(downloads) = result.as_array() {
|
||||||
return Ok(downloads.clone());
|
return Ok(downloads.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = response.error {
|
if let Some(error) = response.error {
|
||||||
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("无法获取已停止的下载任务".into())
|
Err("无法获取已停止的下载任务".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 暂停下载任务
|
/// 暂停下载任务
|
||||||
///
|
///
|
||||||
/// 暂停指定GID的下载任务
|
/// 暂停指定GID的下载任务
|
||||||
///
|
///
|
||||||
/// # 参数
|
/// # 参数
|
||||||
///
|
///
|
||||||
/// * `gid` - 下载任务的GID
|
/// * `gid` - 下载任务的GID
|
||||||
///
|
///
|
||||||
/// # 返回
|
/// # 返回
|
||||||
///
|
///
|
||||||
/// 成功时返回被暂停的GID
|
/// 成功时返回被暂停的GID
|
||||||
pub async fn pause(&self, gid: &str) -> Result<String, Box<dyn Error>> {
|
pub async fn pause(&self, gid: &str) -> Result<String, Box<dyn Error>> {
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if let Some(secret) = &self.secret {
|
if let Some(secret) = &self.secret {
|
||||||
params.push(json!(secret));
|
params.push(json!(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
params.push(json!(gid));
|
params.push(json!(gid));
|
||||||
|
|
||||||
let response = self.send_request("aria2.pause", params).await?;
|
let response = self.send_request("aria2.pause", params).await?;
|
||||||
|
|
||||||
if let Some(result) = response.result {
|
if let Some(result) = response.result {
|
||||||
if let Some(paused_gid) = result.as_str() {
|
if let Some(paused_gid) = result.as_str() {
|
||||||
return Ok(paused_gid.to_string());
|
return Ok(paused_gid.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = response.error {
|
if let Some(error) = response.error {
|
||||||
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("无法暂停下载任务".into())
|
Err("无法暂停下载任务".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 恢复下载任务
|
/// 恢复下载任务
|
||||||
///
|
///
|
||||||
/// 恢复指定GID的下载任务
|
/// 恢复指定GID的下载任务
|
||||||
///
|
///
|
||||||
/// # 参数
|
/// # 参数
|
||||||
///
|
///
|
||||||
/// * `gid` - 下载任务的GID
|
/// * `gid` - 下载任务的GID
|
||||||
///
|
///
|
||||||
/// # 返回
|
/// # 返回
|
||||||
///
|
///
|
||||||
/// 成功时返回被恢复的GID
|
/// 成功时返回被恢复的GID
|
||||||
pub async fn unpause(&self, gid: &str) -> Result<String, Box<dyn Error>> {
|
pub async fn unpause(&self, gid: &str) -> Result<String, Box<dyn Error>> {
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if let Some(secret) = &self.secret {
|
if let Some(secret) = &self.secret {
|
||||||
params.push(json!(secret));
|
params.push(json!(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
params.push(json!(gid));
|
params.push(json!(gid));
|
||||||
|
|
||||||
let response = self.send_request("aria2.unpause", params).await?;
|
let response = self.send_request("aria2.unpause", params).await?;
|
||||||
|
|
||||||
if let Some(result) = response.result {
|
if let Some(result) = response.result {
|
||||||
if let Some(unpaused_gid) = result.as_str() {
|
if let Some(unpaused_gid) = result.as_str() {
|
||||||
return Ok(unpaused_gid.to_string());
|
return Ok(unpaused_gid.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = response.error {
|
if let Some(error) = response.error {
|
||||||
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("无法恢复下载任务".into())
|
Err("无法恢复下载任务".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 删除下载任务
|
/// 删除下载任务
|
||||||
///
|
///
|
||||||
/// 删除指定GID的下载任务
|
/// 删除指定GID的下载任务
|
||||||
///
|
///
|
||||||
/// # 参数
|
/// # 参数
|
||||||
///
|
///
|
||||||
/// * `gid` - 下载任务的GID
|
/// * `gid` - 下载任务的GID
|
||||||
///
|
///
|
||||||
/// # 返回
|
/// # 返回
|
||||||
///
|
///
|
||||||
/// 成功时返回被删除的GID
|
/// 成功时返回被删除的GID
|
||||||
pub async fn remove(&self, gid: &str) -> Result<String, Box<dyn Error>> {
|
pub async fn remove(&self, gid: &str) -> Result<String, Box<dyn Error>> {
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if let Some(secret) = &self.secret {
|
if let Some(secret) = &self.secret {
|
||||||
params.push(json!(secret));
|
params.push(json!(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
params.push(json!(gid));
|
params.push(json!(gid));
|
||||||
|
|
||||||
let response = self.send_request("aria2.remove", params).await?;
|
let response = self.send_request("aria2.remove", params).await?;
|
||||||
|
|
||||||
if let Some(result) = response.result {
|
if let Some(result) = response.result {
|
||||||
if let Some(removed_gid) = result.as_str() {
|
if let Some(removed_gid) = result.as_str() {
|
||||||
return Ok(removed_gid.to_string());
|
return Ok(removed_gid.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = response.error {
|
if let Some(error) = response.error {
|
||||||
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
return Err(format!("Aria2 RPC错误: {} ({})", error.message, error.code).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Err("无法删除下载任务".into())
|
Err("无法删除下载任务".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
|
use crate::handlers::server::get_json_server_url;
|
||||||
|
use crate::models::download::{
|
||||||
|
DownloadTask, DownloadTaskResponse, InstallStatus, InstallTask, ResponseStatus,
|
||||||
|
};
|
||||||
|
use crate::utils::{aria2::Aria2Client, UA};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Mutex;
|
use std::net::TcpListener;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::net::TcpListener;
|
use std::sync::Mutex;
|
||||||
use crate::models::download::{DownloadTask, DownloadTaskResponse, InstallStatus, InstallTask, ResponseStatus};
|
|
||||||
use crate::handlers::server::get_json_server_url;
|
|
||||||
use crate::utils::{UA, aria2::Aria2Client};
|
|
||||||
|
|
||||||
use super::format::{format_size, format_speed, format_icon_url};
|
use super::format::{format_icon_url, format_size, format_speed};
|
||||||
|
|
||||||
pub struct DownloadManager {
|
pub struct DownloadManager {
|
||||||
download_queue: Mutex<HashMap<String, DownloadTask>>,
|
download_queue: Mutex<HashMap<String, DownloadTask>>,
|
||||||
@@ -28,7 +30,7 @@ impl DownloadManager {
|
|||||||
aria2_started: Arc::new(AtomicBool::new(false)),
|
aria2_started: Arc::new(AtomicBool::new(false)),
|
||||||
aria2_port: Arc::new(Mutex::new(5144)),
|
aria2_port: Arc::new(Mutex::new(5144)),
|
||||||
aria2_pid: Arc::new(Mutex::new(None)),
|
aria2_pid: Arc::new(Mutex::new(None)),
|
||||||
installing: Arc::new(AtomicBool::new(false)), // 初始化为 false
|
installing: Arc::new(AtomicBool::new(false)), // 初始化为 false
|
||||||
last_get_downloads: Arc::new(Mutex::new(None)),
|
last_get_downloads: Arc::new(Mutex::new(None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,59 +69,68 @@ impl DownloadManager {
|
|||||||
let tasks_clone: Vec<DownloadTask>;
|
let tasks_clone: Vec<DownloadTask>;
|
||||||
let aria2_started;
|
let aria2_started;
|
||||||
let port;
|
let port;
|
||||||
|
|
||||||
{
|
{
|
||||||
// 使用作用域限制锁的生命周期
|
// 使用作用域限制锁的生命周期
|
||||||
let downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
let downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
||||||
tasks_clone = downloads.values().cloned().collect();
|
tasks_clone = downloads.values().cloned().collect();
|
||||||
aria2_started = self.aria2_started.load(Ordering::SeqCst);
|
aria2_started = self.aria2_started.load(Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果 aria2 未启动,直接返回队列中的任务
|
// 如果 aria2 未启动,直接返回队列中的任务
|
||||||
if !aria2_started {
|
if !aria2_started {
|
||||||
return Ok(tasks_clone.into_iter().map(|task| DownloadTaskResponse {
|
return Ok(tasks_clone
|
||||||
category: task.category,
|
.into_iter()
|
||||||
pkgname: task.pkgname,
|
.map(|task| DownloadTaskResponse {
|
||||||
filename: task.filename,
|
category: task.category,
|
||||||
status: ResponseStatus::Error, // 如果 aria2 未启动,标记为错误状态
|
pkgname: task.pkgname,
|
||||||
icon: task.icon,
|
filename: task.filename,
|
||||||
name: task.name,
|
status: ResponseStatus::Error, // 如果 aria2 未启动,标记为错误状态
|
||||||
progress: 0.0,
|
icon: task.icon,
|
||||||
speed: None,
|
name: task.name,
|
||||||
size: None,
|
progress: 0.0,
|
||||||
}).collect());
|
speed: None,
|
||||||
|
size: None,
|
||||||
|
})
|
||||||
|
.collect());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取端口(在单独的作用域中获取锁)
|
// 获取端口(在单独的作用域中获取锁)
|
||||||
{
|
{
|
||||||
port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 Aria2Client 实例
|
// 创建 Aria2Client 实例
|
||||||
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
||||||
|
|
||||||
// 获取所有活动中的下载任务
|
// 获取所有活动中的下载任务
|
||||||
let active_downloads = aria2_client.tell_active().await
|
let active_downloads = aria2_client
|
||||||
|
.tell_active()
|
||||||
|
.await
|
||||||
.map_err(|e| format!("获取活动下载任务失败: {}", e))?;
|
.map_err(|e| format!("获取活动下载任务失败: {}", e))?;
|
||||||
|
|
||||||
// 获取所有等待中的下载任务(最多100个)
|
// 获取所有等待中的下载任务(最多100个)
|
||||||
let waiting_downloads = aria2_client.tell_waiting(0, 100).await
|
let waiting_downloads = aria2_client
|
||||||
|
.tell_waiting(0, 100)
|
||||||
|
.await
|
||||||
.map_err(|e| format!("获取等待中下载任务失败: {}", e))?;
|
.map_err(|e| format!("获取等待中下载任务失败: {}", e))?;
|
||||||
|
|
||||||
// 获取所有已停止的下载任务(最多100个)
|
// 获取所有已停止的下载任务(最多100个)
|
||||||
let stopped_downloads = aria2_client.tell_stopped(0, 100).await
|
let stopped_downloads = aria2_client
|
||||||
|
.tell_stopped(0, 100)
|
||||||
|
.await
|
||||||
.map_err(|e| format!("获取已停止下载任务失败: {}", e))?;
|
.map_err(|e| format!("获取已停止下载任务失败: {}", e))?;
|
||||||
|
|
||||||
// 创建一个映射,用于存储 GID 到 aria2 任务状态的映射
|
// 创建一个映射,用于存储 GID 到 aria2 任务状态的映射
|
||||||
let mut aria2_tasks = HashMap::new();
|
let mut aria2_tasks = HashMap::new();
|
||||||
|
|
||||||
// 处理活动中的下载任务
|
// 处理活动中的下载任务
|
||||||
for task in active_downloads {
|
for task in active_downloads {
|
||||||
if let Some(gid) = task["gid"].as_str() {
|
if let Some(gid) = task["gid"].as_str() {
|
||||||
aria2_tasks.insert(gid.to_string(), (task, ResponseStatus::Downloading));
|
aria2_tasks.insert(gid.to_string(), (task, ResponseStatus::Downloading));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理等待中的下载任务
|
// 处理等待中的下载任务
|
||||||
for task in waiting_downloads {
|
for task in waiting_downloads {
|
||||||
if let Some(gid) = task["gid"].as_str() {
|
if let Some(gid) = task["gid"].as_str() {
|
||||||
@@ -131,7 +142,7 @@ impl DownloadManager {
|
|||||||
aria2_tasks.insert(gid.to_string(), (task, status));
|
aria2_tasks.insert(gid.to_string(), (task, status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理已停止的下载任务
|
// 处理已停止的下载任务
|
||||||
for task in stopped_downloads {
|
for task in stopped_downloads {
|
||||||
if let Some(gid) = task["gid"].as_str() {
|
if let Some(gid) = task["gid"].as_str() {
|
||||||
@@ -139,15 +150,14 @@ impl DownloadManager {
|
|||||||
// 当任务完成时,将其加入到安装队列
|
// 当任务完成时,将其加入到安装队列
|
||||||
let task_info = {
|
let task_info = {
|
||||||
let downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
let downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
||||||
downloads.values()
|
downloads.values().find(|t| t.gid == gid).cloned()
|
||||||
.find(|t| t.gid == gid)
|
|
||||||
.cloned()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(task_info) = task_info {
|
if let Some(task_info) = task_info {
|
||||||
let task_id = format!("{}/{}", task_info.category, task_info.pkgname);
|
let task_id = format!("{}/{}", task_info.category, task_info.pkgname);
|
||||||
let should_process = {
|
let should_process = {
|
||||||
let mut install_queue = self.install_queue.lock().map_err(|e| e.to_string())?;
|
let mut install_queue =
|
||||||
|
self.install_queue.lock().map_err(|e| e.to_string())?;
|
||||||
if !install_queue.contains_key(&task_id) {
|
if !install_queue.contains_key(&task_id) {
|
||||||
let install_task = InstallTask {
|
let install_task = InstallTask {
|
||||||
category: task_info.category.clone(),
|
category: task_info.category.clone(),
|
||||||
@@ -174,16 +184,16 @@ impl DownloadManager {
|
|||||||
aria2_tasks.insert(gid.to_string(), (task, status));
|
aria2_tasks.insert(gid.to_string(), (task, status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将队列中的任务与 aria2 任务状态结合,生成响应
|
// 将队列中的任务与 aria2 任务状态结合,生成响应
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|
||||||
// 获取安装队列(在生成响应之前)
|
// 获取安装队列(在生成响应之前)
|
||||||
let install_queue = self.install_queue.lock().map_err(|e| e.to_string())?;
|
let install_queue = self.install_queue.lock().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
for task in tasks_clone {
|
for task in tasks_clone {
|
||||||
let task_id = format!("{}/{}", task.category, task.pkgname);
|
let task_id = format!("{}/{}", task.category, task.pkgname);
|
||||||
|
|
||||||
let mut response = DownloadTaskResponse {
|
let mut response = DownloadTaskResponse {
|
||||||
category: task.category.clone(),
|
category: task.category.clone(),
|
||||||
pkgname: task.pkgname.clone(),
|
pkgname: task.pkgname.clone(),
|
||||||
@@ -195,7 +205,7 @@ impl DownloadManager {
|
|||||||
speed: None,
|
speed: None,
|
||||||
size: None,
|
size: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 首先检查是否在安装队列中
|
// 首先检查是否在安装队列中
|
||||||
if let Some(install_task) = install_queue.get(&task_id) {
|
if let Some(install_task) = install_queue.get(&task_id) {
|
||||||
response.status = match install_task.status {
|
response.status = match install_task.status {
|
||||||
@@ -208,25 +218,29 @@ impl DownloadManager {
|
|||||||
// 如果不在安装队列中,则检查下载状态
|
// 如果不在安装队列中,则检查下载状态
|
||||||
if let Some((aria2_task, status)) = aria2_tasks.get(&task.gid) {
|
if let Some((aria2_task, status)) = aria2_tasks.get(&task.gid) {
|
||||||
response.status = status.clone();
|
response.status = status.clone();
|
||||||
|
|
||||||
// 计算进度(百分比)
|
// 计算进度(百分比)
|
||||||
if let (Some(completed_length), Some(total_length)) = (
|
if let (Some(completed_length), Some(total_length)) = (
|
||||||
aria2_task["completedLength"].as_str().and_then(|s| s.parse::<f64>().ok()),
|
aria2_task["completedLength"]
|
||||||
aria2_task["totalLength"].as_str().and_then(|s| s.parse::<f64>().ok())
|
.as_str()
|
||||||
|
.and_then(|s| s.parse::<f64>().ok()),
|
||||||
|
aria2_task["totalLength"]
|
||||||
|
.as_str()
|
||||||
|
.and_then(|s| s.parse::<f64>().ok()),
|
||||||
) {
|
) {
|
||||||
if total_length > 0.0 {
|
if total_length > 0.0 {
|
||||||
let progress = ((completed_length / total_length) * 100.0) as f32;
|
let progress = ((completed_length / total_length) * 100.0) as f32;
|
||||||
response.progress = (progress * 100.0).round() / 100.0;
|
response.progress = (progress * 100.0).round() / 100.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取下载速度
|
// 获取下载速度
|
||||||
if let Some(download_speed) = aria2_task["downloadSpeed"].as_str() {
|
if let Some(download_speed) = aria2_task["downloadSpeed"].as_str() {
|
||||||
if let Ok(speed) = download_speed.parse::<u64>() {
|
if let Ok(speed) = download_speed.parse::<u64>() {
|
||||||
response.speed = Some(format_speed(speed));
|
response.speed = Some(format_speed(speed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件大小
|
// 获取文件大小
|
||||||
if let Some(total_length) = aria2_task["totalLength"].as_str() {
|
if let Some(total_length) = aria2_task["totalLength"].as_str() {
|
||||||
if let Ok(size) = total_length.parse::<u64>() {
|
if let Ok(size) = total_length.parse::<u64>() {
|
||||||
@@ -235,10 +249,10 @@ impl DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(response);
|
result.push(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +277,8 @@ impl DownloadManager {
|
|||||||
"--dir=/tmp/spark-store", // 设置下载目录为 /tmp/spark-store
|
"--dir=/tmp/spark-store", // 设置下载目录为 /tmp/spark-store
|
||||||
])
|
])
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| format!("启动 aria2 失败: {}", e)).unwrap();
|
.map_err(|e| format!("启动 aria2 失败: {}", e))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// 保存进程 ID
|
// 保存进程 ID
|
||||||
if let Ok(mut pid_guard) = self.aria2_pid.lock() {
|
if let Ok(mut pid_guard) = self.aria2_pid.lock() {
|
||||||
@@ -274,17 +289,26 @@ impl DownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加下载任务
|
// 添加下载任务
|
||||||
pub async fn add_download(&self, category: String, pkgname: String, filename: String, name: String) -> Result<(), String> {
|
pub async fn add_download(
|
||||||
|
&self,
|
||||||
|
category: String,
|
||||||
|
pkgname: String,
|
||||||
|
filename: String,
|
||||||
|
name: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
// 检查并启动 aria2(如果还没启动)
|
// 检查并启动 aria2(如果还没启动)
|
||||||
if !self.aria2_started.load(Ordering::SeqCst) {
|
if !self.aria2_started.load(Ordering::SeqCst) {
|
||||||
self.start_aria2();
|
self.start_aria2();
|
||||||
}
|
}
|
||||||
|
|
||||||
let task_id = format!("{}/{}", category, pkgname);
|
let task_id = format!("{}/{}", category, pkgname);
|
||||||
|
|
||||||
// 获取metalink文件URL和内容(在获取锁之前完成)
|
// 获取metalink文件URL和内容(在获取锁之前完成)
|
||||||
let json_server_url = get_json_server_url();
|
let json_server_url = get_json_server_url();
|
||||||
let metalink_url = format!("{}{}/{}/{}.metalink", json_server_url, category, pkgname, filename);
|
let metalink_url = format!(
|
||||||
|
"{}{}/{}/{}.metalink",
|
||||||
|
json_server_url, category, pkgname, filename
|
||||||
|
);
|
||||||
|
|
||||||
// 发送请求获取metalink文件
|
// 发送请求获取metalink文件
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
@@ -294,33 +318,31 @@ impl DownloadManager {
|
|||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("获取metalink文件失败: {}", e))?;
|
.map_err(|e| format!("获取metalink文件失败: {}", e))?;
|
||||||
|
|
||||||
// 检查响应状态
|
// 检查响应状态
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
return Err(format!("获取metalink文件失败: HTTP {}", response.status()));
|
return Err(format!("获取metalink文件失败: HTTP {}", response.status()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取metalink文件内容
|
// 获取metalink文件内容
|
||||||
let metalink_content = response.bytes()
|
let metalink_content = response
|
||||||
.await
|
.bytes()
|
||||||
.map_err(|e| format!("读取metalink内容失败: {}", e))?;
|
.await
|
||||||
|
.map_err(|e| format!("读取metalink内容失败: {}", e))?;
|
||||||
|
|
||||||
// 创建Aria2Client并添加下载任务
|
// 创建Aria2Client并添加下载任务
|
||||||
let port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
let port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
||||||
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
||||||
|
|
||||||
// 使用Aria2Client添加metalink下载
|
// 使用Aria2Client添加metalink下载
|
||||||
let gids = aria2_client.add_metalink(
|
let gids = aria2_client
|
||||||
&metalink_content,
|
.add_metalink(&metalink_content, None, None)
|
||||||
None,
|
.await
|
||||||
None
|
.map_err(|e| format!("添加下载任务失败: {}", e))?;
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("添加下载任务失败: {}", e))?;
|
|
||||||
|
|
||||||
// 获取一次锁,完成所有状态更新操作
|
// 获取一次锁,完成所有状态更新操作
|
||||||
let mut downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
let mut downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// 检查任务是否已存在
|
// 检查任务是否已存在
|
||||||
if downloads.contains_key(&task_id) {
|
if downloads.contains_key(&task_id) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -333,19 +355,19 @@ impl DownloadManager {
|
|||||||
filename,
|
filename,
|
||||||
name,
|
name,
|
||||||
icon: format_icon_url(&category, &pkgname),
|
icon: format_icon_url(&category, &pkgname),
|
||||||
gid: gids[0].clone()
|
gid: gids[0].clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加任务到队列
|
// 添加任务到队列
|
||||||
downloads.insert(task_id, task);
|
downloads.insert(task_id, task);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暂停下载任务
|
// 暂停下载任务
|
||||||
pub async fn pause_download(&self, category: String, pkgname: String) -> Result<(), String> {
|
pub async fn pause_download(&self, category: String, pkgname: String) -> Result<(), String> {
|
||||||
let task_id = format!("{}/{}", category, pkgname);
|
let task_id = format!("{}/{}", category, pkgname);
|
||||||
|
|
||||||
// 获取任务信息,并在作用域结束时释放锁
|
// 获取任务信息,并在作用域结束时释放锁
|
||||||
let task_gid = {
|
let task_gid = {
|
||||||
let downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
let downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
||||||
@@ -354,27 +376,29 @@ impl DownloadManager {
|
|||||||
None => return Err(format!("找不到下载任务: {}", task_id)),
|
None => return Err(format!("找不到下载任务: {}", task_id)),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果 aria2 未启动,返回错误
|
// 如果 aria2 未启动,返回错误
|
||||||
if !self.aria2_started.load(Ordering::SeqCst) {
|
if !self.aria2_started.load(Ordering::SeqCst) {
|
||||||
return Err("aria2 未启动".to_string());
|
return Err("aria2 未启动".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 Aria2Client 实例
|
// 创建 Aria2Client 实例
|
||||||
let port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
let port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
||||||
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
||||||
|
|
||||||
// 调用 aria2 的 pause 方法
|
// 调用 aria2 的 pause 方法
|
||||||
aria2_client.pause(task_gid.as_str()).await
|
aria2_client
|
||||||
|
.pause(task_gid.as_str())
|
||||||
|
.await
|
||||||
.map_err(|e| format!("暂停下载任务失败: {}", e))?;
|
.map_err(|e| format!("暂停下载任务失败: {}", e))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复下载任务
|
// 恢复下载任务
|
||||||
pub async fn resume_download(&self, category: String, pkgname: String) -> Result<(), String> {
|
pub async fn resume_download(&self, category: String, pkgname: String) -> Result<(), String> {
|
||||||
let task_id = format!("{}/{}", category, pkgname);
|
let task_id = format!("{}/{}", category, pkgname);
|
||||||
|
|
||||||
// 获取任务信息,并在作用域结束时释放锁
|
// 获取任务信息,并在作用域结束时释放锁
|
||||||
let task_gid = {
|
let task_gid = {
|
||||||
let downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
let downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
||||||
@@ -383,27 +407,29 @@ impl DownloadManager {
|
|||||||
None => return Err(format!("找不到下载任务: {}", task_id)),
|
None => return Err(format!("找不到下载任务: {}", task_id)),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果 aria2 未启动,返回错误
|
// 如果 aria2 未启动,返回错误
|
||||||
if !self.aria2_started.load(Ordering::SeqCst) {
|
if !self.aria2_started.load(Ordering::SeqCst) {
|
||||||
return Err("aria2 未启动".to_string());
|
return Err("aria2 未启动".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 Aria2Client 实例
|
// 创建 Aria2Client 实例
|
||||||
let port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
let port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
||||||
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
||||||
|
|
||||||
// 调用 aria2 的 unpause 方法
|
// 调用 aria2 的 unpause 方法
|
||||||
aria2_client.unpause(task_gid.as_str()).await
|
aria2_client
|
||||||
|
.unpause(task_gid.as_str())
|
||||||
|
.await
|
||||||
.map_err(|e| format!("恢复下载任务失败: {}", e))?;
|
.map_err(|e| format!("恢复下载任务失败: {}", e))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消下载任务
|
// 取消下载任务
|
||||||
pub async fn cancel_download(&self, category: String, pkgname: String) -> Result<(), String> {
|
pub async fn cancel_download(&self, category: String, pkgname: String) -> Result<(), String> {
|
||||||
let task_id = format!("{}/{}", category, pkgname);
|
let task_id = format!("{}/{}", category, pkgname);
|
||||||
|
|
||||||
// 获取任务信息,并在作用域结束时释放锁
|
// 获取任务信息,并在作用域结束时释放锁
|
||||||
let task_gid = {
|
let task_gid = {
|
||||||
let mut downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
let mut downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
||||||
@@ -415,23 +441,25 @@ impl DownloadManager {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
task.gid.clone()
|
task.gid.clone()
|
||||||
},
|
}
|
||||||
None => return Err(format!("找不到下载任务: {}", task_id)),
|
None => return Err(format!("找不到下载任务: {}", task_id)),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建 Aria2Client 实例
|
// 创建 Aria2Client 实例
|
||||||
let port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
let port = *self.aria2_port.lock().map_err(|e| e.to_string())?;
|
||||||
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
let aria2_client = Aria2Client::new("127.0.0.1", port, None);
|
||||||
|
|
||||||
// 调用 aria2 的 remove 方法
|
// 调用 aria2 的 remove 方法
|
||||||
aria2_client.remove(task_gid.as_str()).await
|
aria2_client
|
||||||
|
.remove(task_gid.as_str())
|
||||||
|
.await
|
||||||
.map_err(|e| format!("取消下载任务失败: {}", e))?;
|
.map_err(|e| format!("取消下载任务失败: {}", e))?;
|
||||||
|
|
||||||
// 从队列中移除任务
|
// 从队列中移除任务
|
||||||
let mut downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
let mut downloads = self.download_queue.lock().map_err(|e| e.to_string())?;
|
||||||
downloads.remove(&task_id);
|
downloads.remove(&task_id);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,9 +470,7 @@ impl DownloadManager {
|
|||||||
if let Ok(pid_guard) = self.aria2_pid.lock() {
|
if let Ok(pid_guard) = self.aria2_pid.lock() {
|
||||||
if let Some(pid) = *pid_guard {
|
if let Some(pid) = *pid_guard {
|
||||||
// 使用 kill 命令终止特定的进程
|
// 使用 kill 命令终止特定的进程
|
||||||
if let Ok(output) = Command::new("kill")
|
if let Ok(output) = Command::new("kill").arg(pid.to_string()).output() {
|
||||||
.arg(pid.to_string())
|
|
||||||
.output() {
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
println!("成功关闭 aria2 (PID: {})", pid);
|
println!("成功关闭 aria2 (PID: {})", pid);
|
||||||
} else {
|
} else {
|
||||||
@@ -467,7 +493,8 @@ impl DownloadManager {
|
|||||||
// 查找第一个等待安装的任务
|
// 查找第一个等待安装的任务
|
||||||
let (task_id, task) = {
|
let (task_id, task) = {
|
||||||
let mut install_queue = self.install_queue.lock().map_err(|e| e.to_string())?;
|
let mut install_queue = self.install_queue.lock().map_err(|e| e.to_string())?;
|
||||||
if let Some((id, task)) = install_queue.iter_mut()
|
if let Some((id, task)) = install_queue
|
||||||
|
.iter_mut()
|
||||||
.find(|(_, task)| matches!(task.status, InstallStatus::Queued))
|
.find(|(_, task)| matches!(task.status, InstallStatus::Queued))
|
||||||
.map(|(id, task)| (id.clone(), task.clone()))
|
.map(|(id, task)| (id.clone(), task.clone()))
|
||||||
{
|
{
|
||||||
@@ -512,7 +539,7 @@ impl DownloadManager {
|
|||||||
// 实际执行安装的方法
|
// 实际执行安装的方法
|
||||||
async fn install_package(&self, task: &InstallTask) -> Result<(), String> {
|
async fn install_package(&self, task: &InstallTask) -> Result<(), String> {
|
||||||
println!("开始安装包: {}", task.filepath);
|
println!("开始安装包: {}", task.filepath);
|
||||||
|
|
||||||
// 移除可能存在的引号
|
// 移除可能存在的引号
|
||||||
let filepath = task.filepath.trim_matches('"');
|
let filepath = task.filepath.trim_matches('"');
|
||||||
|
|
||||||
@@ -546,4 +573,4 @@ impl DownloadManager {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ pub fn format_size(size: u64) -> String {
|
|||||||
const KB: u64 = 1024;
|
const KB: u64 = 1024;
|
||||||
const MB: u64 = KB * 1024;
|
const MB: u64 = KB * 1024;
|
||||||
const GB: u64 = MB * 1024;
|
const GB: u64 = MB * 1024;
|
||||||
|
|
||||||
if size >= GB {
|
if size >= GB {
|
||||||
format!("{:.2} GB", size as f64 / GB as f64)
|
format!("{:.2} GB", size as f64 / GB as f64)
|
||||||
} else if size >= MB {
|
} else if size >= MB {
|
||||||
@@ -24,4 +24,4 @@ pub fn format_speed(speed: u64) -> String {
|
|||||||
|
|
||||||
pub fn format_icon_url(category: &str, pkgname: &str) -> String {
|
pub fn format_icon_url(category: &str, pkgname: &str) -> String {
|
||||||
format!("{}{}/{}/icon.png", get_img_server_url(), category, pkgname)
|
format!("{}{}/{}/icon.png", get_img_server_url(), category, pkgname)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
pub mod search;
|
|
||||||
pub mod download_manager;
|
|
||||||
pub mod aria2;
|
pub mod aria2;
|
||||||
|
pub mod download_manager;
|
||||||
pub mod format;
|
pub mod format;
|
||||||
|
pub mod search;
|
||||||
|
|
||||||
pub const UA: &str = concat!("Spark-Store/", env!("CARGO_PKG_VERSION"));
|
pub const UA: &str = concat!("Spark-Store/", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_user_agent() -> String {
|
pub fn get_user_agent() -> String {
|
||||||
UA.into()
|
UA.into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ use std::collections::HashSet;
|
|||||||
pub fn match_text(text: &str, query: &str) -> bool {
|
pub fn match_text(text: &str, query: &str) -> bool {
|
||||||
let text_lower = text.to_lowercase();
|
let text_lower = text.to_lowercase();
|
||||||
let query_lower = query.to_lowercase();
|
let query_lower = query.to_lowercase();
|
||||||
|
|
||||||
// 直接匹配原文
|
// 直接匹配原文
|
||||||
if text_lower.contains(&query_lower) {
|
if text_lower.contains(&query_lower) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文本的拼音并转换为小写
|
// 获取文本的拼音并转换为小写
|
||||||
let text_pinyin: String = text
|
let text_pinyin: String = text
|
||||||
.to_pinyin()
|
.to_pinyin()
|
||||||
.filter_map(|p| p)
|
.filter_map(|p| p)
|
||||||
.map(|p| p.plain().to_lowercase())
|
.map(|p| p.plain().to_lowercase())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// 获取查询的拼音并转换为小写
|
// 获取查询的拼音并转换为小写
|
||||||
// 判断查询是否为纯英文
|
// 判断查询是否为纯英文
|
||||||
let query_pinyin = if query.chars().all(|c| c.is_ascii_alphabetic()) {
|
let query_pinyin = if query.chars().all(|c| c.is_ascii_alphabetic()) {
|
||||||
@@ -28,33 +28,37 @@ pub fn match_text(text: &str, query: &str) -> bool {
|
|||||||
.map(|p| p.plain().to_lowercase())
|
.map(|p| p.plain().to_lowercase())
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
// 匹配拼音
|
// 匹配拼音
|
||||||
text_pinyin.contains(&query_pinyin)
|
text_pinyin.contains(&query_pinyin)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_apps(apps: &[crate::models::app::AppItem], query: &str) -> Vec<crate::models::app::AppItem> {
|
pub fn search_apps(
|
||||||
|
apps: &[crate::models::app::AppItem],
|
||||||
|
query: &str,
|
||||||
|
) -> Vec<crate::models::app::AppItem> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
return apps.to_vec();
|
return apps.to_vec();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
let mut seen = HashSet::new();
|
let mut seen = HashSet::new();
|
||||||
|
|
||||||
for app in apps {
|
for app in apps {
|
||||||
// 避免重复
|
// 避免重复
|
||||||
if seen.contains(&app.pkgname) {
|
if seen.contains(&app.pkgname) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 匹配名称、包名和描述
|
// 匹配名称、包名和描述
|
||||||
if match_text(&app.name, query) ||
|
if match_text(&app.name, query)
|
||||||
match_text(&app.pkgname, query) ||
|
|| match_text(&app.pkgname, query)
|
||||||
match_text(&app.more, query) {
|
|| match_text(&app.more, query)
|
||||||
|
{
|
||||||
results.push(app.clone());
|
results.push(app.clone());
|
||||||
seen.insert(app.pkgname.clone());
|
seen.insert(app.pkgname.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
{
|
{
|
||||||
"title": "spark-store",
|
"title": "spark-store",
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 600
|
"height": 600,
|
||||||
|
"minWidth": 355,
|
||||||
|
"minHeight": 510
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
|
|||||||
@@ -702,6 +702,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@tauri-apps/api" "^2.0.0"
|
"@tauri-apps/api" "^2.0.0"
|
||||||
|
|
||||||
|
"@tauri-apps/plugin-window-state@~2":
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.npmmirror.com/@tauri-apps/plugin-window-state/-/plugin-window-state-2.2.1.tgz#11bbc9d4445c0ef31893198b2d9d949c4f02a454"
|
||||||
|
integrity sha512-L7FhG/ocQNt8t+TMBkvl8eLhCU6I19t848unKMUgNHuvwHPaurzZr4knulNyKzqz7zVYSz9AdvgWy4915eq+AA==
|
||||||
|
dependencies:
|
||||||
|
"@tauri-apps/api" "^2.0.0"
|
||||||
|
|
||||||
"@types/babel__core@^7.20.4":
|
"@types/babel__core@^7.20.4":
|
||||||
version "7.20.5"
|
version "7.20.5"
|
||||||
resolved "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
|
resolved "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
|
||||||
|
|||||||
Reference in New Issue
Block a user