feat: implement settings engine with directory picker and aria2 configuration sync

This commit is contained in:
NimBold
2026-06-16 15:10:29 +03:30
parent 275eb6e5c4
commit 85cdabd72b
5 changed files with 140 additions and 5 deletions
+25 -4
View File
@@ -1435,10 +1435,26 @@ pub fn run() {
}
crate::scheduler::spawn_scheduler(app.handle().clone());
let mut global_speed_limit = String::new();
{
use tauri_plugin_store::StoreExt;
if let Ok(store) = app.handle().store("store.bin") {
if let Some(settings_val) = store.get("settings") {
if let Some(settings_str) = settings_val.as_str() {
if let Ok(settings_json) = serde_json::from_str::<serde_json::Value>(settings_str) {
if let Some(limit) = settings_json.get("globalSpeedLimit").and_then(|v| v.as_str()) {
global_speed_limit = limit.to_string();
}
}
}
}
}
}
use tauri_plugin_shell::ShellExt;
let aria2_process = match app.handle().shell().sidecar("aria2c") {
Ok(cmd) => {
cmd.arg("--enable-rpc=true")
Ok(mut cmd) => {
cmd = cmd.arg("--enable-rpc=true")
.arg(format!("--rpc-listen-port={}", aria2_port))
.arg(format!("--rpc-secret={}", aria2_secret))
.arg("--rpc-listen-all=false")
@@ -1447,8 +1463,13 @@ pub fn run() {
.arg("--summary-interval=1")
.arg("--console-log-level=warn")
.arg("--download-result=hide")
.arg("--check-certificate=true")
.spawn()
.arg("--check-certificate=true");
if !global_speed_limit.is_empty() {
cmd = cmd.arg(format!("--max-overall-download-limit={}", global_speed_limit));
}
cmd.spawn()
.map(|(_, child)| child)
.ok()
}
+1 -1
View File
@@ -34,7 +34,7 @@ function App() {
const doneCount = downloads.filter(download => download.status === 'completed').length;
const schedulerRunning = useSettingsStore(state => state.schedulerRunning);
const globalSpeedLimit = useSettingsStore(state => state.globalSpeedLimit);
const previousSpeedLimit = useRef(globalSpeedLimit);
const previousSpeedLimit = useRef<string | null>(null);
const maxConcurrentDownloads = useSettingsStore(state => state.maxConcurrentDownloads);
const startSidebarResize = (event: React.PointerEvent<HTMLDivElement>) => {
+26
View File
@@ -5,6 +5,7 @@ import {
SettingsTab,
useSettingsStore
} from '../store/useSettingsStore';
import { useDirectoryPicker } from '../hooks/useDirectoryPicker';
import {
Download, Palette, Globe, Folder, Key,
Moon, Terminal, Puzzle, Info, Plus, Trash2, Copy, RefreshCw, Code
@@ -29,6 +30,7 @@ const settingsTabs: { type: SettingsTab; label: string; icon: typeof Download }[
export default function SettingsView() {
const settings = useSettingsStore();
const { pickDirectory } = useDirectoryPicker();
const activeTab = settings.activeSettingsTab;
// Local state for versions
@@ -537,6 +539,30 @@ export default function SettingsView() {
{/* Locations Pane */}
{activeTab === 'locations' && (
<div className="settings-pane max-w-[760px]">
<div className="mac-settings-group">
<div className="mac-settings-row">
<span className="text-[13px] font-semibold text-text-primary">Default Download Path</span>
<div className="flex gap-2">
<input
type="text"
value={settings.defaultDownloadPath || ''}
onChange={(e) => settings.setDefaultDownloadPath(e.target.value)}
className="app-control w-64 text-[11px] px-2"
placeholder="~/Downloads"
/>
<button
onClick={async () => {
const path = await pickDirectory(settings.defaultDownloadPath);
if (path) settings.setDefaultDownloadPath(path);
}}
className="app-button px-3 text-xs text-text-secondary hover:bg-item-hover"
>
Browse
</button>
</div>
</div>
</div>
<div className="mac-settings-group">
<label className="mac-settings-row cursor-default">
<span className="text-[13px] text-text-primary">Ask where to save each file</span>
+22
View File
@@ -0,0 +1,22 @@
import { open } from '@tauri-apps/plugin-dialog';
export const useDirectoryPicker = () => {
const pickDirectory = async (defaultPath?: string): Promise<string | null> => {
try {
const selected = await open({
directory: true,
multiple: false,
defaultPath: defaultPath,
});
if (selected && typeof selected === 'string') {
return selected;
}
return null;
} catch (e) {
console.error('Failed to pick directory:', e);
return null;
}
};
return { pickDirectory };
};
+66
View File
@@ -0,0 +1,66 @@
import { create } from 'zustand';
import { persist, createJSONStorage, StateStorage } from 'zustand/middleware';
import { LazyStore } from '@tauri-apps/plugin-store';
export const tauriStore = new LazyStore('store.bin');
const tauriStorage: StateStorage = {
getItem: async (name: string): Promise<string | null> => {
if (name === 'firelink-engine-settings') {
try {
const data = await tauriStore.get<string>('engine_settings');
return data || null;
} catch (e) {
console.error("Failed to load engine settings from DB", e);
return null;
}
}
return null;
},
setItem: async (name: string, value: string): Promise<void> => {
if (name === 'firelink-engine-settings') {
try {
await tauriStore.set('engine_settings', value);
await tauriStore.save();
} catch (e) {
console.error("Failed to save engine settings to DB", e);
}
}
},
removeItem: async (_name: string): Promise<void> => {
// no-op for now
},
};
export interface SettingsState {
defaultDownloadPath: string;
globalSpeedLimit: number;
concurrentDownloads: number;
setDefaultDownloadPath: (path: string) => void;
setGlobalSpeedLimit: (limit: number) => void;
setConcurrentDownloads: (count: number) => void;
}
export const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
defaultDownloadPath: '~/Downloads',
globalSpeedLimit: 0,
concurrentDownloads: 3,
setDefaultDownloadPath: (path) => set({ defaultDownloadPath: path }),
setGlobalSpeedLimit: (limit) => set({ globalSpeedLimit: limit }),
setConcurrentDownloads: (count) => set({ concurrentDownloads: count }),
}),
{
name: 'firelink-engine-settings',
storage: createJSONStorage(() => tauriStorage),
partialize: (state) => ({
defaultDownloadPath: state.defaultDownloadPath,
globalSpeedLimit: state.globalSpeedLimit,
concurrentDownloads: state.concurrentDownloads,
}),
}
)
);