添加记录上次关闭窗口大小/默认窗口大小

spark-store-private/spark-store#9
This commit is contained in:
柚子
2025-03-07 15:08:45 +08:00
parent e0ad5b62df
commit d440de22c7
25 changed files with 427 additions and 325 deletions

View File

@@ -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
View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,14 @@
{
"identifier": "desktop-capability",
"platforms": [
"macOS",
"windows",
"linux"
],
"windows": [
"main"
],
"permissions": [
"window-state:default"
]
}

View File

@@ -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> {
@@ -23,22 +27,27 @@ pub async fn get_app_info(category: String, pkgname: String) -> Result<AppDetail
.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(
get_download_times(category, pkgname)
.await .await
.map_err(|e| format!("获取下载次数失败: {}", e))?); .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

View File

@@ -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());
@@ -32,10 +32,7 @@ pub async fn get_category_apps(category_id: String) -> Result<Vec<AppItem>, Stri
.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 {
@@ -44,7 +41,10 @@ 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)
} }
@@ -63,7 +63,7 @@ pub async fn get_all_apps() -> Result<Vec<AppItem>, String> {
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),
} }
} }
@@ -89,10 +89,7 @@ pub async fn get_all_categories() -> Result<Vec<Category>, String> {
.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());

View File

@@ -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
} }

View File

@@ -1,8 +1,8 @@
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> {
@@ -19,14 +19,11 @@ pub async fn get_home_links() -> Result<Vec<HomeLink>, String> {
.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 {
@@ -49,13 +46,10 @@ pub async fn get_home_list_apps(json_url: String) -> Result<Vec<HomeListApp>, St
.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())?;
let mut apps: Vec<HomeListApp> = serde_json::from_str(&response_text) let mut apps: Vec<HomeListApp> =
.map_err(|e| e.to_string())?; 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));
@@ -77,13 +71,9 @@ pub async fn get_home_lists() -> Result<Vec<HomeList>, String> {
.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())?;
let lists: Vec<HomeList> = serde_json::from_str(&response_text) let lists: Vec<HomeList> = serde_json::from_str(&response_text).map_err(|e| e.to_string())?;
.map_err(|e| e.to_string())?;
Ok(lists) Ok(lists)
} }

View File

@@ -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 download;
pub mod deb; pub mod deb;
pub mod download;
pub mod file;
pub mod home;
pub mod server;

View File

@@ -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())

View File

@@ -1,6 +1,6 @@
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,

View File

@@ -1,4 +1,4 @@
pub mod category;
pub mod app; pub mod app;
pub mod home; pub mod category;
pub mod download; pub mod download;
pub mod home;

View File

@@ -121,7 +121,8 @@ impl Aria2Client {
params, params,
}; };
let response = self.client let response = self
.client
.post(&self.rpc_url) .post(&self.rpc_url)
.json(&request) .json(&request)
.send() .send()
@@ -196,7 +197,11 @@ impl Aria2Client {
/// # 返回 /// # 返回
/// ///
/// 返回包含等待中下载任务信息的数组 /// 返回包含等待中下载任务信息的数组
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 {
@@ -233,7 +238,11 @@ impl Aria2Client {
/// # 返回 /// # 返回
/// ///
/// 返回包含已停止下载任务信息的数组 /// 返回包含已停止下载任务信息的数组
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 {

View File

@@ -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>>,
@@ -77,7 +79,9 @@ impl DownloadManager {
// 如果 aria2 未启动,直接返回队列中的任务 // 如果 aria2 未启动,直接返回队列中的任务
if !aria2_started { if !aria2_started {
return Ok(tasks_clone.into_iter().map(|task| DownloadTaskResponse { return Ok(tasks_clone
.into_iter()
.map(|task| DownloadTaskResponse {
category: task.category, category: task.category,
pkgname: task.pkgname, pkgname: task.pkgname,
filename: task.filename, filename: task.filename,
@@ -87,7 +91,8 @@ impl DownloadManager {
progress: 0.0, progress: 0.0,
speed: None, speed: None,
size: None, size: None,
}).collect()); })
.collect());
} }
// 获取端口(在单独的作用域中获取锁) // 获取端口(在单独的作用域中获取锁)
@@ -99,15 +104,21 @@ impl DownloadManager {
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 任务状态的映射
@@ -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(),
@@ -211,8 +221,12 @@ impl DownloadManager {
// 计算进度(百分比) // 计算进度(百分比)
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;
@@ -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,7 +289,13 @@ 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();
@@ -284,7 +305,10 @@ impl DownloadManager {
// 获取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();
@@ -301,7 +325,8 @@ impl DownloadManager {
} }
// 获取metalink文件内容 // 获取metalink文件内容
let metalink_content = response.bytes() let metalink_content = response
.bytes()
.await .await
.map_err(|e| format!("读取metalink内容失败: {}", e))?; .map_err(|e| format!("读取metalink内容失败: {}", e))?;
@@ -310,11 +335,8 @@ impl DownloadManager {
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,
None
)
.await .await
.map_err(|e| format!("添加下载任务失败: {}", e))?; .map_err(|e| format!("添加下载任务失败: {}", e))?;
@@ -333,7 +355,7 @@ 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(),
}; };
// 添加任务到队列 // 添加任务到队列
@@ -365,7 +387,9 @@ impl DownloadManager {
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(())
@@ -394,7 +418,9 @@ impl DownloadManager {
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(())
@@ -415,7 +441,7 @@ impl DownloadManager {
return Ok(()); return Ok(());
} }
task.gid.clone() task.gid.clone()
}, }
None => return Err(format!("找不到下载任务: {}", task_id)), None => return Err(format!("找不到下载任务: {}", task_id)),
} }
}; };
@@ -425,7 +451,9 @@ impl DownloadManager {
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))?;
// 从队列中移除任务 // 从队列中移除任务
@@ -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()))
{ {

View File

@@ -1,7 +1,7 @@
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"));

View File

@@ -33,7 +33,10 @@ pub fn match_text(text: &str, query: &str) -> bool {
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();
} }
@@ -48,9 +51,10 @@ pub fn search_apps(apps: &[crate::models::app::AppItem], query: &str) -> Vec<cra
} }
// 匹配名称、包名和描述 // 匹配名称、包名和描述
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());
} }

View File

@@ -14,7 +14,9 @@
{ {
"title": "spark-store", "title": "spark-store",
"width": 800, "width": 800,
"height": 600 "height": 600,
"minWidth": 355,
"minHeight": 510
} }
], ],
"security": { "security": {

View File

@@ -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"