Skip to main content

mangofetch_core/models/
settings.rs

1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct AppSettings {
6    pub schema_version: u32,
7    pub appearance: AppearanceSettings,
8    pub download: DownloadSettings,
9    pub advanced: AdvancedSettings,
10    #[serde(default)]
11    pub telegram: TelegramSettings,
12    #[serde(default)]
13    pub proxy: ProxySettings,
14    #[serde(default)]
15    pub onboarding_completed: bool,
16    #[serde(default)]
17    pub start_with_windows: bool,
18    #[serde(default)]
19    pub portable_mode: bool,
20    #[serde(default)]
21    pub legal_acknowledged: bool,
22    #[serde(default)]
23    pub last_download_options: LastDownloadOptions,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27pub struct LastDownloadOptions {
28    #[serde(default)]
29    pub mode: Option<String>,
30    #[serde(default)]
31    pub quality: Option<String>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct AppearanceSettings {
36    pub theme: String,
37    pub language: String,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct DownloadSettings {
42    pub default_output_dir: PathBuf,
43    pub always_ask_path: bool,
44    pub video_quality: String,
45    pub skip_existing: bool,
46    pub download_attachments: bool,
47    pub download_descriptions: bool,
48    #[serde(default = "default_true")]
49    pub embed_metadata: bool,
50    #[serde(default = "default_true")]
51    pub embed_thumbnail: bool,
52    #[serde(default)]
53    pub clipboard_detection: bool,
54    #[serde(default = "default_filename_template")]
55    pub filename_template: String,
56    #[serde(default)]
57    pub organize_by_platform: bool,
58    #[serde(default)]
59    pub download_subtitles: bool,
60    #[serde(default)]
61    pub include_auto_subtitles: bool,
62    #[serde(default)]
63    pub translate_metadata: bool,
64    #[serde(default)]
65    pub youtube_sponsorblock: bool,
66    #[serde(default)]
67    pub split_by_chapters: bool,
68    #[serde(default)]
69    pub hotkey_enabled: bool,
70    #[serde(default = "default_hotkey_binding")]
71    pub hotkey_binding: String,
72    #[serde(default)]
73    pub extra_ytdlp_flags: Vec<String>,
74    #[serde(default = "default_true")]
75    pub copy_to_clipboard_on_hotkey: bool,
76    #[serde(default)]
77    pub cookie_file: String,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct AdvancedSettings {
82    pub max_concurrent_segments: u32,
83    pub max_retries: u32,
84    #[serde(default = "default_max_concurrent_downloads")]
85    pub max_concurrent_downloads: u32,
86    #[serde(default = "default_concurrent_fragments")]
87    pub concurrent_fragments: u32,
88    #[serde(default = "default_stagger_delay_ms")]
89    pub stagger_delay_ms: u64,
90    #[serde(default = "default_torrent_listen_port")]
91    pub torrent_listen_port: u16,
92    #[serde(default)]
93    pub cookies_from_browser: String,
94    #[serde(default)]
95    pub twitter_manual_cookie: String,
96}
97
98fn default_concurrent_fragments() -> u32 {
99    8
100}
101
102fn default_max_concurrent_downloads() -> u32 {
103    2
104}
105
106fn default_stagger_delay_ms() -> u64 {
107    150
108}
109
110fn default_torrent_listen_port() -> u16 {
111    6881
112}
113
114fn default_true() -> bool {
115    true
116}
117
118pub fn default_filename_template() -> String {
119    "%(title).200s [%(id)s].%(ext)s".into()
120}
121
122fn default_hotkey_binding() -> String {
123    "CmdOrCtrl+Shift+D".into()
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct TelegramSettings {
128    pub concurrent_downloads: u32,
129    pub fix_file_extensions: bool,
130}
131
132impl Default for TelegramSettings {
133    fn default() -> Self {
134        Self {
135            concurrent_downloads: 3,
136            fix_file_extensions: true,
137        }
138    }
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, Default)]
142pub struct ProxySettings {
143    #[serde(default)]
144    pub enabled: bool,
145    #[serde(default = "default_proxy_type")]
146    pub proxy_type: String,
147    #[serde(default)]
148    pub host: String,
149    #[serde(default = "default_proxy_port")]
150    pub port: u16,
151    #[serde(default)]
152    pub username: String,
153    #[serde(default)]
154    pub password: String,
155}
156
157fn default_proxy_type() -> String {
158    "http".into()
159}
160
161fn default_proxy_port() -> u16 {
162    8080
163}
164
165impl Default for AppSettings {
166    fn default() -> Self {
167        Self {
168            schema_version: 1,
169            appearance: AppearanceSettings {
170                theme: "system".into(),
171                language: "en".into(),
172            },
173            download: DownloadSettings {
174                default_output_dir: dirs::download_dir().unwrap_or_else(|| PathBuf::from(".")),
175                always_ask_path: true,
176                video_quality: "720p".into(),
177                skip_existing: true,
178                download_attachments: true,
179                download_descriptions: true,
180                embed_metadata: true,
181                embed_thumbnail: true,
182                clipboard_detection: false,
183                filename_template: default_filename_template(),
184                organize_by_platform: false,
185                download_subtitles: false,
186                include_auto_subtitles: false,
187                translate_metadata: false,
188                youtube_sponsorblock: false,
189                split_by_chapters: false,
190                hotkey_enabled: false,
191                hotkey_binding: default_hotkey_binding(),
192                extra_ytdlp_flags: Vec::new(),
193                copy_to_clipboard_on_hotkey: true,
194                cookie_file: String::new(),
195            },
196            advanced: AdvancedSettings {
197                max_concurrent_segments: 20,
198                max_retries: 3,
199                max_concurrent_downloads: 2,
200                concurrent_fragments: 8,
201                stagger_delay_ms: 150,
202                torrent_listen_port: 6881,
203                cookies_from_browser: String::new(),
204                twitter_manual_cookie: String::new(),
205            },
206            telegram: TelegramSettings::default(),
207            proxy: ProxySettings::default(),
208            onboarding_completed: false,
209            start_with_windows: false,
210            portable_mode: false,
211            legal_acknowledged: false,
212            last_download_options: LastDownloadOptions::default(),
213        }
214    }
215}
216
217impl AppSettings {
218    pub fn load_from_disk() -> Self {
219        let data_dir = match crate::core::paths::app_data_dir() {
220            Some(d) => d,
221            None => return Self::default(),
222        };
223        let store_path = data_dir.join("settings.json");
224        let content = match std::fs::read_to_string(&store_path) {
225            Ok(c) => c,
226            Err(_) => return Self::default(),
227        };
228        let json: serde_json::Value = match serde_json::from_str(&content) {
229            Ok(v) => v,
230            Err(_) => return Self::default(),
231        };
232        match json.get("app_settings") {
233            Some(val) => serde_json::from_value::<Self>(val.clone()).unwrap_or_default(),
234            None => Self::default(),
235        }
236    }
237
238    pub fn save_to_disk(&self) -> anyhow::Result<()> {
239        let data_dir = crate::core::paths::app_data_dir()
240            .ok_or_else(|| anyhow::anyhow!("Could not find app data dir"))?;
241        let store_path = data_dir.join("settings.json");
242
243        let mut json = if store_path.exists() {
244            let content = std::fs::read_to_string(&store_path)?;
245            serde_json::from_str::<serde_json::Value>(&content).unwrap_or(serde_json::json!({}))
246        } else {
247            serde_json::json!({})
248        };
249
250        if let Some(obj) = json.as_object_mut() {
251            obj.insert("app_settings".to_string(), serde_json::to_value(self)?);
252        }
253
254        let serialized = serde_json::to_string_pretty(&json)?;
255        std::fs::write(store_path, serialized)?;
256        Ok(())
257    }
258}