@bghitcode/bghitapp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/dist/cli.js +2995 -0
- package/package.json +104 -0
- package/src-tauri/Cargo.lock +5966 -0
- package/src-tauri/Cargo.toml +59 -0
- package/src-tauri/Info.plist +14 -0
- package/src-tauri/assets/macos/dmg/background.png +0 -0
- package/src-tauri/assets/main.wxs +350 -0
- package/src-tauri/bghitapp.json +42 -0
- package/src-tauri/build.rs +5 -0
- package/src-tauri/capabilities/default.json +29 -0
- package/src-tauri/entitlements.plist +7 -0
- package/src-tauri/icons/chatgpt.icns +0 -0
- package/src-tauri/icons/deepseek.icns +0 -0
- package/src-tauri/icons/excalidraw.icns +0 -0
- package/src-tauri/icons/flomo.icns +0 -0
- package/src-tauri/icons/gemini.icns +0 -0
- package/src-tauri/icons/grok.icns +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/icons/lizhi.icns +0 -0
- package/src-tauri/icons/programmusic.icns +0 -0
- package/src-tauri/icons/qwerty.icns +0 -0
- package/src-tauri/icons/twitter.icns +0 -0
- package/src-tauri/icons/wechat.icns +0 -0
- package/src-tauri/icons/weekly.icns +0 -0
- package/src-tauri/icons/weread.icns +0 -0
- package/src-tauri/icons/xiaohongshu.icns +0 -0
- package/src-tauri/icons/youtube.icns +0 -0
- package/src-tauri/icons/youtubemusic.icns +0 -0
- package/src-tauri/rust_proxy.toml +10 -0
- package/src-tauri/src/app/config.rs +100 -0
- package/src-tauri/src/app/invoke.rs +242 -0
- package/src-tauri/src/app/menu.rs +324 -0
- package/src-tauri/src/app/mod.rs +6 -0
- package/src-tauri/src/app/setup.rs +172 -0
- package/src-tauri/src/app/window.rs +577 -0
- package/src-tauri/src/inject/auth.js +75 -0
- package/src-tauri/src/inject/custom.js +0 -0
- package/src-tauri/src/inject/event.js +1111 -0
- package/src-tauri/src/inject/find.js +708 -0
- package/src-tauri/src/inject/fullscreen.js +253 -0
- package/src-tauri/src/inject/offline.js +68 -0
- package/src-tauri/src/inject/splash-transition.js +13 -0
- package/src-tauri/src/inject/style.js +505 -0
- package/src-tauri/src/inject/theme_refresh.js +59 -0
- package/src-tauri/src/inject/toast.js +22 -0
- package/src-tauri/src/lib.rs +227 -0
- package/src-tauri/src/main.rs +8 -0
- package/src-tauri/src/util.rs +245 -0
- package/src-tauri/tauri.conf.json +20 -0
- package/src-tauri/tauri.linux.conf.json +12 -0
- package/src-tauri/tauri.macos.conf.json +28 -0
- package/src-tauri/tauri.windows.conf.json +15 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
2
|
+
mod app;
|
|
3
|
+
mod util;
|
|
4
|
+
|
|
5
|
+
use tauri::Manager;
|
|
6
|
+
use tauri_plugin_window_state::Builder as WindowStatePlugin;
|
|
7
|
+
use tauri_plugin_window_state::StateFlags;
|
|
8
|
+
|
|
9
|
+
#[cfg(target_os = "macos")]
|
|
10
|
+
use std::time::Duration;
|
|
11
|
+
|
|
12
|
+
const WINDOW_SHOW_DELAY: u64 = 50;
|
|
13
|
+
|
|
14
|
+
use app::{
|
|
15
|
+
invoke::{
|
|
16
|
+
clear_cache_and_restart, clear_dock_badge, close_splashscreen, download_file,
|
|
17
|
+
increment_dock_badge, send_notification, set_dock_badge, set_dock_badge_label,
|
|
18
|
+
update_theme_mode,
|
|
19
|
+
},
|
|
20
|
+
setup::{set_global_shortcut, set_system_tray},
|
|
21
|
+
window::{build_splash_window, open_additional_window_safe, set_window, MultiWindowState},
|
|
22
|
+
};
|
|
23
|
+
use util::get_bghitapp_config;
|
|
24
|
+
|
|
25
|
+
pub fn run_app() {
|
|
26
|
+
#[cfg(target_os = "linux")]
|
|
27
|
+
{
|
|
28
|
+
if std::env::var("WEBKIT_DISABLE_DMABUF_RENDERER").is_err() {
|
|
29
|
+
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
|
30
|
+
}
|
|
31
|
+
if std::env::var("WEBKIT_DISABLE_COMPOSITING_MODE").is_err() {
|
|
32
|
+
std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let (bghitapp_config, tauri_config) = get_bghitapp_config();
|
|
37
|
+
let tauri_app = tauri::Builder::default();
|
|
38
|
+
|
|
39
|
+
let show_system_tray = bghitapp_config.show_system_tray();
|
|
40
|
+
let hide_on_close = bghitapp_config.windows[0].hide_on_close;
|
|
41
|
+
let activation_shortcut = bghitapp_config.windows[0].activation_shortcut.clone();
|
|
42
|
+
let init_fullscreen = bghitapp_config.windows[0].fullscreen;
|
|
43
|
+
let start_to_tray = bghitapp_config.windows[0].start_to_tray && show_system_tray; // Only valid when tray is enabled
|
|
44
|
+
let multi_instance = bghitapp_config.multi_instance;
|
|
45
|
+
let multi_window = bghitapp_config.multi_window;
|
|
46
|
+
let _enable_find = bghitapp_config.windows[0].enable_find;
|
|
47
|
+
let has_splash = !bghitapp_config.windows[0].splash.is_empty();
|
|
48
|
+
let splash_asset = bghitapp_config.windows[0].splash.clone();
|
|
49
|
+
|
|
50
|
+
let window_state_plugin = WindowStatePlugin::default()
|
|
51
|
+
.with_state_flags(if init_fullscreen {
|
|
52
|
+
StateFlags::FULLSCREEN
|
|
53
|
+
} else {
|
|
54
|
+
// Prevent flickering on the first open.
|
|
55
|
+
StateFlags::all() & !StateFlags::VISIBLE
|
|
56
|
+
})
|
|
57
|
+
.build();
|
|
58
|
+
|
|
59
|
+
#[allow(deprecated)]
|
|
60
|
+
let mut app_builder = tauri_app
|
|
61
|
+
.plugin(window_state_plugin)
|
|
62
|
+
.plugin(tauri_plugin_oauth::init())
|
|
63
|
+
.plugin(tauri_plugin_http::init())
|
|
64
|
+
.plugin(tauri_plugin_shell::init())
|
|
65
|
+
.plugin(tauri_plugin_notification::init())
|
|
66
|
+
.plugin(tauri_plugin_opener::init()); // Add this
|
|
67
|
+
|
|
68
|
+
// Only add single instance plugin if multiple instances are not allowed
|
|
69
|
+
if !multi_instance {
|
|
70
|
+
app_builder = app_builder.plugin(tauri_plugin_single_instance::init(
|
|
71
|
+
move |app, _args, _cwd| {
|
|
72
|
+
if multi_window {
|
|
73
|
+
open_additional_window_safe(app);
|
|
74
|
+
} else if let Some(window) = app.get_webview_window("bghitapp") {
|
|
75
|
+
let _ = window.unminimize();
|
|
76
|
+
let _ = window.show();
|
|
77
|
+
let _ = window.set_focus();
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
app_builder
|
|
84
|
+
.invoke_handler(tauri::generate_handler![
|
|
85
|
+
download_file,
|
|
86
|
+
send_notification,
|
|
87
|
+
increment_dock_badge,
|
|
88
|
+
set_dock_badge,
|
|
89
|
+
set_dock_badge_label,
|
|
90
|
+
clear_dock_badge,
|
|
91
|
+
update_theme_mode,
|
|
92
|
+
clear_cache_and_restart,
|
|
93
|
+
close_splashscreen,
|
|
94
|
+
])
|
|
95
|
+
.setup(move |app| {
|
|
96
|
+
app.manage(MultiWindowState::new(
|
|
97
|
+
bghitapp_config.clone(),
|
|
98
|
+
tauri_config.clone(),
|
|
99
|
+
));
|
|
100
|
+
|
|
101
|
+
// --- Menu Construction Start ---
|
|
102
|
+
#[cfg(target_os = "macos")]
|
|
103
|
+
{
|
|
104
|
+
app::menu::set_app_menu(app.app_handle(), multi_window, _enable_find)?;
|
|
105
|
+
|
|
106
|
+
// Event Handling for Custom Menu Item
|
|
107
|
+
app.on_menu_event(move |app_handle, event| {
|
|
108
|
+
app::menu::handle_menu_click(app_handle, event.id().as_ref());
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// --- Menu Construction End ---
|
|
112
|
+
|
|
113
|
+
// Create splash window if configured
|
|
114
|
+
if has_splash {
|
|
115
|
+
let _splash = build_splash_window(app.app_handle(), &splash_asset)?;
|
|
116
|
+
let _ = _splash.show();
|
|
117
|
+
|
|
118
|
+
// 10-second splash timeout: if main window hasn't signaled ready, show it anyway
|
|
119
|
+
let app_handle = app.app_handle().clone();
|
|
120
|
+
tauri::async_runtime::spawn(async move {
|
|
121
|
+
tokio::time::sleep(tokio::time::Duration::from_millis(10000)).await;
|
|
122
|
+
if let Some(splash) = app_handle.get_webview_window("splash") {
|
|
123
|
+
let _ = splash.destroy();
|
|
124
|
+
}
|
|
125
|
+
if let Some(main) = app_handle.get_webview_window("bghitapp") {
|
|
126
|
+
let _ = main.show();
|
|
127
|
+
let _ = main.set_focus();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let window = set_window(app.app_handle(), &bghitapp_config, &tauri_config)?;
|
|
133
|
+
set_system_tray(
|
|
134
|
+
app.app_handle(),
|
|
135
|
+
show_system_tray,
|
|
136
|
+
&bghitapp_config.system_tray_path,
|
|
137
|
+
init_fullscreen,
|
|
138
|
+
multi_window,
|
|
139
|
+
)?;
|
|
140
|
+
set_global_shortcut(app.app_handle(), activation_shortcut, init_fullscreen)?;
|
|
141
|
+
|
|
142
|
+
// Show window after state restoration to prevent position flashing
|
|
143
|
+
// Unless start_to_tray is enabled or splash is configured (splash handles showing)
|
|
144
|
+
if !start_to_tray && !has_splash {
|
|
145
|
+
let window_clone = window.clone();
|
|
146
|
+
tauri::async_runtime::spawn(async move {
|
|
147
|
+
tokio::time::sleep(tokio::time::Duration::from_millis(WINDOW_SHOW_DELAY)).await;
|
|
148
|
+
let _ = window_clone.show();
|
|
149
|
+
|
|
150
|
+
// Fixed: Linux fullscreen issue with virtual keyboard
|
|
151
|
+
#[cfg(target_os = "linux")]
|
|
152
|
+
{
|
|
153
|
+
if init_fullscreen {
|
|
154
|
+
let _ = window_clone.set_fullscreen(true);
|
|
155
|
+
// Ensure webview maintains focus for input after fullscreen
|
|
156
|
+
let _ = window_clone.set_focus();
|
|
157
|
+
} else {
|
|
158
|
+
// Fix: Ubuntu 24.04/GNOME window buttons non-functional until resize (#1122)
|
|
159
|
+
// The window manager needs time to process the MapWindow event before
|
|
160
|
+
// accepting focus requests. Without this, decorations remain non-interactive.
|
|
161
|
+
tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
|
|
162
|
+
let _ = window_clone.set_focus();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Ok(())
|
|
169
|
+
})
|
|
170
|
+
.on_window_event(move |_window, _event| {
|
|
171
|
+
if let tauri::WindowEvent::CloseRequested { api, .. } = _event {
|
|
172
|
+
if hide_on_close && _window.label() == "bghitapp" {
|
|
173
|
+
// Hide window when hide_on_close is enabled (regardless of tray status)
|
|
174
|
+
let window = _window.clone();
|
|
175
|
+
tauri::async_runtime::spawn(async move {
|
|
176
|
+
#[cfg(target_os = "macos")]
|
|
177
|
+
{
|
|
178
|
+
if window.is_fullscreen().unwrap_or(false) {
|
|
179
|
+
let _ = window.set_fullscreen(false);
|
|
180
|
+
tokio::time::sleep(Duration::from_millis(900)).await;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
#[cfg(target_os = "linux")]
|
|
184
|
+
{
|
|
185
|
+
if window.is_fullscreen().unwrap_or(false) {
|
|
186
|
+
let _ = window.set_fullscreen(false);
|
|
187
|
+
// Restore focus after exiting fullscreen to fix input issues
|
|
188
|
+
let _ = window.set_focus();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// On macOS, directly hide without minimize to avoid duplicate Dock icons
|
|
192
|
+
#[cfg(not(target_os = "macos"))]
|
|
193
|
+
let _ = window.minimize();
|
|
194
|
+
let _ = window.hide();
|
|
195
|
+
});
|
|
196
|
+
api.prevent_close();
|
|
197
|
+
}
|
|
198
|
+
// If hide_on_close is false, allow normal close behavior
|
|
199
|
+
// This lets tauri-plugin-window-state save the window position and size
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
.build(tauri::generate_context!())
|
|
203
|
+
.unwrap_or_else(|error| {
|
|
204
|
+
eprintln!("[BghitApp] Fatal error while building Tauri application: {error}");
|
|
205
|
+
std::process::exit(1);
|
|
206
|
+
})
|
|
207
|
+
.run(|_app, _event| {
|
|
208
|
+
// Handle macOS dock icon click to reopen hidden window
|
|
209
|
+
#[cfg(target_os = "macos")]
|
|
210
|
+
if let tauri::RunEvent::Reopen {
|
|
211
|
+
has_visible_windows,
|
|
212
|
+
..
|
|
213
|
+
} = _event
|
|
214
|
+
{
|
|
215
|
+
if !has_visible_windows {
|
|
216
|
+
if let Some(window) = _app.get_webview_window("bghitapp") {
|
|
217
|
+
let _ = window.show();
|
|
218
|
+
let _ = window.set_focus();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
pub fn run() {
|
|
226
|
+
run_app()
|
|
227
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
use crate::app::config::BghitappConfig;
|
|
2
|
+
use std::env;
|
|
3
|
+
use std::path::{Path, PathBuf};
|
|
4
|
+
use tauri::{AppHandle, Config, Manager, WebviewWindow};
|
|
5
|
+
|
|
6
|
+
pub fn get_bghitapp_config() -> (BghitappConfig, Config) {
|
|
7
|
+
#[cfg(feature = "cli-build")]
|
|
8
|
+
let bghitapp_config: BghitappConfig = serde_json::from_str(include_str!("../.bghitapp/bghitapp.json"))
|
|
9
|
+
.expect("Failed to parse bghitapp config");
|
|
10
|
+
|
|
11
|
+
#[cfg(not(feature = "cli-build"))]
|
|
12
|
+
let bghitapp_config: BghitappConfig =
|
|
13
|
+
serde_json::from_str(include_str!("../bghitapp.json")).expect("Failed to parse bghitapp config");
|
|
14
|
+
|
|
15
|
+
#[cfg(feature = "cli-build")]
|
|
16
|
+
let tauri_config: Config = serde_json::from_str(include_str!("../.bghitapp/tauri.conf.json"))
|
|
17
|
+
.expect("Failed to parse tauri config");
|
|
18
|
+
|
|
19
|
+
#[cfg(not(feature = "cli-build"))]
|
|
20
|
+
let tauri_config: Config = serde_json::from_str(include_str!("../tauri.conf.json"))
|
|
21
|
+
.expect("Failed to parse tauri config");
|
|
22
|
+
|
|
23
|
+
(bghitapp_config, tauri_config)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn get_data_dir(app: &AppHandle, package_name: String) -> std::io::Result<PathBuf> {
|
|
27
|
+
let data_dir = app
|
|
28
|
+
.path()
|
|
29
|
+
.config_dir()
|
|
30
|
+
.map_err(|err| {
|
|
31
|
+
std::io::Error::new(
|
|
32
|
+
std::io::ErrorKind::NotFound,
|
|
33
|
+
format!("Failed to resolve config dir: {err}"),
|
|
34
|
+
)
|
|
35
|
+
})?
|
|
36
|
+
.join(package_name);
|
|
37
|
+
|
|
38
|
+
if !data_dir.exists() {
|
|
39
|
+
std::fs::create_dir_all(&data_dir).map_err(|err| {
|
|
40
|
+
std::io::Error::new(
|
|
41
|
+
err.kind(),
|
|
42
|
+
format!("Can't create dir {}: {err}", data_dir.display()),
|
|
43
|
+
)
|
|
44
|
+
})?;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Ok(data_dir)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub fn show_toast(window: &WebviewWindow, message: &str) {
|
|
51
|
+
let script = format!(r#"bghitappToast("{message}");"#);
|
|
52
|
+
if let Err(error) = window.eval(&script) {
|
|
53
|
+
eprintln!("[BghitApp] Failed to show toast: {error}");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub enum MessageType {
|
|
58
|
+
Start,
|
|
59
|
+
Success,
|
|
60
|
+
Failure,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub fn get_download_message_with_lang(
|
|
64
|
+
message_type: MessageType,
|
|
65
|
+
language: Option<String>,
|
|
66
|
+
) -> String {
|
|
67
|
+
let default_start_message = "Start downloading~";
|
|
68
|
+
let chinese_start_message = "开始下载中~";
|
|
69
|
+
|
|
70
|
+
let default_success_message = "Download successful, saved to download directory~";
|
|
71
|
+
let chinese_success_message = "下载成功,已保存到下载目录~";
|
|
72
|
+
|
|
73
|
+
let default_failure_message = "Download failed, please check your network connection~";
|
|
74
|
+
let chinese_failure_message = "下载失败,请检查你的网络连接~";
|
|
75
|
+
|
|
76
|
+
let is_chinese = language
|
|
77
|
+
.as_ref()
|
|
78
|
+
.map(|lang| {
|
|
79
|
+
lang.starts_with("zh")
|
|
80
|
+
|| lang.contains("CN")
|
|
81
|
+
|| lang.contains("TW")
|
|
82
|
+
|| lang.contains("HK")
|
|
83
|
+
})
|
|
84
|
+
.unwrap_or_else(|| {
|
|
85
|
+
// Try multiple environment variables for better system detection
|
|
86
|
+
["LANG", "LC_ALL", "LC_MESSAGES", "LANGUAGE"]
|
|
87
|
+
.iter()
|
|
88
|
+
.find_map(|var| env::var(var).ok())
|
|
89
|
+
.map(|lang| {
|
|
90
|
+
lang.starts_with("zh")
|
|
91
|
+
|| lang.contains("CN")
|
|
92
|
+
|| lang.contains("TW")
|
|
93
|
+
|| lang.contains("HK")
|
|
94
|
+
})
|
|
95
|
+
.unwrap_or(false)
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if is_chinese {
|
|
99
|
+
match message_type {
|
|
100
|
+
MessageType::Start => chinese_start_message,
|
|
101
|
+
MessageType::Success => chinese_success_message,
|
|
102
|
+
MessageType::Failure => chinese_failure_message,
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
match message_type {
|
|
106
|
+
MessageType::Start => default_start_message,
|
|
107
|
+
MessageType::Success => default_success_message,
|
|
108
|
+
MessageType::Failure => default_failure_message,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
.to_string()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Check if the file exists. If it does, append `-N` to the stem until a free
|
|
115
|
+
/// path is found.
|
|
116
|
+
///
|
|
117
|
+
/// Robustness notes:
|
|
118
|
+
/// - Files without an extension are handled (we keep them extensionless).
|
|
119
|
+
/// - If the numeric suffix would overflow `u32::MAX` we fall back to the
|
|
120
|
+
/// original file_path so the caller never enters an infinite loop on
|
|
121
|
+
/// pathologically large filenames (regression guard for #1183).
|
|
122
|
+
pub fn check_file_or_append(file_path: &str) -> String {
|
|
123
|
+
let mut new_path = PathBuf::from(file_path);
|
|
124
|
+
|
|
125
|
+
while new_path.exists() {
|
|
126
|
+
let file_stem = new_path
|
|
127
|
+
.file_stem()
|
|
128
|
+
.map(|s| s.to_string_lossy().to_string())
|
|
129
|
+
.unwrap_or_default();
|
|
130
|
+
let extension = new_path
|
|
131
|
+
.extension()
|
|
132
|
+
.map(|e| e.to_string_lossy().to_string());
|
|
133
|
+
let parent_dir = new_path.parent().unwrap_or(Path::new(""));
|
|
134
|
+
|
|
135
|
+
let parsed_suffix = file_stem.rfind('-').and_then(|index| {
|
|
136
|
+
file_stem[index + 1..]
|
|
137
|
+
.parse::<u32>()
|
|
138
|
+
.ok()
|
|
139
|
+
.map(|n| (index, n))
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
let new_file_stem = match parsed_suffix {
|
|
143
|
+
Some((index, current)) => {
|
|
144
|
+
let Some(next) = current.checked_add(1) else {
|
|
145
|
+
// u32::MAX collisions are a sign of something pathological;
|
|
146
|
+
// bail with the original path instead of looping forever.
|
|
147
|
+
return file_path.to_string();
|
|
148
|
+
};
|
|
149
|
+
let base_name = &file_stem[..index];
|
|
150
|
+
format!("{base_name}-{next}")
|
|
151
|
+
}
|
|
152
|
+
None => format!("{file_stem}-1"),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
new_path = match &extension {
|
|
156
|
+
Some(ext) => parent_dir.join(format!("{new_file_stem}.{ext}")),
|
|
157
|
+
None => parent_dir.join(new_file_stem),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
new_path.to_string_lossy().into_owned()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#[cfg(test)]
|
|
165
|
+
mod tests {
|
|
166
|
+
use super::*;
|
|
167
|
+
use std::env;
|
|
168
|
+
use std::fs;
|
|
169
|
+
use std::path::PathBuf;
|
|
170
|
+
|
|
171
|
+
fn temp_path(name: &str) -> PathBuf {
|
|
172
|
+
let mut dir = env::temp_dir();
|
|
173
|
+
dir.push(format!(
|
|
174
|
+
"bghitapp-util-test-{}-{}",
|
|
175
|
+
std::process::id(),
|
|
176
|
+
std::time::SystemTime::now()
|
|
177
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
178
|
+
.map(|d| d.as_nanos())
|
|
179
|
+
.unwrap_or(0)
|
|
180
|
+
));
|
|
181
|
+
fs::create_dir_all(&dir).unwrap();
|
|
182
|
+
dir.push(name);
|
|
183
|
+
dir
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[test]
|
|
187
|
+
fn check_file_or_append_returns_input_when_missing() {
|
|
188
|
+
let path = temp_path("ghost.txt");
|
|
189
|
+
let resolved = check_file_or_append(path.to_str().unwrap());
|
|
190
|
+
assert_eq!(resolved, path.to_string_lossy());
|
|
191
|
+
let _ = fs::remove_dir_all(path.parent().unwrap());
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#[test]
|
|
195
|
+
fn check_file_or_append_increments_suffix() {
|
|
196
|
+
let path = temp_path("dup.txt");
|
|
197
|
+
fs::write(&path, b"existing").unwrap();
|
|
198
|
+
let resolved = check_file_or_append(path.to_str().unwrap());
|
|
199
|
+
assert!(resolved.ends_with("dup-1.txt"), "got {resolved}");
|
|
200
|
+
let _ = fs::remove_dir_all(path.parent().unwrap());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#[test]
|
|
204
|
+
fn check_file_or_append_handles_files_without_extension() {
|
|
205
|
+
let path = temp_path("README");
|
|
206
|
+
fs::write(&path, b"existing").unwrap();
|
|
207
|
+
let resolved = check_file_or_append(path.to_str().unwrap());
|
|
208
|
+
assert!(resolved.ends_with("README-1"), "got {resolved}");
|
|
209
|
+
let _ = fs::remove_dir_all(path.parent().unwrap());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[test]
|
|
213
|
+
fn check_file_or_append_does_not_panic_on_huge_suffix() {
|
|
214
|
+
let path = temp_path(&format!("huge-{}.txt", u32::MAX));
|
|
215
|
+
fs::write(&path, b"existing").unwrap();
|
|
216
|
+
let resolved = check_file_or_append(path.to_str().unwrap());
|
|
217
|
+
assert!(resolved.contains("huge-"));
|
|
218
|
+
let _ = fs::remove_dir_all(path.parent().unwrap());
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[test]
|
|
222
|
+
fn download_message_falls_back_to_english_for_unknown_locale() {
|
|
223
|
+
let msg = get_download_message_with_lang(MessageType::Start, Some("fr-FR".to_string()));
|
|
224
|
+
assert_eq!(msg, "Start downloading~");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#[test]
|
|
228
|
+
fn download_message_picks_chinese_for_zh_locales() {
|
|
229
|
+
for tag in ["zh", "zh-CN", "zh-TW", "en-CN", "en-HK"] {
|
|
230
|
+
let msg = get_download_message_with_lang(MessageType::Success, Some(tag.to_string()));
|
|
231
|
+
assert_eq!(
|
|
232
|
+
msg, "下载成功,已保存到下载目录~",
|
|
233
|
+
"expected Chinese for {tag}"
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#[test]
|
|
239
|
+
fn download_message_failure_localized() {
|
|
240
|
+
let en = get_download_message_with_lang(MessageType::Failure, Some("en".into()));
|
|
241
|
+
let zh = get_download_message_with_lang(MessageType::Failure, Some("zh".into()));
|
|
242
|
+
assert!(en.contains("Download failed"));
|
|
243
|
+
assert!(zh.contains("下载失败"));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"productName": "BghitApp",
|
|
3
|
+
"identifier": "com.bghitcode.bghitapp",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"app": {
|
|
6
|
+
"withGlobalTauri": true,
|
|
7
|
+
"trayIcon": {
|
|
8
|
+
"iconPath": "png/weekly_512.png",
|
|
9
|
+
"iconAsTemplate": false,
|
|
10
|
+
"id": "bghitapp-tray"
|
|
11
|
+
},
|
|
12
|
+
"security": {
|
|
13
|
+
"headers": {},
|
|
14
|
+
"csp": null
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"build": {
|
|
18
|
+
"frontendDist": "../dist"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bundle": {
|
|
3
|
+
"icon": ["icons/weekly.icns"],
|
|
4
|
+
"active": true,
|
|
5
|
+
"targets": ["dmg"],
|
|
6
|
+
"macOS": {
|
|
7
|
+
"signingIdentity": "-",
|
|
8
|
+
"hardenedRuntime": true,
|
|
9
|
+
"entitlements": "entitlements.plist",
|
|
10
|
+
"infoPlist": "Info.plist",
|
|
11
|
+
"dmg": {
|
|
12
|
+
"background": "assets/macos/dmg/background.png",
|
|
13
|
+
"windowSize": {
|
|
14
|
+
"width": 680,
|
|
15
|
+
"height": 420
|
|
16
|
+
},
|
|
17
|
+
"appPosition": {
|
|
18
|
+
"x": 190,
|
|
19
|
+
"y": 250
|
|
20
|
+
},
|
|
21
|
+
"applicationFolderPosition": {
|
|
22
|
+
"x": 500,
|
|
23
|
+
"y": 250
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bundle": {
|
|
3
|
+
"icon": ["png/weekly_256.ico", "png/weekly_32.ico"],
|
|
4
|
+
"active": true,
|
|
5
|
+
"resources": ["png/weekly_32.ico"],
|
|
6
|
+
"targets": ["msi"],
|
|
7
|
+
"windows": {
|
|
8
|
+
"digestAlgorithm": "sha256",
|
|
9
|
+
"wix": {
|
|
10
|
+
"language": ["en-US"],
|
|
11
|
+
"template": "assets/main.wxs"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|