mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-22 05:34:04 +02:00
refactor: Clean up code formatting in utils module
This commit is contained in:
parent
60b670c7a7
commit
22fcd84b8d
14 changed files with 485 additions and 394 deletions
|
@ -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");
|
||||||
|
|
|
@ -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" }
|
||||||
})),
|
})
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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(|_| {});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
))
|
|
||||||
}
|
}
|
|
@ -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());
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue