mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-07-09 06:15:08 +02: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:
+52
-26
@@ -1,48 +1,74 @@
|
|||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use log::debug;
|
||||||
|
use std::sync::OnceLock;
|
||||||
use tauri::{AppHandle, Runtime};
|
use tauri::{AppHandle, Runtime};
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
|
|
||||||
const NAMESPACE: &str = "analytics";
|
const NAMESPACE: &str = "analytics";
|
||||||
const NUM_LAUNCHES_KEY: &str = "num_launches";
|
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 struct LaunchEventInfo {
|
||||||
pub current_version: String,
|
pub current_version: String,
|
||||||
pub previous_version: String,
|
pub previous_version: String,
|
||||||
pub launched_after_update: bool,
|
pub launched_after_update: bool,
|
||||||
|
pub version_since: NaiveDateTime,
|
||||||
|
pub user_since: NaiveDateTime,
|
||||||
pub num_launches: i32,
|
pub num_launches: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn store_launch_history<R: Runtime>(app_handle: &AppHandle<R>) -> LaunchEventInfo {
|
static LAUNCH_INFO: OnceLock<LaunchEventInfo> = OnceLock::new();
|
||||||
let last_tracked_version_key = "last_tracked_version";
|
|
||||||
|
|
||||||
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;
|
// The rest will be set below
|
||||||
info.current_version = app_handle.package_info().version.to_string();
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
app_handle
|
app_handle
|
||||||
.with_tx(|tx| {
|
.with_tx(|tx| {
|
||||||
info.previous_version =
|
// Load the previously tracked version
|
||||||
tx.get_key_value_string(NAMESPACE, last_tracked_version_key, "");
|
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() {
|
// We just updated if the app version is different from the last tracked version we stored
|
||||||
info.launched_after_update = info.current_version != info.previous_version;
|
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;
|
// Rotate stored versions: move previous into the "prev" slot before overwriting
|
||||||
let version = info.current_version.as_str();
|
let source = &UpdateSource::Background;
|
||||||
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();
|
|
||||||
|
|
||||||
info
|
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);
|
||||||
pub async fn get_num_launches<R: Runtime>(app_handle: &AppHandle<R>) -> i32 {
|
tx.set_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, source);
|
||||||
app_handle.db().get_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, 0)
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
debug!("Initialized launch info");
|
||||||
|
|
||||||
|
info
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1454,7 +1454,7 @@ pub fn run() {
|
|||||||
let _ = window::create_main_window(app_handle, "/");
|
let _ = window::create_main_window(app_handle, "/");
|
||||||
let h = app_handle.clone();
|
let h = app_handle.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
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);
|
debug!("Launched Yaak {:?}", info);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::history::get_num_launches;
|
use crate::history::get_or_upsert_launch_info;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
@@ -79,7 +79,7 @@ impl YaakNotifier {
|
|||||||
|
|
||||||
#[cfg(feature = "license")]
|
#[cfg(feature = "license")]
|
||||||
let license_check = {
|
let license_check = {
|
||||||
use yaak_license::{LicenseCheckStatus, check_license};
|
use yaak_license::{check_license, LicenseCheckStatus};
|
||||||
match check_license(window).await {
|
match check_license(window).await {
|
||||||
Ok(LicenseCheckStatus::PersonalUse { .. }) => "personal".to_string(),
|
Ok(LicenseCheckStatus::PersonalUse { .. }) => "personal".to_string(),
|
||||||
Ok(LicenseCheckStatus::CommercialUse) => "commercial".to_string(),
|
Ok(LicenseCheckStatus::CommercialUse) => "commercial".to_string(),
|
||||||
@@ -91,17 +91,17 @@ impl YaakNotifier {
|
|||||||
#[cfg(not(feature = "license"))]
|
#[cfg(not(feature = "license"))]
|
||||||
let license_check = "disabled".to_string();
|
let license_check = "disabled".to_string();
|
||||||
|
|
||||||
let settings = window.db().get_settings();
|
let launch_info = get_or_upsert_launch_info(app_handle);
|
||||||
let num_launches = get_num_launches(app_handle).await;
|
|
||||||
let info = app_handle.package_info().clone();
|
|
||||||
let req = yaak_api_client(app_handle)?
|
let req = yaak_api_client(app_handle)?
|
||||||
.request(Method::GET, "https://notify.yaak.app/notifications")
|
.request(Method::GET, "https://notify.yaak.app/notifications")
|
||||||
.query(&[
|
.query(&[
|
||||||
("version", info.version.to_string().as_str()),
|
("version", &launch_info.current_version),
|
||||||
("launches", num_launches.to_string().as_str()),
|
("version_prev", &launch_info.previous_version),
|
||||||
("installed", settings.created_at.format("%Y-%m-%d").to_string().as_str()),
|
("launches", &launch_info.num_launches.to_string()),
|
||||||
|
("installed", &launch_info.user_since.format("%Y-%m-%d").to_string()),
|
||||||
("license", &license_check),
|
("license", &license_check),
|
||||||
("platform", get_os()),
|
("updates", &get_updater_status(app_handle).to_string()),
|
||||||
|
("platform", &get_os().to_string()),
|
||||||
]);
|
]);
|
||||||
let resp = req.send().await?;
|
let resp = req.send().await?;
|
||||||
if resp.status() != 200 {
|
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)?),
|
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?;
|
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_ACTIVATION_ID_KEY,
|
||||||
KV_NAMESPACE,
|
KV_NAMESPACE,
|
||||||
body.activation_id.as_str(),
|
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 {
|
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::db_context::DbContext;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::{KeyValue, KeyValueIden, UpsertModelInfo};
|
use crate::models::{KeyValue, KeyValueIden, UpsertModelInfo};
|
||||||
@@ -22,7 +23,7 @@ impl<'a> DbContext<'a> {
|
|||||||
Ok(items.map(|v| v.unwrap()).collect())
|
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) {
|
match self.get_key_value_raw(namespace, key) {
|
||||||
None => default.to_string(),
|
None => default.to_string(),
|
||||||
Some(v) => {
|
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 {
|
pub fn get_key_value_int(&self, namespace: &str, key: &str, default: i32) -> i32 {
|
||||||
match self.get_key_value_raw(namespace, key) {
|
match self.get_key_value_raw(namespace, key) {
|
||||||
None => default.clone(),
|
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()
|
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,
|
&self,
|
||||||
namespace: &str,
|
namespace: &str,
|
||||||
key: &str,
|
key: &str,
|
||||||
|
|||||||
Reference in New Issue
Block a user