feat: autoupdate ui

This commit is contained in:
2025-04-12 10:53:06 +00:00
parent 28269c5d75
commit f3016eb029
6 changed files with 92 additions and 24 deletions

View File

@ -11,6 +11,7 @@ use tauri::AppHandle;
pub struct GlobalState { pub struct GlobalState {
pub remain_open: bool, pub remain_open: bool,
pub has_updated: bool,
} }
pub struct AppData { pub struct AppData {
@ -49,7 +50,7 @@ impl AppData {
profile: profile, profile: profile,
pkgs: PackageStore::new(apph.clone()), pkgs: PackageStore::new(apph.clone()),
cfg, cfg,
state: GlobalState { remain_open: true }, state: GlobalState { remain_open: true, has_updated: false },
patch_set patch_set
} }
} }

View File

@ -469,4 +469,12 @@ pub async fn list_patches(state: State<'_, Mutex<AppData>>, target: String) -> R
let list = appd.patch_set.find_patches(target).map_err(|e| e.to_string())?; let list = appd.patch_set.find_patches(target).map_err(|e| e.to_string())?;
Ok(list) Ok(list)
}
#[tauri::command]
pub async fn has_updated(state: State<'_, Mutex<AppData>>) -> Result<bool, ()> {
log::debug!("invoke: has_updated");
let appd = state.lock().await;
Ok(appd.state.has_updated)
} }

View File

@ -17,10 +17,9 @@ use fern::colors::{Color, ColoredLevelConfig};
use model::misc::Game; use model::misc::Game;
use pkg::PkgKey; use pkg::PkgKey;
use pkg_store::Payload; use pkg_store::Payload;
use tauri::{AppHandle, Listener, Manager, RunEvent}; use tauri::{AppHandle, Emitter, Listener, Manager, RunEvent};
use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_cli::CliExt; use tauri_plugin_cli::CliExt;
use tauri_plugin_updater::UpdaterExt;
use tokio::{fs, sync::Mutex, try_join}; use tokio::{fs, sync::Mutex, try_join};
static EXIT_REQUESTED: OnceLock<()> = OnceLock::new(); static EXIT_REQUESTED: OnceLock<()> = OnceLock::new();
@ -248,7 +247,9 @@ pub async fn run(_args: Vec<String>) {
cmd::list_com_ports, cmd::list_com_ports,
cmd::list_patches cmd::list_patches,
cmd::has_updated,
]) ])
.build(tauri::generate_context!()) .build(tauri::generate_context!())
.expect("error while building tauri application"); .expect("error while building tauri application");
@ -309,28 +310,56 @@ fn deep_link(app: AppHandle, args: Vec<String>) {
async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> { async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> {
let mutex = app.state::<Mutex<AppData>>(); let mutex = app.state::<Mutex<AppData>>();
let appd = mutex.lock().await; {
if !appd.cfg.enable_autoupdates { let mut appd = mutex.lock().await;
log::info!("skipping autoupdate"); if !appd.cfg.enable_autoupdates {
return Ok(()); log::info!("skipping auto-update");
// The frontend may not be available at this point
// So emit isn't suitable
appd.state.has_updated = true;
return Ok(());
}
} }
if let Some(update) = app.updater()?.check().await? { #[cfg(not(debug_assertions))]
{
use tauri_plugin_updater::UpdaterExt;
if let Some(update) = app.updater()?.check().await? {
let mut downloaded = 0;
update.download_and_install(
|chunk_length, content_length| {
downloaded += chunk_length;
_ = app.emit("update-progress", (chunk_length as f64) / (content_length.unwrap_or(u64::MAX) as f64));
},
|| {
log::info!("download finished");
},
)
.await?;
log::info!("update installed");
app.restart();
}
}
// One day I will write proper tests
#[cfg(debug_assertions)]
{
std::thread::sleep(std::time::Duration::from_millis(2000));
let mut downloaded = 0; let mut downloaded = 0;
update.download_and_install( while downloaded < 200 {
|chunk_length, content_length| { std::thread::sleep(std::time::Duration::from_millis(10));
downloaded += chunk_length; downloaded += 1;
log::debug!("downloaded {downloaded} from {content_length:?}"); app.emit("update-progress", (downloaded as f32) / 200f32)?;
}, }
|| {
log::info!("download finished");
},
)
.await?;
log::info!("update installed");
app.restart();
} }
log::info!("ending auto-update check");
let mut appd = mutex.lock().await;
appd.state.has_updated = true;
Ok(()) Ok(())
} }

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "STARTLINER", "productName": "STARTLINER",
"version": "0.5.0", "version": "0.6.0",
"identifier": "zip.patafour.startliner", "identifier": "zip.patafour.startliner",
"build": { "build": {
"beforeDevCommand": "bun run dev", "beforeDevCommand": "bun run dev",

View File

@ -40,10 +40,30 @@ const pkgSearchTerm = ref('');
const isProfileDisabled = computed(() => prf.current === null); const isProfileDisabled = computed(() => prf.current === null);
const updateVisible = ref(false);
const updateProgress = ref(-1);
const hasUpdatedCheck = async () => {
const res = await invoke('has_updated');
if (res == false) {
updateVisible.value = true;
setTimeout(hasUpdatedCheck, 200);
} else {
updateVisible.value = false;
}
};
listen<number>('update-progress', (ev) => {
updateProgress.value = ev.payload;
});
onMounted(async () => { onMounted(async () => {
invoke('list_directories').then((d) => { invoke('list_directories').then((d) => {
general.dirs = d as Dirs; general.dirs = d as Dirs;
client.load(); client.load();
if (client.enableAutoupdates) {
hasUpdatedCheck();
}
}); });
const fetch_promise = pkg.fetch(true); const fetch_promise = pkg.fetch(true);
@ -135,6 +155,17 @@ listen<{ message: string; header: string }>('invoke-error', (event) => {
/> />
</div> </div>
</Dialog> </Dialog>
<Dialog
modal
:visible="updateVisible"
:closable="false"
:header="updateProgress < 0 ? 'Checking for updates' : 'Updating'"
:style="{ width: '50vw' }"
>
<div v-if="updateProgress >= 0">
{{ (updateProgress * 100).toFixed(0) }}%
</div>
</Dialog>
<Tabs <Tabs
lazy lazy

View File

@ -2,7 +2,6 @@
import { Ref, ref } from 'vue'; import { Ref, ref } from 'vue';
import * as path from '@tauri-apps/api/path'; import * as path from '@tauri-apps/api/path';
import OptionCategory from './OptionCategory.vue'; import OptionCategory from './OptionCategory.vue';
import OptionRow from './OptionRow.vue';
import PatchEntry from './PatchEntry.vue'; import PatchEntry from './PatchEntry.vue';
import { invoke } from '../invoke'; import { invoke } from '../invoke';
import { usePrfStore } from '../stores'; import { usePrfStore } from '../stores';