mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:14:03 +01:00
Pass the previous app version to the notification endpoint so the update notification can display all missed changelogs, not just the latest one.
This commit is contained in:
@@ -1,48 +1,74 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use log::debug;
|
||||
use std::sync::OnceLock;
|
||||
use tauri::{AppHandle, Runtime};
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::UpdateSource;
|
||||
|
||||
const NAMESPACE: &str = "analytics";
|
||||
const NUM_LAUNCHES_KEY: &str = "num_launches";
|
||||
const LAST_VERSION_KEY: &str = "last_tracked_version";
|
||||
const PREV_VERSION_KEY: &str = "last_tracked_version_prev";
|
||||
const VERSION_SINCE_KEY: &str = "last_tracked_version_since";
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct LaunchEventInfo {
|
||||
pub current_version: String,
|
||||
pub previous_version: String,
|
||||
pub launched_after_update: bool,
|
||||
pub version_since: NaiveDateTime,
|
||||
pub user_since: NaiveDateTime,
|
||||
pub num_launches: i32,
|
||||
}
|
||||
|
||||
pub async fn store_launch_history<R: Runtime>(app_handle: &AppHandle<R>) -> LaunchEventInfo {
|
||||
let last_tracked_version_key = "last_tracked_version";
|
||||
static LAUNCH_INFO: OnceLock<LaunchEventInfo> = OnceLock::new();
|
||||
|
||||
let mut info = LaunchEventInfo::default();
|
||||
pub fn get_or_upsert_launch_info<R: Runtime>(app_handle: &AppHandle<R>) -> &LaunchEventInfo {
|
||||
LAUNCH_INFO.get_or_init(|| {
|
||||
let now = Utc::now().naive_utc();
|
||||
let mut info = LaunchEventInfo {
|
||||
version_since: app_handle.db().get_key_value_dte(NAMESPACE, VERSION_SINCE_KEY, now),
|
||||
current_version: app_handle.package_info().version.to_string(),
|
||||
user_since: app_handle.db().get_settings().created_at,
|
||||
num_launches: app_handle.db().get_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, 0) + 1,
|
||||
|
||||
info.num_launches = get_num_launches(app_handle).await + 1;
|
||||
info.current_version = app_handle.package_info().version.to_string();
|
||||
// The rest will be set below
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
app_handle
|
||||
.with_tx(|tx| {
|
||||
info.previous_version =
|
||||
tx.get_key_value_string(NAMESPACE, last_tracked_version_key, "");
|
||||
app_handle
|
||||
.with_tx(|tx| {
|
||||
// Load the previously tracked version
|
||||
let curr_db = tx.get_key_value_str(NAMESPACE, LAST_VERSION_KEY, "");
|
||||
let prev_db = tx.get_key_value_str(NAMESPACE, PREV_VERSION_KEY, "");
|
||||
|
||||
if !info.previous_version.is_empty() {
|
||||
info.launched_after_update = info.current_version != info.previous_version;
|
||||
};
|
||||
// We just updated if the app version is different from the last tracked version we stored
|
||||
if !curr_db.is_empty() && info.current_version != curr_db {
|
||||
info.launched_after_update = true;
|
||||
}
|
||||
|
||||
// Update key values
|
||||
// If we just updated, track the previous version as the "previous" current version
|
||||
if info.launched_after_update {
|
||||
info.previous_version = curr_db.clone();
|
||||
info.version_since = now;
|
||||
} else {
|
||||
info.previous_version = prev_db.clone();
|
||||
}
|
||||
|
||||
let source = &UpdateSource::Background;
|
||||
let version = info.current_version.as_str();
|
||||
tx.set_key_value_string(NAMESPACE, last_tracked_version_key, version, source);
|
||||
tx.set_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, source);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
// Rotate stored versions: move previous into the "prev" slot before overwriting
|
||||
let source = &UpdateSource::Background;
|
||||
|
||||
info
|
||||
}
|
||||
|
||||
pub async fn get_num_launches<R: Runtime>(app_handle: &AppHandle<R>) -> i32 {
|
||||
app_handle.db().get_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, 0)
|
||||
tx.set_key_value_str(NAMESPACE, PREV_VERSION_KEY, &info.previous_version, source);
|
||||
tx.set_key_value_str(NAMESPACE, LAST_VERSION_KEY, &info.current_version, source);
|
||||
tx.set_key_value_dte(NAMESPACE, VERSION_SINCE_KEY, info.version_since, source);
|
||||
tx.set_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, source);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
debug!("Initialized launch info");
|
||||
|
||||
info
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1454,7 +1454,7 @@ pub fn run() {
|
||||
let _ = window::create_main_window(app_handle, "/");
|
||||
let h = app_handle.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let info = history::store_launch_history(&h).await;
|
||||
let info = history::get_or_upsert_launch_info(&h);
|
||||
debug!("Launched Yaak {:?}", info);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::history::get_num_launches;
|
||||
use crate::history::get_or_upsert_launch_info;
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::debug;
|
||||
use reqwest::Method;
|
||||
@@ -79,7 +79,7 @@ impl YaakNotifier {
|
||||
|
||||
#[cfg(feature = "license")]
|
||||
let license_check = {
|
||||
use yaak_license::{LicenseCheckStatus, check_license};
|
||||
use yaak_license::{check_license, LicenseCheckStatus};
|
||||
match check_license(window).await {
|
||||
Ok(LicenseCheckStatus::PersonalUse { .. }) => "personal".to_string(),
|
||||
Ok(LicenseCheckStatus::CommercialUse) => "commercial".to_string(),
|
||||
@@ -91,17 +91,17 @@ impl YaakNotifier {
|
||||
#[cfg(not(feature = "license"))]
|
||||
let license_check = "disabled".to_string();
|
||||
|
||||
let settings = window.db().get_settings();
|
||||
let num_launches = get_num_launches(app_handle).await;
|
||||
let info = app_handle.package_info().clone();
|
||||
let launch_info = get_or_upsert_launch_info(app_handle);
|
||||
let req = yaak_api_client(app_handle)?
|
||||
.request(Method::GET, "https://notify.yaak.app/notifications")
|
||||
.query(&[
|
||||
("version", info.version.to_string().as_str()),
|
||||
("launches", num_launches.to_string().as_str()),
|
||||
("installed", settings.created_at.format("%Y-%m-%d").to_string().as_str()),
|
||||
("version", &launch_info.current_version),
|
||||
("version_prev", &launch_info.previous_version),
|
||||
("launches", &launch_info.num_launches.to_string()),
|
||||
("installed", &launch_info.user_since.format("%Y-%m-%d").to_string()),
|
||||
("license", &license_check),
|
||||
("platform", get_os()),
|
||||
("updates", &get_updater_status(app_handle).to_string()),
|
||||
("platform", &get_os().to_string()),
|
||||
]);
|
||||
let resp = req.send().await?;
|
||||
if resp.status() != 200 {
|
||||
@@ -131,3 +131,32 @@ async fn get_kv<R: Runtime>(app_handle: &AppHandle<R>) -> Result<Vec<String>> {
|
||||
Some(v) => Ok(serde_json::from_str(&v.value)?),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_updater_status<R: Runtime>(app_handle: &AppHandle<R>) -> &'static str {
|
||||
#[cfg(not(feature = "updater"))]
|
||||
{
|
||||
// Updater is not enabled as a Rust feature
|
||||
return "missing";
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "updater", target_os = "linux"))]
|
||||
{
|
||||
let settings = app_handle.db().get_settings();
|
||||
if !settings.autoupdate {
|
||||
// Updates are explicitly disabled
|
||||
"disabled"
|
||||
} else if std::env::var("APPIMAGE").is_err() {
|
||||
// Updates are enabled, but unsupported
|
||||
"unsupported"
|
||||
} else {
|
||||
// Updates are enabled and supported
|
||||
"enabled"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "updater", not(target_os = "linux")))]
|
||||
{
|
||||
let settings = app_handle.db().get_settings();
|
||||
if settings.autoupdate { "enabled" } else { "disabled" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ pub async fn activate_license<R: Runtime>(
|
||||
}
|
||||
|
||||
let body: ActivateLicenseResponsePayload = response.json().await?;
|
||||
window.app_handle().db().set_key_value_string(
|
||||
window.app_handle().db().set_key_value_str(
|
||||
KV_ACTIVATION_ID_KEY,
|
||||
KV_NAMESPACE,
|
||||
body.activation_id.as_str(),
|
||||
@@ -207,5 +207,5 @@ fn build_url(path: &str) -> String {
|
||||
}
|
||||
|
||||
pub async fn get_activation_id<R: Runtime>(app_handle: &AppHandle<R>) -> String {
|
||||
app_handle.db().get_key_value_string(KV_ACTIVATION_ID_KEY, KV_NAMESPACE, "")
|
||||
app_handle.db().get_key_value_str(KV_ACTIVATION_ID_KEY, KV_NAMESPACE, "")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{KeyValue, KeyValueIden, UpsertModelInfo};
|
||||
@@ -22,7 +23,7 @@ impl<'a> DbContext<'a> {
|
||||
Ok(items.map(|v| v.unwrap()).collect())
|
||||
}
|
||||
|
||||
pub fn get_key_value_string(&self, namespace: &str, key: &str, default: &str) -> String {
|
||||
pub fn get_key_value_str(&self, namespace: &str, key: &str, default: &str) -> String {
|
||||
match self.get_key_value_raw(namespace, key) {
|
||||
None => default.to_string(),
|
||||
Some(v) => {
|
||||
@@ -38,6 +39,22 @@ impl<'a> DbContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_key_value_dte(&self, namespace: &str, key: &str, default: NaiveDateTime) -> NaiveDateTime {
|
||||
match self.get_key_value_raw(namespace, key) {
|
||||
None => default,
|
||||
Some(v) => {
|
||||
let result = serde_json::from_str(&v.value);
|
||||
match result {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("Failed to parse date key value: {}", e);
|
||||
default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_key_value_int(&self, namespace: &str, key: &str, default: i32) -> i32 {
|
||||
match self.get_key_value_raw(namespace, key) {
|
||||
None => default.clone(),
|
||||
@@ -67,7 +84,18 @@ impl<'a> DbContext<'a> {
|
||||
self.conn.resolve().query_row(sql.as_str(), &*params.as_params(), KeyValue::from_row).ok()
|
||||
}
|
||||
|
||||
pub fn set_key_value_string(
|
||||
pub fn set_key_value_dte(
|
||||
&self,
|
||||
namespace: &str,
|
||||
key: &str,
|
||||
value: NaiveDateTime,
|
||||
source: &UpdateSource,
|
||||
) -> (KeyValue, bool) {
|
||||
let encoded = serde_json::to_string(&value).unwrap();
|
||||
self.set_key_value_raw(namespace, key, &encoded, source)
|
||||
}
|
||||
|
||||
pub fn set_key_value_str(
|
||||
&self,
|
||||
namespace: &str,
|
||||
key: &str,
|
||||
|
||||
Reference in New Issue
Block a user