diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 675d1c5..43ff881 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1643,6 +1643,21 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -1729,6 +1744,7 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -4081,6 +4097,7 @@ dependencies = [ "sqlx", "tauri", "tauri-build", + "tauri-plugin-aptabase", "tauri-plugin-autostart", "tauri-plugin-clipboard", "tauri-plugin-dialog", @@ -4796,9 +4813,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa 1.0.11", "memchr", @@ -5664,6 +5681,25 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-aptabase" +version = "0.5.1" +source = "git+https://github.com/aptabase/tauri-plugin-aptabase?branch=v2#373abe1954bf20152082e506d36be07727259bb5" +dependencies = [ + "futures", + "log", + "os_info", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "sys-locale", + "tauri", + "tauri-plugin", + "time", + "tokio", +] + [[package]] name = "tauri-plugin-autostart" version = "2.2.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9710029..d151155 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,7 +13,7 @@ tauri-build = { version = "2.0.3", features = [] } tauri = { version = "2.1.1", features = [ "macos-private-api", "tray-icon", - "image-png", + "image-png" ] } tauri-plugin-sql = { version = "2.2.0", features = ["sqlite"] } tauri-plugin-autostart = "2.2.0" @@ -24,10 +24,11 @@ tauri-plugin-fs = "2.2.0" tauri-plugin-clipboard = "2.1.11" tauri-plugin-prevent-default = "1.0.1" tauri-plugin-global-shortcut = "2.2.0" +tauri-plugin-aptabase = { git = "https://github.com/aptabase/tauri-plugin-aptabase", branch = "v2" } sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "sqlite", "chrono"] } serde = { version = "1.0.216", features = ["derive"] } tokio = { version = "1.42.0", features = ["full"] } -serde_json = "1.0.133" +serde_json = "1.0.134" rdev = "0.5.3" rand = "0.8.5" base64 = "0.22.1" diff --git a/src-tauri/src/api/clipboard.rs b/src-tauri/src/api/clipboard.rs index 76f245f..59ac7b1 100644 --- a/src-tauri/src/api/clipboard.rs +++ b/src-tauri/src/api/clipboard.rs @@ -1,3 +1,4 @@ +use tauri_plugin_aptabase::EventTracker; use base64::{engine::general_purpose::STANDARD, Engine}; // use hyperpolyglot; use lazy_static::lazy_static; @@ -7,7 +8,7 @@ use sqlx::SqlitePool; use std::fs; use std::sync::atomic::{AtomicBool, Ordering}; use std::{thread, time::Duration}; -use tauri::{AppHandle, Emitter, Listener, Manager, Runtime}; +use tauri::{AppHandle, Emitter, Listener, Manager}; use tauri_plugin_clipboard::Clipboard; use tokio::runtime::Runtime as TokioRuntime; use url::Url; @@ -23,8 +24,8 @@ lazy_static! { } #[tauri::command] -pub async fn write_and_paste( - app_handle: tauri::AppHandle, +pub async fn write_and_paste( + app_handle: AppHandle, content: String, content_type: String, ) -> Result<(), String> { @@ -80,39 +81,44 @@ pub async fn write_and_paste( IS_PROGRAMMATIC_PASTE.store(false, Ordering::SeqCst); }); + let _ = app_handle.track_event("clipboard_paste", Some(serde_json::json!({ + "content_type": content_type + }))); + Ok(()) } -pub fn setup(app: &AppHandle) { - let app = app.clone(); +pub fn setup(app: &AppHandle) { + let app_handle = app.clone(); let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime"); - app.clone().listen( + app_handle.clone().listen( "plugin:clipboard://clipboard-monitor/update", move |_event| { - let app = app.clone(); + let app_handle = app_handle.clone(); runtime.block_on(async move { if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) { return; } - let clipboard = app.state::(); + let clipboard = app_handle.state::(); let available_types = clipboard.available_types().unwrap(); let (app_name, app_icon) = get_app_info(); - match get_pool(&app).await { + match get_pool(&app_handle).await { Ok(pool) => { if available_types.image { println!("Handling image change"); if let Ok(image_data) = clipboard.read_image_base64() { - let file_path = save_image_to_file(&app, &image_data) + let file_path = save_image_to_file(&app_handle, &image_data) .await .map_err(|e| e.to_string()) .unwrap_or_else(|e| e); let _ = db::history::add_history_item( + app_handle.clone(), 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; } } else if available_types.files { @@ -120,6 +126,7 @@ pub fn setup(app: &AppHandle) { if let Ok(files) = clipboard.read_files() { for file in files { let _ = db::history::add_history_item( + app_handle.clone(), pool.clone(), HistoryItem::new( app_name.clone(), @@ -135,6 +142,7 @@ pub fn setup(app: &AppHandle) { } else if available_types.text { println!("Handling text change"); if let Ok(text) = clipboard.read_text() { + 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(); if url_regex.is_match(&text) { @@ -145,6 +153,7 @@ pub fn setup(app: &AppHandle) { }; let _ = db::history::add_history_item( + app_handle.clone(), pool, HistoryItem::new(app_name, ContentType::Link, text, favicon, app_icon, None) ).await; @@ -167,13 +176,15 @@ pub fn setup(app: &AppHandle) { ).await; } else*/ if crate::utils::commands::detect_color(&text) { let _ = db::history::add_history_item( + app_handle.clone(), pool, HistoryItem::new(app_name, ContentType::Color, text, None, app_icon, None) ).await; } else { let _ = db::history::add_history_item( + app_handle.clone(), pool, - HistoryItem::new(app_name, ContentType::Text, text, None, app_icon, None) + HistoryItem::new(app_name, ContentType::Text, text.clone(), None, app_icon, None) ).await; } } @@ -187,14 +198,20 @@ pub fn setup(app: &AppHandle) { } } - let _ = app.emit("clipboard-content-updated", ()); + let _ = app_handle.emit("clipboard-content-updated", ()); + let _ = app_handle.track_event("clipboard_copied", Some(serde_json::json!({ + "content_type": if available_types.image { "image" } + else if available_types.files { "files" } + else if available_types.text { "text" } + else { "unknown" } + }))); }); }, ); } -async fn get_pool( - app_handle: &AppHandle, +async fn get_pool( + app_handle: &AppHandle, ) -> Result, Box> { Ok(app_handle.state::()) } @@ -211,8 +228,8 @@ pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> { Ok(()) } -async fn save_image_to_file( - app_handle: &AppHandle, +async fn save_image_to_file( + app_handle: &AppHandle, base64_data: &str, ) -> Result> { let app_data_dir = app_handle.path().app_data_dir().unwrap(); diff --git a/src-tauri/src/api/hotkeys.rs b/src-tauri/src/api/hotkeys.rs index 7a0b015..0470e24 100644 --- a/src-tauri/src/api/hotkeys.rs +++ b/src-tauri/src/api/hotkeys.rs @@ -1,3 +1,4 @@ +use tauri_plugin_aptabase::EventTracker; use crate::utils::commands::center_window_on_current_monitor; use global_hotkey::{ hotkey::{Code, HotKey, Modifiers}, @@ -142,4 +143,8 @@ fn handle_hotkey_event(app_handle: &AppHandle) { center_window_on_current_monitor(&window); } + + let _ = app_handle.track_event("hotkey_triggered", Some(serde_json::json!({ + "action": if window.is_visible().unwrap() { "hide" } else { "show" } + }))); } diff --git a/src-tauri/src/api/tray.rs b/src-tauri/src/api/tray.rs index 82dd843..8c3d9bc 100644 --- a/src-tauri/src/api/tray.rs +++ b/src-tauri/src/api/tray.rs @@ -3,10 +3,14 @@ use tauri::{ tray::TrayIconBuilder, Emitter, Manager, }; +use tauri_plugin_aptabase::EventTracker; pub fn setup(app: &mut tauri::App) -> Result<(), Box> { let window = app.get_webview_window("main").unwrap(); - let window_clone_for_tray = window.clone(); + let is_visible = window.is_visible().unwrap(); + let _ = app.track_event("tray_toggle", Some(serde_json::json!({ + "action": if is_visible { "hide" } else { "show" } + }))); let icon_bytes = include_bytes!("../../icons/Square71x71Logo.png"); let icon = tauri::image::Image::from_bytes(icon_bytes).unwrap(); @@ -24,20 +28,25 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box> { ) .on_menu_event(move |_app, event| match event.id().as_ref() { "quit" => { + let _ = _app.track_event("app_quit", None); std::process::exit(0); } "show" => { - let is_visible = window_clone_for_tray.is_visible().unwrap(); + let _ = _app.track_event("tray_toggle", Some(serde_json::json!({ + "action": if is_visible { "hide" } else { "show" } + }))); + let is_visible = window.is_visible().unwrap(); if is_visible { - window_clone_for_tray.hide().unwrap(); + window.hide().unwrap(); } else { - window_clone_for_tray.show().unwrap(); - window_clone_for_tray.set_focus().unwrap(); + window.show().unwrap(); + window.set_focus().unwrap(); } - window_clone_for_tray.emit("main_route", ()).unwrap(); + window.emit("main_route", ()).unwrap(); } "keybind" => { - window_clone_for_tray.emit("change_keybind", ()).unwrap(); + let _ = _app.track_event("tray_keybind_change", None); + window.emit("change_keybind", ()).unwrap(); } _ => (), }) diff --git a/src-tauri/src/db/history.rs b/src-tauri/src/db/history.rs index fc24bcb..5296a79 100644 --- a/src-tauri/src/db/history.rs +++ b/src-tauri/src/db/history.rs @@ -4,6 +4,7 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use sqlx::{Row, SqlitePool}; use std::fs; +use tauri_plugin_aptabase::EventTracker; pub async fn initialize_history(pool: &SqlitePool) -> Result<(), Box> { let id: String = thread_rng() @@ -53,6 +54,7 @@ pub async fn get_history(pool: tauri::State<'_, SqlitePool>) -> Result, item: HistoryItem, ) -> Result<(), String> { @@ -95,6 +97,10 @@ pub async fn add_history_item( } } + let _ = app_handle.track_event("history_item_added", Some(serde_json::json!({ + "content_type": item.content_type.to_string() + }))); + Ok(()) } @@ -163,6 +169,7 @@ pub async fn load_history_chunk( #[tauri::command] pub async fn delete_history_item( + app_handle: tauri::AppHandle, pool: tauri::State<'_, SqlitePool>, id: String, ) -> Result<(), String> { @@ -172,16 +179,23 @@ pub async fn delete_history_item( .await .map_err(|e| e.to_string())?; + let _ = app_handle.track_event("history_item_deleted", None); + Ok(()) } #[tauri::command] -pub async fn clear_history(pool: tauri::State<'_, SqlitePool>) -> Result<(), String> { +pub async fn clear_history( + app_handle: tauri::AppHandle, + pool: tauri::State<'_, SqlitePool> +) -> Result<(), String> { sqlx::query("DELETE FROM history") .execute(&*pool) .await .map_err(|e| e.to_string())?; + let _ = app_handle.track_event("history_cleared", None); + Ok(()) } diff --git a/src-tauri/src/db/settings.rs b/src-tauri/src/db/settings.rs index 406bee0..bdd6f58 100644 --- a/src-tauri/src/db/settings.rs +++ b/src-tauri/src/db/settings.rs @@ -3,6 +3,7 @@ use serde_json; use sqlx::Row; use sqlx::SqlitePool; use tauri::{Emitter, Manager}; +use tauri_plugin_aptabase::EventTracker; #[derive(Deserialize, Serialize)] struct KeybindSetting { @@ -26,9 +27,16 @@ pub async fn initialize_settings(pool: &SqlitePool) -> Result<(), Box, pool: tauri::State<'_, SqlitePool>, + keybind: Vec, ) -> Result<(), String> { + let keybind_str = keybind.join("+"); + let keybind_clone = keybind_str.clone(); + + app_handle + .emit("update-shortcut", &keybind_str) + .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', ?)") @@ -37,10 +45,9 @@ pub async fn save_keybind( .await .map_err(|e| e.to_string())?; - let keybind_str = keybind.join("+"); - app_handle - .emit("update-shortcut", keybind_str) - .map_err(|e| e.to_string())?; + let _ = app_handle.track_event("keybind_saved", Some(serde_json::json!({ + "keybind": keybind_clone + }))); Ok(()) } @@ -61,17 +68,22 @@ pub async fn get_setting( #[tauri::command] pub async fn save_setting( + app_handle: tauri::AppHandle, pool: tauri::State<'_, SqlitePool>, key: String, value: String, ) -> Result<(), String> { sqlx::query("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)") - .bind(key) + .bind(key.clone()) .bind(value) .execute(&*pool) .await .map_err(|e| e.to_string())?; + let _ = app_handle.track_event("setting_saved", Some(serde_json::json!({ + "key": key + }))); + Ok(()) } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0621936..c6aed37 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,15 +7,19 @@ mod api; mod db; mod utils; -use sqlx::sqlite::SqlitePoolOptions; use std::fs; use tauri::Manager; use tauri::WebviewUrl; use tauri::WebviewWindow; +use sqlx::sqlite::SqlitePoolOptions; use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_prevent_default::Flags; +use tauri_plugin_aptabase::{EventTracker, InitOptions}; fn main() { + let runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime"); + let _guard = runtime.enter(); + tauri::Builder::default() .plugin(tauri_plugin_clipboard::init()) .plugin(tauri_plugin_os::init()) @@ -23,6 +27,19 @@ fn main() { .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_updater::Builder::default().build()) + .plugin(tauri_plugin_aptabase::Builder::new("A-SH-8937252746") + .with_options(InitOptions { + host: Some("https://aptabase.pandadev.net".to_string()), + flush_interval: None, + }) + .with_panic_hook(Box::new(|client, info, msg| { + let location = info.location().map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column())).unwrap_or_else(|| "".to_string()); + + let _ = client.track_event("panic", Some(serde_json::json!({ + "info": format!("{} ({})", msg, location), + }))); + })) + .build()) .plugin(tauri_plugin_autostart::init( MacosLauncher::LaunchAgent, Some(vec![]), @@ -85,6 +102,8 @@ fn main() { utils::commands::center_window_on_current_monitor(&main_window); main_window.hide()?; + let _ = app.track_event("app_started", None); + tauri::async_runtime::spawn(async move { api::updater::check_for_updates(app_handle).await; });