refactor: Clean up code formatting in utils module

This commit is contained in:
PandaDEV 2025-01-04 11:58:52 +10:00
parent 60b670c7a7
commit 22fcd84b8d
No known key found for this signature in database
GPG key ID: 13EFF9BAF70EE75C
14 changed files with 485 additions and 394 deletions

View file

@ -1,14 +1,14 @@
use tauri_plugin_aptabase::EventTracker; use tauri_plugin_aptabase::EventTracker;
use base64::{engine::general_purpose::STANDARD, Engine}; use base64::{ engine::general_purpose::STANDARD, Engine };
// use hyperpolyglot; // use hyperpolyglot;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use rdev::{simulate, EventType, Key}; use rdev::{ simulate, EventType, Key };
use regex::Regex; use regex::Regex;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::fs; use std::fs;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{ AtomicBool, Ordering };
use std::{thread, time::Duration}; use std::{ thread, time::Duration };
use tauri::{AppHandle, Emitter, Listener, Manager}; use tauri::{ AppHandle, Emitter, Listener, Manager };
use tauri_plugin_clipboard::Clipboard; use tauri_plugin_clipboard::Clipboard;
use tokio::runtime::Runtime as TokioRuntime; use tokio::runtime::Runtime as TokioRuntime;
use url::Url; use url::Url;
@ -17,7 +17,7 @@ use uuid::Uuid;
use crate::db; use crate::db;
use crate::utils::commands::get_app_info; use crate::utils::commands::get_app_info;
use crate::utils::favicon::fetch_favicon_as_base64; use crate::utils::favicon::fetch_favicon_as_base64;
use crate::utils::types::{ContentType, HistoryItem}; use crate::utils::types::{ ContentType, HistoryItem };
lazy_static! { lazy_static! {
static ref IS_PROGRAMMATIC_PASTE: AtomicBool = AtomicBool::new(false); static ref IS_PROGRAMMATIC_PASTE: AtomicBool = AtomicBool::new(false);
@ -27,16 +27,14 @@ lazy_static! {
pub async fn write_and_paste( pub async fn write_and_paste(
app_handle: AppHandle, app_handle: AppHandle,
content: String, content: String,
content_type: String, content_type: String
) -> Result<(), String> { ) -> Result<(), String> {
let clipboard = app_handle.state::<Clipboard>(); let clipboard = app_handle.state::<Clipboard>();
match content_type.as_str() { match content_type.as_str() {
"text" => clipboard.write_text(content).map_err(|e| e.to_string())?, "text" => clipboard.write_text(content).map_err(|e| e.to_string())?,
"image" => { "image" => {
clipboard clipboard.write_image_base64(content).map_err(|e| e.to_string())?;
.write_image_base64(content)
.map_err(|e| e.to_string())?;
} }
"files" => { "files" => {
clipboard clipboard
@ -44,11 +42,13 @@ pub async fn write_and_paste(
content content
.split(", ") .split(", ")
.map(|file| file.to_string()) .map(|file| file.to_string())
.collect::<Vec<String>>(), .collect::<Vec<String>>()
) )
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
} }
_ => return Err("Unsupported content type".to_string()), _ => {
return Err("Unsupported content type".to_string());
}
} }
IS_PROGRAMMATIC_PASTE.store(true, Ordering::SeqCst); IS_PROGRAMMATIC_PASTE.store(true, Ordering::SeqCst);
@ -65,7 +65,7 @@ pub async fn write_and_paste(
EventType::KeyPress(modifier_key), EventType::KeyPress(modifier_key),
EventType::KeyPress(Key::KeyV), EventType::KeyPress(Key::KeyV),
EventType::KeyRelease(Key::KeyV), EventType::KeyRelease(Key::KeyV),
EventType::KeyRelease(modifier_key), EventType::KeyRelease(modifier_key)
]; ];
for event in events { for event in events {
@ -81,9 +81,12 @@ pub async fn write_and_paste(
IS_PROGRAMMATIC_PASTE.store(false, Ordering::SeqCst); IS_PROGRAMMATIC_PASTE.store(false, Ordering::SeqCst);
}); });
let _ = app_handle.track_event("clipboard_paste", Some(serde_json::json!({ let _ = app_handle.track_event(
"clipboard_paste",
Some(serde_json::json!({
"content_type": content_type "content_type": content_type
}))); }))
);
Ok(()) Ok(())
} }
@ -92,9 +95,7 @@ pub fn setup(app: &AppHandle) {
let app_handle = app.clone(); let app_handle = app.clone();
let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime"); let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime");
app_handle.clone().listen( app_handle.clone().listen("plugin:clipboard://clipboard-monitor/update", move |_event| {
"plugin:clipboard://clipboard-monitor/update",
move |_event| {
let app_handle = app_handle.clone(); let app_handle = app_handle.clone();
runtime.block_on(async move { runtime.block_on(async move {
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) { if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
@ -111,14 +112,20 @@ pub fn setup(app: &AppHandle) {
if available_types.image { if available_types.image {
println!("Handling image change"); println!("Handling image change");
if let Ok(image_data) = clipboard.read_image_base64() { if let Ok(image_data) = clipboard.read_image_base64() {
let file_path = save_image_to_file(&app_handle, &image_data) let file_path = save_image_to_file(&app_handle, &image_data).await
.await
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
.unwrap_or_else(|e| e); .unwrap_or_else(|e| e);
let _ = db::history::add_history_item( let _ = db::history::add_history_item(
app_handle.clone(), app_handle.clone(),
pool, pool,
HistoryItem::new(app_name, ContentType::Image, file_path, None, app_icon, None) HistoryItem::new(
app_name,
ContentType::Image,
file_path,
None,
app_icon,
None
)
).await; ).await;
} }
} else if available_types.files { } else if available_types.files {
@ -135,7 +142,7 @@ pub fn setup(app: &AppHandle) {
None, None,
app_icon.clone(), app_icon.clone(),
None None
), )
).await; ).await;
} }
} }
@ -143,7 +150,9 @@ pub fn setup(app: &AppHandle) {
println!("Handling text change"); println!("Handling text change");
if let Ok(text) = clipboard.read_text() { if let Ok(text) = clipboard.read_text() {
let text = text.to_string(); let text = text.to_string();
let url_regex = Regex::new(r"^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)$").unwrap(); let url_regex = Regex::new(
r"^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)$"
).unwrap();
if url_regex.is_match(&text) { if url_regex.is_match(&text) {
if let Ok(url) = Url::parse(&text) { if let Ok(url) = Url::parse(&text) {
@ -155,7 +164,14 @@ pub fn setup(app: &AppHandle) {
let _ = db::history::add_history_item( let _ = db::history::add_history_item(
app_handle.clone(), app_handle.clone(),
pool, pool,
HistoryItem::new(app_name, ContentType::Link, text, favicon, app_icon, None) HistoryItem::new(
app_name,
ContentType::Link,
text,
favicon,
app_icon,
None
)
).await; ).await;
} }
} else { } else {
@ -178,13 +194,27 @@ pub fn setup(app: &AppHandle) {
let _ = db::history::add_history_item( let _ = db::history::add_history_item(
app_handle.clone(), app_handle.clone(),
pool, pool,
HistoryItem::new(app_name, ContentType::Color, text, None, app_icon, None) HistoryItem::new(
app_name,
ContentType::Color,
text,
None,
app_icon,
None
)
).await; ).await;
} else { } else {
let _ = db::history::add_history_item( let _ = db::history::add_history_item(
app_handle.clone(), app_handle.clone(),
pool, pool,
HistoryItem::new(app_name, ContentType::Text, text.clone(), None, app_icon, None) HistoryItem::new(
app_name,
ContentType::Text,
text.clone(),
None,
app_icon,
None
)
).await; ).await;
} }
} }
@ -199,19 +229,23 @@ pub fn setup(app: &AppHandle) {
} }
let _ = app_handle.emit("clipboard-content-updated", ()); let _ = app_handle.emit("clipboard-content-updated", ());
let _ = app_handle.track_event("clipboard_copied", Some(serde_json::json!({ let _ = app_handle.track_event(
"clipboard_copied",
Some(
serde_json::json!({
"content_type": if available_types.image { "image" } "content_type": if available_types.image { "image" }
else if available_types.files { "files" } else if available_types.files { "files" }
else if available_types.text { "text" } else if available_types.text { "text" }
else { "unknown" } else { "unknown" }
}))); })
}); )
},
); );
});
});
} }
async fn get_pool( async fn get_pool(
app_handle: &AppHandle, app_handle: &AppHandle
) -> Result<tauri::State<'_, SqlitePool>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<tauri::State<'_, SqlitePool>, Box<dyn std::error::Error + Send + Sync>> {
Ok(app_handle.state::<SqlitePool>()) Ok(app_handle.state::<SqlitePool>())
} }
@ -219,9 +253,7 @@ async fn get_pool(
#[tauri::command] #[tauri::command]
pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> { pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> {
let clipboard = app_handle.state::<Clipboard>(); let clipboard = app_handle.state::<Clipboard>();
clipboard clipboard.start_monitor(app_handle.clone()).map_err(|e| e.to_string())?;
.start_monitor(app_handle.clone())
.map_err(|e| e.to_string())?;
app_handle app_handle
.emit("plugin:clipboard://clipboard-monitor/status", true) .emit("plugin:clipboard://clipboard-monitor/status", true)
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
@ -230,7 +262,7 @@ pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> {
async fn save_image_to_file( async fn save_image_to_file(
app_handle: &AppHandle, app_handle: &AppHandle,
base64_data: &str, base64_data: &str
) -> Result<String, Box<dyn std::error::Error>> { ) -> Result<String, Box<dyn std::error::Error>> {
let app_data_dir = app_handle.path().app_data_dir().unwrap(); let app_data_dir = app_handle.path().app_data_dir().unwrap();
let images_dir = app_data_dir.join("images"); let images_dir = app_data_dir.join("images");

View file

@ -1,13 +1,15 @@
use crate::utils::commands::center_window_on_current_monitor; use crate::utils::commands::center_window_on_current_monitor;
use crate::utils::keys::KeyCode; use crate::utils::keys::KeyCode;
use global_hotkey::{ use global_hotkey::{
hotkey::{Code, HotKey, Modifiers}, hotkey::{ Code, HotKey, Modifiers },
GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState, GlobalHotKeyEvent,
GlobalHotKeyManager,
HotKeyState,
}; };
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Mutex; use std::sync::Mutex;
use tauri::{AppHandle, Listener, Manager}; use tauri::{ AppHandle, Listener, Manager };
use tauri_plugin_aptabase::EventTracker; use tauri_plugin_aptabase::EventTracker;
lazy_static! { lazy_static! {
@ -110,10 +112,18 @@ fn parse_hotkey(shortcut: &[String]) -> Result<HotKey, Box<dyn std::error::Error
for part in shortcut { for part in shortcut {
match part.as_str() { match part.as_str() {
"ControlLeft" => modifiers |= Modifiers::CONTROL, "ControlLeft" => {
"AltLeft" => modifiers |= Modifiers::ALT, modifiers |= Modifiers::CONTROL;
"ShiftLeft" => modifiers |= Modifiers::SHIFT, }
"MetaLeft" => modifiers |= Modifiers::META, "AltLeft" => {
modifiers |= Modifiers::ALT;
}
"ShiftLeft" => {
modifiers |= Modifiers::SHIFT;
}
"MetaLeft" => {
modifiers |= Modifiers::META;
}
key => { key => {
code = Some(Code::from(KeyCode::from_str(key)?)); code = Some(Code::from(KeyCode::from_str(key)?));
} }
@ -144,8 +154,10 @@ fn handle_hotkey_event(app_handle: &AppHandle) {
let _ = app_handle.track_event( let _ = app_handle.track_event(
"hotkey_triggered", "hotkey_triggered",
Some(serde_json::json!({ Some(
serde_json::json!({
"action": if window.is_visible().unwrap() { "hide" } else { "show" } "action": if window.is_visible().unwrap() { "hide" } else { "show" }
})), })
)
); );
} }

View file

@ -1,16 +1,15 @@
use tauri::{ use tauri::{ menu::{ MenuBuilder, MenuItemBuilder }, tray::TrayIconBuilder, Emitter, Manager };
menu::{MenuBuilder, MenuItemBuilder},
tray::TrayIconBuilder,
Emitter, Manager,
};
use tauri_plugin_aptabase::EventTracker; use tauri_plugin_aptabase::EventTracker;
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let window = app.get_webview_window("main").unwrap(); let window = app.get_webview_window("main").unwrap();
let is_visible = window.is_visible().unwrap(); let is_visible = window.is_visible().unwrap();
let _ = app.track_event("tray_toggle", Some(serde_json::json!({ let _ = app.track_event(
"tray_toggle",
Some(serde_json::json!({
"action": if is_visible { "hide" } else { "show" } "action": if is_visible { "hide" } else { "show" }
}))); }))
);
let icon_bytes = include_bytes!("../../icons/Square71x71Logo.png"); let icon_bytes = include_bytes!("../../icons/Square71x71Logo.png");
let icon = tauri::image::Image::from_bytes(icon_bytes).unwrap(); let icon = tauri::image::Image::from_bytes(icon_bytes).unwrap();
@ -18,23 +17,27 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let _tray = TrayIconBuilder::new() let _tray = TrayIconBuilder::new()
.menu( .menu(
&MenuBuilder::new(app) &MenuBuilder::new(app)
.items(&[&MenuItemBuilder::with_id("app_name", "Qopy") .items(&[&MenuItemBuilder::with_id("app_name", "Qopy").enabled(false).build(app)?])
.enabled(false)
.build(app)?])
.items(&[&MenuItemBuilder::with_id("show", "Show/Hide").build(app)?]) .items(&[&MenuItemBuilder::with_id("show", "Show/Hide").build(app)?])
.items(&[&MenuItemBuilder::with_id("settings", "Settings").build(app)?]) .items(&[&MenuItemBuilder::with_id("settings", "Settings").build(app)?])
.items(&[&MenuItemBuilder::with_id("quit", "Quit").build(app)?]) .items(&[&MenuItemBuilder::with_id("quit", "Quit").build(app)?])
.build()?, .build()?
) )
.on_menu_event(move |_app, event| match event.id().as_ref() { .on_menu_event(move |_app, event| {
match event.id().as_ref() {
"quit" => { "quit" => {
let _ = _app.track_event("app_quit", None); let _ = _app.track_event("app_quit", None);
std::process::exit(0); std::process::exit(0);
} }
"show" => { "show" => {
let _ = _app.track_event("tray_toggle", Some(serde_json::json!({ let _ = _app.track_event(
"tray_toggle",
Some(
serde_json::json!({
"action": if is_visible { "hide" } else { "show" } "action": if is_visible { "hide" } else { "show" }
}))); })
)
);
let is_visible = window.is_visible().unwrap(); let is_visible = window.is_visible().unwrap();
if is_visible { if is_visible {
window.hide().unwrap(); window.hide().unwrap();
@ -49,6 +52,7 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
window.emit("settings", ()).unwrap(); window.emit("settings", ()).unwrap();
} }
_ => (), _ => (),
}
}) })
.icon(icon) .icon(icon)
.build(app)?; .build(app)?;

View file

@ -1,5 +1,5 @@
use tauri::{async_runtime, AppHandle}; use tauri::{ async_runtime, AppHandle };
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind}; use tauri_plugin_dialog::{ DialogExt, MessageDialogButtons, MessageDialogKind };
use tauri_plugin_updater::UpdaterExt; use tauri_plugin_updater::UpdaterExt;
pub async fn check_for_updates(app: AppHandle) { pub async fn check_for_updates(app: AppHandle) {
@ -21,18 +21,35 @@ pub async fn check_for_updates(app: AppHandle) {
app.dialog() app.dialog()
.message(msg) .message(msg)
.title("Qopy Update Available") .title("Qopy Update Available")
.buttons(MessageDialogButtons::OkCancelCustom(String::from("Install"), String::from("Cancel"))) .buttons(
MessageDialogButtons::OkCancelCustom(
String::from("Install"),
String::from("Cancel")
)
)
.show(move |response| { .show(move |response| {
if !response { if !response {
return; return;
} }
async_runtime::spawn(async move { async_runtime::spawn(async move {
match update.download_and_install(|_, _| {}, || {}).await { match
update.download_and_install(
|_, _| {},
|| {}
).await
{
Ok(_) => { Ok(_) => {
app.dialog() app.dialog()
.message("Update installed successfully. The application needs to restart to apply the changes.") .message(
"Update installed successfully. The application needs to restart to apply the changes."
)
.title("Qopy Update Installed") .title("Qopy Update Installed")
.buttons(MessageDialogButtons::OkCancelCustom(String::from("Restart"), String::from("Cancel"))) .buttons(
MessageDialogButtons::OkCancelCustom(
String::from("Restart"),
String::from("Cancel")
)
)
.show(move |response| { .show(move |response| {
if response { if response {
app.restart(); app.restart();
@ -42,7 +59,9 @@ pub async fn check_for_updates(app: AppHandle) {
Err(e) => { Err(e) => {
println!("Error installing new update: {:?}", e); println!("Error installing new update: {:?}", e);
app.dialog() app.dialog()
.message("Failed to install new update. The new update can be downloaded from Github") .message(
"Failed to install new update. The new update can be downloaded from Github"
)
.kind(MessageDialogKind::Error) .kind(MessageDialogKind::Error)
.show(|_| {}); .show(|_| {});
} }

View file

@ -1,5 +1,5 @@
use include_dir::{include_dir, Dir}; use include_dir::{ include_dir, Dir };
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions}; use sqlx::sqlite::{ SqlitePool, SqlitePoolOptions };
use std::fs; use std::fs;
use tauri::Manager; use tauri::Manager;
use tokio::runtime::Runtime as TokioRuntime; use tokio::runtime::Runtime as TokioRuntime;
@ -25,8 +25,7 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let pool = rt.block_on(async { let pool = rt.block_on(async {
SqlitePoolOptions::new() SqlitePoolOptions::new()
.max_connections(5) .max_connections(5)
.connect(&db_url) .connect(&db_url).await
.await
.expect("Failed to create pool") .expect("Failed to create pool")
}); });
@ -49,24 +48,22 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
} }
async fn apply_migrations(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> { async fn apply_migrations(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
sqlx::query( sqlx
::query(
"CREATE TABLE IF NOT EXISTS schema_version ( "CREATE TABLE IF NOT EXISTS schema_version (
version INTEGER PRIMARY KEY, version INTEGER PRIMARY KEY,
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
);", );"
) )
.execute(pool) .execute(pool).await?;
.await?;
let current_version: Option<i64> = let current_version: Option<i64> = sqlx
sqlx::query_scalar("SELECT MAX(version) FROM schema_version") ::query_scalar("SELECT MAX(version) FROM schema_version")
.fetch_one(pool) .fetch_one(pool).await?;
.await?;
let current_version = current_version.unwrap_or(0); let current_version = current_version.unwrap_or(0);
let mut migration_files: Vec<(i64, &str)> = MIGRATIONS_DIR let mut migration_files: Vec<(i64, &str)> = MIGRATIONS_DIR.files()
.files()
.filter_map(|file| { .filter_map(|file| {
let file_name = file.path().file_name()?.to_str()?; let file_name = file.path().file_name()?.to_str()?;
if file_name.ends_with(".sql") && file_name.starts_with("v") { if file_name.ends_with(".sql") && file_name.starts_with("v") {
@ -93,16 +90,16 @@ async fn apply_migrations(pool: &SqlitePool) -> Result<(), Box<dyn std::error::E
.collect(); .collect();
for statement in statements { for statement in statements {
sqlx::query(statement) sqlx
.execute(pool) ::query(statement)
.await .execute(pool).await
.map_err(|e| format!("Failed to execute migration {}: {}", version, e))?; .map_err(|e| format!("Failed to execute migration {}: {}", version, e))?;
} }
sqlx::query("INSERT INTO schema_version (version) VALUES (?)") sqlx
::query("INSERT INTO schema_version (version) VALUES (?)")
.bind(version) .bind(version)
.execute(pool) .execute(pool).await?;
.await?;
} }
} }

View file

@ -1,38 +1,34 @@
use crate::utils::types::{ContentType, HistoryItem}; use crate::utils::types::{ ContentType, HistoryItem };
use base64::{engine::general_purpose::STANDARD, Engine}; use base64::{ engine::general_purpose::STANDARD, Engine };
use rand::distributions::Alphanumeric; use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng}; use rand::{ thread_rng, Rng };
use sqlx::{Row, SqlitePool}; use sqlx::{ Row, SqlitePool };
use std::fs; use std::fs;
use tauri_plugin_aptabase::EventTracker; use tauri_plugin_aptabase::EventTracker;
pub async fn initialize_history(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> { pub async fn initialize_history(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
let id: String = thread_rng() let id: String = thread_rng().sample_iter(&Alphanumeric).take(16).map(char::from).collect();
.sample_iter(&Alphanumeric)
.take(16)
.map(char::from)
.collect();
sqlx::query( sqlx
::query(
"INSERT INTO history (id, source, content_type, content, timestamp) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)" "INSERT INTO history (id, source, content_type, content, timestamp) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)"
) )
.bind(id) .bind(id)
.bind("System") .bind("System")
.bind("text") .bind("text")
.bind("Welcome to your clipboard history!") .bind("Welcome to your clipboard history!")
.execute(pool) .execute(pool).await?;
.await?;
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command]
pub async fn get_history(pool: tauri::State<'_, SqlitePool>) -> Result<Vec<HistoryItem>, String> { pub async fn get_history(pool: tauri::State<'_, SqlitePool>) -> Result<Vec<HistoryItem>, String> {
let rows = sqlx::query( let rows = sqlx
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC", ::query(
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC"
) )
.fetch_all(&*pool) .fetch_all(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let items = rows let items = rows
@ -56,31 +52,32 @@ pub async fn get_history(pool: tauri::State<'_, SqlitePool>) -> Result<Vec<Histo
pub async fn add_history_item( pub async fn add_history_item(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
pool: tauri::State<'_, SqlitePool>, pool: tauri::State<'_, SqlitePool>,
item: HistoryItem, item: HistoryItem
) -> Result<(), String> { ) -> Result<(), String> {
let (id, source, source_icon, content_type, content, favicon, timestamp, language) = let (id, source, source_icon, content_type, content, favicon, timestamp, language) =
item.to_row(); item.to_row();
let existing = sqlx::query("SELECT id FROM history WHERE content = ? AND content_type = ?") let existing = sqlx
::query("SELECT id FROM history WHERE content = ? AND content_type = ?")
.bind(&content) .bind(&content)
.bind(&content_type) .bind(&content_type)
.fetch_optional(&*pool) .fetch_optional(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
match existing { match existing {
Some(_) => { Some(_) => {
sqlx::query( sqlx
::query(
"UPDATE history SET timestamp = strftime('%Y-%m-%dT%H:%M:%f+00:00', 'now') WHERE content = ? AND content_type = ?" "UPDATE history SET timestamp = strftime('%Y-%m-%dT%H:%M:%f+00:00', 'now') WHERE content = ? AND content_type = ?"
) )
.bind(&content) .bind(&content)
.bind(&content_type) .bind(&content_type)
.execute(&*pool) .execute(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
} }
None => { None => {
sqlx::query( sqlx
::query(
"INSERT INTO history (id, source, source_icon, content_type, content, favicon, timestamp, language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" "INSERT INTO history (id, source, source_icon, content_type, content, favicon, timestamp, language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
) )
.bind(id) .bind(id)
@ -91,15 +88,17 @@ pub async fn add_history_item(
.bind(favicon) .bind(favicon)
.bind(timestamp) .bind(timestamp)
.bind(language) .bind(language)
.execute(&*pool) .execute(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
} }
} }
let _ = app_handle.track_event("history_item_added", Some(serde_json::json!({ let _ = app_handle.track_event(
"history_item_added",
Some(serde_json::json!({
"content_type": item.content_type.to_string() "content_type": item.content_type.to_string()
}))); }))
);
Ok(()) Ok(())
} }
@ -107,15 +106,15 @@ pub async fn add_history_item(
#[tauri::command] #[tauri::command]
pub async fn search_history( pub async fn search_history(
pool: tauri::State<'_, SqlitePool>, pool: tauri::State<'_, SqlitePool>,
query: String, query: String
) -> Result<Vec<HistoryItem>, String> { ) -> Result<Vec<HistoryItem>, String> {
let query = format!("%{}%", query); let query = format!("%{}%", query);
let rows = sqlx::query( let rows = sqlx
::query(
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history WHERE content LIKE ? ORDER BY timestamp DESC" "SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history WHERE content LIKE ? ORDER BY timestamp DESC"
) )
.bind(query) .bind(query)
.fetch_all(&*pool) .fetch_all(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let items = rows let items = rows
@ -139,15 +138,15 @@ pub async fn search_history(
pub async fn load_history_chunk( pub async fn load_history_chunk(
pool: tauri::State<'_, SqlitePool>, pool: tauri::State<'_, SqlitePool>,
offset: i64, offset: i64,
limit: i64, limit: i64
) -> Result<Vec<HistoryItem>, String> { ) -> Result<Vec<HistoryItem>, String> {
let rows = sqlx::query( let rows = sqlx
::query(
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC LIMIT ? OFFSET ?" "SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC LIMIT ? OFFSET ?"
) )
.bind(limit) .bind(limit)
.bind(offset) .bind(offset)
.fetch_all(&*pool) .fetch_all(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let items = rows let items = rows
@ -171,12 +170,12 @@ pub async fn load_history_chunk(
pub async fn delete_history_item( pub async fn delete_history_item(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
pool: tauri::State<'_, SqlitePool>, pool: tauri::State<'_, SqlitePool>,
id: String, id: String
) -> Result<(), String> { ) -> Result<(), String> {
sqlx::query("DELETE FROM history WHERE id = ?") sqlx
::query("DELETE FROM history WHERE id = ?")
.bind(id) .bind(id)
.execute(&*pool) .execute(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let _ = app_handle.track_event("history_item_deleted", None); let _ = app_handle.track_event("history_item_deleted", None);
@ -189,9 +188,9 @@ pub async fn clear_history(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
pool: tauri::State<'_, SqlitePool> pool: tauri::State<'_, SqlitePool>
) -> Result<(), String> { ) -> Result<(), String> {
sqlx::query("DELETE FROM history") sqlx
.execute(&*pool) ::query("DELETE FROM history")
.await .execute(&*pool).await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let _ = app_handle.track_event("history_cleared", None); let _ = app_handle.track_event("history_cleared", None);

View file

@ -1,8 +1,8 @@
use serde::{Deserialize, Serialize}; use serde::{ Deserialize, Serialize };
use serde_json; use serde_json;
use sqlx::Row; use sqlx::Row;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tauri::{Emitter, Manager}; use tauri::{ Emitter, Manager };
use tauri_plugin_aptabase::EventTracker; use tauri_plugin_aptabase::EventTracker;
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
@ -16,10 +16,10 @@ pub async fn initialize_settings(pool: &SqlitePool) -> Result<(), Box<dyn std::e
}; };
let json = serde_json::to_string(&default_keybind)?; let json = serde_json::to_string(&default_keybind)?;
sqlx::query("INSERT INTO settings (key, value) VALUES ('keybind', ?)") sqlx
::query("INSERT INTO settings (key, value) VALUES ('keybind', ?)")
.bind(json) .bind(json)
.execute(pool) .execute(pool).await?;
.await?;
Ok(()) Ok(())
} }
@ -28,23 +28,24 @@ pub async fn initialize_settings(pool: &SqlitePool) -> Result<(), Box<dyn std::e
pub async fn save_keybind( pub async fn save_keybind(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
pool: tauri::State<'_, SqlitePool>, pool: tauri::State<'_, SqlitePool>,
keybind: Vec<String>, keybind: Vec<String>
) -> Result<(), String> { ) -> Result<(), String> {
app_handle app_handle.emit("update-shortcut", &keybind).map_err(|e| e.to_string())?;
.emit("update-shortcut", &keybind)
.map_err(|e| e.to_string())?;
let json = serde_json::to_string(&keybind).map_err(|e| e.to_string())?; let json = serde_json::to_string(&keybind).map_err(|e| e.to_string())?;
sqlx::query("INSERT OR REPLACE INTO settings (key, value) VALUES ('keybind', ?)") sqlx
::query("INSERT OR REPLACE INTO settings (key, value) VALUES ('keybind', ?)")
.bind(json) .bind(json)
.execute(&*pool) .execute(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let _ = app_handle.track_event("keybind_saved", Some(serde_json::json!({ let _ = app_handle.track_event(
"keybind_saved",
Some(serde_json::json!({
"keybind": keybind "keybind": keybind
}))); }))
);
Ok(()) Ok(())
} }
@ -52,12 +53,12 @@ pub async fn save_keybind(
#[tauri::command] #[tauri::command]
pub async fn get_setting( pub async fn get_setting(
pool: tauri::State<'_, SqlitePool>, pool: tauri::State<'_, SqlitePool>,
key: String, key: String
) -> Result<String, String> { ) -> Result<String, String> {
let row = sqlx::query("SELECT value FROM settings WHERE key = ?") let row = sqlx
::query("SELECT value FROM settings WHERE key = ?")
.bind(key) .bind(key)
.fetch_optional(&*pool) .fetch_optional(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
Ok(row.map(|r| r.get("value")).unwrap_or_default()) Ok(row.map(|r| r.get("value")).unwrap_or_default())
@ -68,18 +69,21 @@ pub async fn save_setting(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
pool: tauri::State<'_, SqlitePool>, pool: tauri::State<'_, SqlitePool>,
key: String, key: String,
value: String, value: String
) -> Result<(), String> { ) -> Result<(), String> {
sqlx::query("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)") sqlx
::query("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
.bind(key.clone()) .bind(key.clone())
.bind(value) .bind(value)
.execute(&*pool) .execute(&*pool).await
.await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let _ = app_handle.track_event("setting_saved", Some(serde_json::json!({ let _ = app_handle.track_event(
"setting_saved",
Some(serde_json::json!({
"key": key "key": key
}))); }))
);
Ok(()) Ok(())
} }
@ -88,13 +92,16 @@ pub async fn save_setting(
pub async fn get_keybind(app_handle: tauri::AppHandle) -> Result<Vec<String>, String> { pub async fn get_keybind(app_handle: tauri::AppHandle) -> Result<Vec<String>, String> {
let pool = app_handle.state::<SqlitePool>(); let pool = app_handle.state::<SqlitePool>();
let row = sqlx::query("SELECT value FROM settings WHERE key = 'keybind'") let row = sqlx
.fetch_optional(&*pool) ::query("SELECT value FROM settings WHERE key = 'keybind'")
.await .fetch_optional(&*pool).await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let json = row.map(|r| r.get::<String, _>("value")).unwrap_or_else(|| { let json = row
serde_json::to_string(&vec!["MetaLeft".to_string(), "KeyV".to_string()]) .map(|r| r.get::<String, _>("value"))
.unwrap_or_else(|| {
serde_json
::to_string(&vec!["MetaLeft".to_string(), "KeyV".to_string()])
.expect("Failed to serialize default keybind") .expect("Failed to serialize default keybind")
}); });

View file

@ -1,7 +1,4 @@
#![cfg_attr( #![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod api; mod api;
mod db; mod db;
@ -10,7 +7,7 @@ mod utils;
use sqlx::sqlite::SqlitePoolOptions; use sqlx::sqlite::SqlitePoolOptions;
use std::fs; use std::fs;
use tauri::Manager; use tauri::Manager;
use tauri_plugin_aptabase::{EventTracker, InitOptions}; use tauri_plugin_aptabase::{ EventTracker, InitOptions };
use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_prevent_default::Flags; use tauri_plugin_prevent_default::Flags;
@ -18,7 +15,8 @@ fn main() {
let runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime"); let runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let _guard = runtime.enter(); let _guard = runtime.enter();
tauri::Builder::default() tauri::Builder
::default()
.plugin(tauri_plugin_clipboard::init()) .plugin(tauri_plugin_clipboard::init())
.plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_sql::Builder::default().build()) .plugin(tauri_plugin_sql::Builder::default().build())
@ -26,12 +24,14 @@ fn main() {
.plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_updater::Builder::default().build()) .plugin(tauri_plugin_updater::Builder::default().build())
.plugin( .plugin(
tauri_plugin_aptabase::Builder::new("A-SH-8937252746") tauri_plugin_aptabase::Builder
::new("A-SH-8937252746")
.with_options(InitOptions { .with_options(InitOptions {
host: Some("https://aptabase.pandadev.net".to_string()), host: Some("https://aptabase.pandadev.net".to_string()),
flush_interval: None, flush_interval: None,
}) })
.with_panic_hook(Box::new(|client, info, msg| { .with_panic_hook(
Box::new(|client, info, msg| {
let location = info let location = info
.location() .location()
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column())) .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
@ -39,21 +39,22 @@ fn main() {
let _ = client.track_event( let _ = client.track_event(
"panic", "panic",
Some(serde_json::json!({ Some(
serde_json::json!({
"info": format!("{} ({})", msg, location), "info": format!("{} ({})", msg, location),
})), })
);
}))
.build(),
) )
.plugin(tauri_plugin_autostart::init( );
MacosLauncher::LaunchAgent, })
Some(vec![]), )
)) .build()
)
.plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, Some(vec![])))
.plugin( .plugin(
tauri_plugin_prevent_default::Builder::new() tauri_plugin_prevent_default::Builder
::new()
.with_flags(Flags::all().difference(Flags::CONTEXT_MENU)) .with_flags(Flags::all().difference(Flags::CONTEXT_MENU))
.build(), .build()
) )
.setup(|app| { .setup(|app| {
let app_data_dir = app.path().app_data_dir().unwrap(); let app_data_dir = app.path().app_data_dir().unwrap();
@ -75,8 +76,7 @@ fn main() {
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let pool = SqlitePoolOptions::new() let pool = SqlitePoolOptions::new()
.max_connections(5) .max_connections(5)
.connect(&db_url) .connect(&db_url).await
.await
.expect("Failed to create pool"); .expect("Failed to create pool");
app_handle_clone.manage(pool); app_handle_clone.manage(pool);
@ -91,7 +91,10 @@ fn main() {
let _ = api::clipboard::start_monitor(app_handle.clone()); let _ = api::clipboard::start_monitor(app_handle.clone());
utils::commands::center_window_on_current_monitor(main_window.as_ref().unwrap()); utils::commands::center_window_on_current_monitor(main_window.as_ref().unwrap());
main_window.as_ref().map(|w| w.hide()).unwrap_or(Ok(()))?; main_window
.as_ref()
.map(|w| w.hide())
.unwrap_or(Ok(()))?;
let _ = app.track_event("app_started", None); let _ = app.track_event("app_started", None);
@ -109,7 +112,8 @@ fn main() {
} }
} }
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(
tauri::generate_handler![
api::clipboard::write_and_paste, api::clipboard::write_and_paste,
db::history::get_history, db::history::get_history,
db::history::add_history_item, db::history::add_history_item,
@ -122,8 +126,9 @@ fn main() {
db::settings::save_setting, db::settings::save_setting,
db::settings::save_keybind, db::settings::save_keybind,
db::settings::get_keybind, db::settings::get_keybind,
utils::commands::fetch_page_meta, utils::commands::fetch_page_meta
]) ]
)
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }

View file

@ -1,11 +1,16 @@
use active_win_pos_rs::get_active_window; use active_win_pos_rs::get_active_window;
use base64::{engine::general_purpose::STANDARD, Engine}; use base64::{ engine::general_purpose::STANDARD, Engine };
use image::codecs::png::PngEncoder; use image::codecs::png::PngEncoder;
use tauri::PhysicalPosition; use tauri::PhysicalPosition;
use meta_fetcher; use meta_fetcher;
pub fn center_window_on_current_monitor(window: &tauri::WebviewWindow) { pub fn center_window_on_current_monitor(window: &tauri::WebviewWindow) {
if let Some(monitor) = window.available_monitors().unwrap().iter().find(|m| { if
let Some(monitor) = window
.available_monitors()
.unwrap()
.iter()
.find(|m| {
let primary_monitor = window let primary_monitor = window
.primary_monitor() .primary_monitor()
.unwrap() .unwrap()
@ -13,22 +18,20 @@ pub fn center_window_on_current_monitor(window: &tauri::WebviewWindow) {
let mouse_position = primary_monitor.position(); let mouse_position = primary_monitor.position();
let monitor_position = m.position(); let monitor_position = m.position();
let monitor_size = m.size(); let monitor_size = m.size();
mouse_position.x >= monitor_position.x mouse_position.x >= monitor_position.x &&
&& mouse_position.x < monitor_position.x + monitor_size.width as i32 mouse_position.x < monitor_position.x + (monitor_size.width as i32) &&
&& mouse_position.y >= monitor_position.y mouse_position.y >= monitor_position.y &&
&& mouse_position.y < monitor_position.y + monitor_size.height as i32 mouse_position.y < monitor_position.y + (monitor_size.height as i32)
}) { })
{
let monitor_size = monitor.size(); let monitor_size = monitor.size();
let window_size = window.outer_size().unwrap(); let window_size = window.outer_size().unwrap();
let x = (monitor_size.width as i32 - window_size.width as i32) / 2; let x = ((monitor_size.width as i32) - (window_size.width as i32)) / 2;
let y = (monitor_size.height as i32 - window_size.height as i32) / 2; let y = ((monitor_size.height as i32) - (window_size.height as i32)) / 2;
window window
.set_position(PhysicalPosition::new( .set_position(PhysicalPosition::new(monitor.position().x + x, monitor.position().y + y))
monitor.position().x + x,
monitor.position().y + y,
))
.unwrap(); .unwrap();
} }
} }
@ -51,7 +54,6 @@ fn _process_icon_to_base64(path: &str) -> Result<String, Box<dyn std::error::Err
Ok(STANDARD.encode(png_buffer)) Ok(STANDARD.encode(png_buffer))
} }
pub fn detect_color(color: &str) -> bool { pub fn detect_color(color: &str) -> bool {
let color = color.trim().to_lowercase(); let color = color.trim().to_lowercase();
@ -60,12 +62,16 @@ pub fn detect_color(color: &str) -> bool {
let hex = &color[1..]; let hex = &color[1..];
return match hex.len() { return match hex.len() {
3 | 6 | 8 => hex.chars().all(|c| c.is_ascii_hexdigit()), 3 | 6 | 8 => hex.chars().all(|c| c.is_ascii_hexdigit()),
_ => false _ => false,
}; };
} }
// rgb/rgba // rgb/rgba
if (color.starts_with("rgb(") || color.starts_with("rgba(")) && color.ends_with(")") && !color[..color.len()-1].contains(")") { if
(color.starts_with("rgb(") || color.starts_with("rgba(")) &&
color.ends_with(")") &&
!color[..color.len() - 1].contains(")")
{
let values = color let values = color
.trim_start_matches("rgba(") .trim_start_matches("rgba(")
.trim_start_matches("rgb(") .trim_start_matches("rgb(")
@ -75,12 +81,16 @@ pub fn detect_color(color: &str) -> bool {
return match values.len() { return match values.len() {
3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()), 3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()),
_ => false _ => false,
}; };
} }
// hsl/hsla // hsl/hsla
if (color.starts_with("hsl(") || color.starts_with("hsla(")) && color.ends_with(")") && !color[..color.len()-1].contains(")") { if
(color.starts_with("hsl(") || color.starts_with("hsla(")) &&
color.ends_with(")") &&
!color[..color.len() - 1].contains(")")
{
let values = color let values = color
.trim_start_matches("hsla(") .trim_start_matches("hsla(")
.trim_start_matches("hsl(") .trim_start_matches("hsl(")
@ -90,7 +100,7 @@ pub fn detect_color(color: &str) -> bool {
return match values.len() { return match values.len() {
3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()), 3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()),
_ => false _ => false,
}; };
} }
@ -99,11 +109,9 @@ pub fn detect_color(color: &str) -> bool {
#[tauri::command] #[tauri::command]
pub async fn fetch_page_meta(url: String) -> Result<(String, Option<String>), String> { pub async fn fetch_page_meta(url: String) -> Result<(String, Option<String>), String> {
let metadata = meta_fetcher::fetch_metadata(&url) let metadata = meta_fetcher
::fetch_metadata(&url)
.map_err(|e| format!("Failed to fetch metadata: {}", e))?; .map_err(|e| format!("Failed to fetch metadata: {}", e))?;
Ok(( Ok((metadata.title.unwrap_or_else(|| "No title found".to_string()), metadata.image))
metadata.title.unwrap_or_else(|| "No title found".to_string()),
metadata.image
))
} }

View file

@ -5,7 +5,7 @@ use reqwest;
use url::Url; use url::Url;
pub async fn fetch_favicon_as_base64( pub async fn fetch_favicon_as_base64(
url: Url, url: Url
) -> Result<Option<String>, Box<dyn std::error::Error>> { ) -> Result<Option<String>, Box<dyn std::error::Error>> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let favicon_url = format!("https://favicone.com/{}", url.host_str().unwrap()); let favicon_url = format!("https://favicone.com/{}", url.host_str().unwrap());

View file

@ -105,7 +105,9 @@ impl FromStr for KeyCode {
"F10" => Code::F10, "F10" => Code::F10,
"F11" => Code::F11, "F11" => Code::F11,
"F12" => Code::F12, "F12" => Code::F12,
_ => return Err(format!("Unknown key code: {}", s)), _ => {
return Err(format!("Unknown key code: {}", s));
}
}; };
Ok(KeyCode(code)) Ok(KeyCode(code))
} }

View file

@ -1,6 +1,6 @@
use chrono; use chrono;
use log::{LevelFilter, SetLoggerError}; use log::{ LevelFilter, SetLoggerError };
use std::fs::{File, OpenOptions}; use std::fs::{ File, OpenOptions };
use std::io::Write; use std::io::Write;
use std::panic; use std::panic;
@ -50,19 +50,22 @@ pub fn init_logger(app_data_dir: &std::path::Path) -> Result<(), SetLoggerError>
// Set up panic hook // Set up panic hook
let panic_file = file.try_clone().expect("Failed to clone file handle"); let panic_file = file.try_clone().expect("Failed to clone file handle");
panic::set_hook(Box::new(move |panic_info| { panic::set_hook(
Box::new(move |panic_info| {
let mut file = panic_file.try_clone().expect("Failed to clone file handle"); let mut file = panic_file.try_clone().expect("Failed to clone file handle");
let location = panic_info.location() let location = panic_info
.location()
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column())) .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
.unwrap_or_else(|| "unknown location".to_string()); .unwrap_or_else(|| "unknown location".to_string());
let message = match panic_info.payload().downcast_ref::<&str>() { let message = match panic_info.payload().downcast_ref::<&str>() {
Some(s) => *s, Some(s) => *s,
None => match panic_info.payload().downcast_ref::<String>() { None =>
match panic_info.payload().downcast_ref::<String>() {
Some(s) => s.as_str(), Some(s) => s.as_str(),
None => "Unknown panic message", None => "Unknown panic message",
}, }
}; };
let _ = writeln!( let _ = writeln!(
@ -72,10 +75,13 @@ pub fn init_logger(app_data_dir: &std::path::Path) -> Result<(), SetLoggerError>
message, message,
location location
); );
})); })
);
let logger = Box::new(FileLogger { file }); let logger = Box::new(FileLogger { file });
unsafe { log::set_logger_racy(Box::leak(logger))? }; unsafe {
log::set_logger_racy(Box::leak(logger))?;
}
log::set_max_level(LevelFilter::Debug); log::set_max_level(LevelFilter::Debug);
Ok(()) Ok(())
} }

View file

@ -1,5 +1,5 @@
use chrono::{DateTime, Utc}; use chrono::{ DateTime, Utc };
use serde::{Deserialize, Serialize}; use serde::{ Deserialize, Serialize };
use std::fmt; use std::fmt;
use uuid::Uuid; use uuid::Uuid;
@ -115,7 +115,7 @@ impl HistoryItem {
content: String, content: String,
favicon: Option<String>, favicon: Option<String>,
source_icon: Option<String>, source_icon: Option<String>,
language: Option<String>, language: Option<String>
) -> Self { ) -> Self {
Self { Self {
id: Uuid::new_v4().to_string(), id: Uuid::new_v4().to_string(),
@ -130,7 +130,7 @@ impl HistoryItem {
} }
pub fn to_row( pub fn to_row(
&self, &self
) -> ( ) -> (
String, String,
String, String,