aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src-tauri/bindings/InstallProgress.ts4
-rw-r--r--src-tauri/bindings/InstallState.ts3
-rw-r--r--src-tauri/src/lib.rs74
-rw-r--r--src-tauri/src/main.rs6
-rw-r--r--src-vue/src/components/InstallProgressBar.vue102
-rw-r--r--src-vue/src/components/PlayButton.vue48
-rw-r--r--src-vue/src/i18n/lang/en.json4
-rw-r--r--src-vue/src/views/PlayView.vue17
-rw-r--r--src-vue/src/views/RepairView.vue11
9 files changed, 231 insertions, 38 deletions
diff --git a/src-tauri/bindings/InstallProgress.ts b/src-tauri/bindings/InstallProgress.ts
new file mode 100644
index 00000000..7bea9bb8
--- /dev/null
+++ b/src-tauri/bindings/InstallProgress.ts
@@ -0,0 +1,4 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { InstallState } from "./InstallState";
+
+export interface InstallProgress { current_downloaded: bigint, total_size: bigint, state: InstallState, } \ No newline at end of file
diff --git a/src-tauri/bindings/InstallState.ts b/src-tauri/bindings/InstallState.ts
new file mode 100644
index 00000000..21dbc0c7
--- /dev/null
+++ b/src-tauri/bindings/InstallState.ts
@@ -0,0 +1,3 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type InstallState = "DOWNLOADING" | "EXTRACTING" | "DONE"; \ No newline at end of file
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index a3553448..56bc590c 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -1,4 +1,4 @@
-use std::{env, fs, path::Path, time::Duration};
+use std::{cell::RefCell, env, fs, path::Path, time::Duration, time::Instant};
use anyhow::{anyhow, Context, Result};
@@ -52,6 +52,22 @@ pub struct NorthstarServer {
pub player_count: i32,
}
+#[derive(Serialize, Deserialize, Debug, Clone, TS)]
+#[ts(export)]
+pub enum InstallState {
+ DOWNLOADING,
+ EXTRACTING,
+ DONE,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, TS)]
+#[ts(export)]
+struct InstallProgress {
+ current_downloaded: u64,
+ total_size: u64,
+ state: InstallState,
+}
+
/// Check version number of a mod
pub fn check_mod_version_number(path_to_mod_folder: String) -> Result<String, anyhow::Error> {
// println!("{}", format!("{}/mod.json", path_to_mod_folder));
@@ -187,7 +203,11 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> {
///Install N* from the provided mod
///
///Checks cache, else downloads the latest version
-async fn do_install(nmod: &thermite::model::ModVersion, game_path: &std::path::Path) -> Result<()> {
+async fn do_install(
+ window: tauri::Window,
+ nmod: &thermite::model::ModVersion,
+ game_path: &std::path::Path,
+) -> Result<()> {
let filename = format!("northstar-{}.zip", nmod.version);
let download_directory = format!("{}/___flightcore-temp-download-dir/", game_path.display());
@@ -196,7 +216,43 @@ async fn do_install(nmod: &thermite::model::ModVersion, game_path: &std::path::P
let download_path = format!("{}/{}", download_directory, filename);
log::info!("Download path: {download_path}");
- let nfile = thermite::core::manage::download_file(&nmod.url, download_path).unwrap();
+ let last_emit = RefCell::new(Instant::now()); // Keep track of the last time a signal was emitted
+ let nfile = thermite::core::manage::download_file_with_progress(
+ &nmod.url,
+ download_path,
+ |delta, current, total| {
+ if delta != 0 {
+ // Only emit a signal once every 100ms
+ // This way we don't bombard the frontend with events on fast download speeds
+ let time_since_last_emit = Instant::now().duration_since(*last_emit.borrow());
+ if time_since_last_emit >= Duration::from_millis(100) {
+ window
+ .emit(
+ "northstar-install-download-progress",
+ InstallProgress {
+ current_downloaded: current,
+ total_size: total,
+ state: InstallState::DOWNLOADING,
+ },
+ )
+ .unwrap();
+ *last_emit.borrow_mut() = Instant::now();
+ }
+ }
+ },
+ )
+ .unwrap();
+
+ window
+ .emit(
+ "northstar-install-download-progress",
+ InstallProgress {
+ current_downloaded: 0,
+ total_size: 0,
+ state: InstallState::EXTRACTING,
+ },
+ )
+ .unwrap();
log::info!("Extracting Northstar...");
extract(nfile, game_path)?;
@@ -206,11 +262,22 @@ async fn do_install(nmod: &thermite::model::ModVersion, game_path: &std::path::P
std::fs::remove_dir_all(download_directory).unwrap();
log::info!("Done installing Northstar!");
+ window
+ .emit(
+ "northstar-install-download-progress",
+ InstallProgress {
+ current_downloaded: 0,
+ total_size: 0,
+ state: InstallState::DONE,
+ },
+ )
+ .unwrap();
Ok(())
}
pub async fn install_northstar(
+ window: tauri::Window,
game_path: &str,
northstar_package_name: Option<String>,
) -> Result<String, String> {
@@ -235,6 +302,7 @@ pub async fn install_northstar(
log::info!("Install path \"{}\"", game_path);
match do_install(
+ window,
nmod.versions.get(&nmod.latest).unwrap(),
std::path::Path::new(game_path),
)
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 16b951ec..76e8833e 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -284,11 +284,12 @@ async fn get_host_os_caller() -> String {
#[tauri::command]
/// Installs Northstar to the given path
async fn install_northstar_caller(
+ window: tauri::Window,
game_path: String,
northstar_package_name: Option<String>,
) -> Result<bool, String> {
log::info!("Running");
- match install_northstar(&game_path, northstar_package_name).await {
+ match install_northstar(window, &game_path, northstar_package_name).await {
Ok(_) => Ok(true),
Err(err) => {
log::error!("{}", err);
@@ -300,13 +301,14 @@ async fn install_northstar_caller(
#[tauri::command]
/// Update Northstar install in the given path
async fn update_northstar_caller(
+ window: tauri::Window,
game_path: String,
northstar_package_name: Option<String>,
) -> Result<bool, String> {
log::info!("Updating Northstar");
// Simply re-run install with up-to-date version for upate
- match install_northstar(&game_path, northstar_package_name).await {
+ match install_northstar(window, &game_path, northstar_package_name).await {
Ok(_) => Ok(true),
Err(err) => {
log::error!("{}", err);
diff --git a/src-vue/src/components/InstallProgressBar.vue b/src-vue/src/components/InstallProgressBar.vue
new file mode 100644
index 00000000..d0c2047c
--- /dev/null
+++ b/src-vue/src/components/InstallProgressBar.vue
@@ -0,0 +1,102 @@
+<script lang="ts">
+import { defineComponent } from 'vue';
+import { appWindow } from '@tauri-apps/api/window';
+import { InstallProgress } from '../../../src-tauri/bindings/InstallProgress';
+
+export default defineComponent({
+ name: 'InstallProgressBar',
+ computed: {
+ progressBarStyle(): string {
+ return !this.install_or_update ? 'hide-progress' : '';
+ }
+ },
+ data() {
+ return {
+ percentage: 0,
+ color: '#409EFF',
+ install_or_update: false,
+ status: "unknown",
+ current_downloaded: -1,
+ total_size: -1,
+ };
+ },
+ methods: {
+ formatBytes(bytes: number, decimals = 2) {
+ if (bytes === 0) return '0 Bytes';
+ const k = 1000;
+ const dm = decimals < 0 ? 0 : decimals;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+ },
+ formatText() {
+ if (this.status == "DOWNLOADING") {
+ const current_downloaded_string = this.formatBytes(this.current_downloaded);
+ const total_size_string = this.formatBytes(this.total_size);
+ const status = this.$t("generic.downloading");
+ return `${status}: ${current_downloaded_string}/${total_size_string}`;
+ }
+ if (this.status == "EXTRACTING") {
+ return this.$t("generic.extracting");
+ }
+ return "Inactive"; // Needed to keep same size format when progress bar is hidden
+ }
+ },
+ mounted() {
+ appWindow.listen<InstallProgress>(
+ 'northstar-install-download-progress',
+ ({ event, payload }) => {
+ this.install_or_update = true;
+ let progress = payload;
+ this.status = progress.state;
+ if (progress.state == "DOWNLOADING") {
+ this.percentage = ((Number(progress.current_downloaded) / Number(progress.total_size)) * 100);
+ this.color = '#409EFF';
+ this.current_downloaded = Number(progress.current_downloaded);
+ this.total_size = Number(progress.total_size);
+ }
+ if (progress.state == "EXTRACTING") {
+ this.percentage = 100;
+ this.color = '#67C23A';
+ }
+ if (progress.state == "DONE") {
+ // Clear state again
+ this.install_or_update = false
+ }
+ }
+ );
+ }
+});
+</script>
+
+<template>
+ <el-progress
+ :class="progressBarStyle"
+ :format="formatText"
+ :percentage="percentage"
+ :color="color"
+ :indeterminate="status === 'EXTRACTING'"
+ :duration="1"
+ >
+ </el-progress>
+</template>
+
+<style scoped>
+.el-progress {
+ margin-top: 10px;
+}
+
+/* Set progress bar width */
+.el-progress:deep(.el-progress-bar) {
+ width: 200px;
+ flex-grow: initial;
+}
+
+.el-progress:deep(.el-progress__text) {
+ line-height: 1.2;
+}
+
+.hide-progress {
+ opacity: 0;
+}
+</style>
diff --git a/src-vue/src/components/PlayButton.vue b/src-vue/src/components/PlayButton.vue
index 3efcc9f5..208b4703 100644
--- a/src-vue/src/components/PlayButton.vue
+++ b/src-vue/src/components/PlayButton.vue
@@ -83,10 +83,10 @@ export default defineComponent({
return this.showReleaseSwitch
? 'border-radius: 2px 0 0 2px;'
: 'border-radius: 2px';
- }
+ },
},
methods: {
- launchGame() {
+ async launchGame() {
this.$store.commit('launchGame');
}
}
@@ -94,30 +94,31 @@ export default defineComponent({
</script>
<template>
- <el-button :disabled="northstarIsRunning"
- type="primary" size="large" @click="launchGame"
- class="fc_launch__button" :style="buttonRadiusStyle">
- {{ playButtonLabel }}
- </el-button>
- <el-select v-if="showReleaseSwitch" :disabled="northstarIsRunning"
- v-model="currentCanal" placeholder="Select">
- <el-option-group
- v-for="group in selectOptions"
- :key="group.label"
- :label="group.label"
- >
- <el-option
- v-for="item in group.options"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-option-group>
- </el-select>
+ <nav>
+ <el-button :disabled="northstarIsRunning"
+ type="primary" size="large" @click="launchGame"
+ class="fc_launch__button" :style="buttonRadiusStyle">
+ {{ playButtonLabel }}
+ </el-button>
+ <el-select v-if="showReleaseSwitch" :disabled="northstarIsRunning"
+ v-model="currentCanal" placeholder="Select">
+ <el-option-group
+ v-for="group in selectOptions"
+ :key="group.label"
+ :label="group.label"
+ >
+ <el-option
+ v-for="item in group.options"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-option-group>
+ </el-select>
+ </nav>
</template>
<style scoped>
-
button {
text-transform: uppercase;
padding: 30px;
@@ -130,7 +131,6 @@ button {
}
/* Release canal selector */
-
.el-select {
width: 0;
margin-right: 50px;
diff --git a/src-vue/src/i18n/lang/en.json b/src-vue/src/i18n/lang/en.json
index 5e1974cc..3553d2a5 100644
--- a/src-vue/src/i18n/lang/en.json
+++ b/src-vue/src/i18n/lang/en.json
@@ -12,7 +12,9 @@
"no": "No",
"error": "Error",
"cancel": "Cancel",
- "informationShort": "Info"
+ "informationShort": "Info",
+ "downloading": "Downloading",
+ "extracting": "Extracting"
},
"play": {
diff --git a/src-vue/src/views/PlayView.vue b/src-vue/src/views/PlayView.vue
index 2f3562ef..76f4f328 100644
--- a/src-vue/src/views/PlayView.vue
+++ b/src-vue/src/views/PlayView.vue
@@ -2,10 +2,12 @@
import { Tabs } from "../utils/Tabs";
import PlayButton from '../components/PlayButton.vue';
import { defineComponent } from "vue";
+import InstallProgressBar from "../components/InstallProgressBar.vue";
export default defineComponent({
components: {
- PlayButton
+ PlayButton,
+ InstallProgressBar
},
computed: {
northstarIsRunning(): boolean {
@@ -45,7 +47,9 @@ export default defineComponent({
{{ $t('play.unable_to_load_playercount') }}
</div>
</div>
- <div>
+
+ <!-- Align play button and services state container -->
+ <div style="display: flex">
<PlayButton />
<div v-if="$store.state.developer_mode" id="fc_services__status">
<div>
@@ -58,12 +62,13 @@ export default defineComponent({
</div>
</div>
</div>
+ <InstallProgressBar />
</div>
</template>
<style scoped>
.fc_launch__container {
- margin: 50px;
+ margin: 50px 50px 30px 50px;
position: fixed;
bottom: 0;
}
@@ -101,13 +106,9 @@ export default defineComponent({
border-color: var(--el-color-primary);
}
-
#fc_services__status {
- display: inline-block;
- position: fixed;
- padding: 10px 20px;
color: #e8edef;
- bottom: 43px;
+ align-self: end;
}
.fc_version__line {
diff --git a/src-vue/src/views/RepairView.vue b/src-vue/src/views/RepairView.vue
index 9de9a7f3..3d449d70 100644
--- a/src-vue/src/views/RepairView.vue
+++ b/src-vue/src/views/RepairView.vue
@@ -33,6 +33,7 @@
<script lang="ts">
import { defineComponent } from "vue";
import { GameInstall } from "../utils/GameInstall";
+import { InstallProgress } from "../../../src-tauri/bindings/InstallProgress";
import { invoke } from "@tauri-apps/api";
import { ReleaseCanal } from "../utils/ReleaseCanal";
import { Store } from 'tauri-plugin-store-api';
@@ -76,6 +77,16 @@ export default defineComponent({
);
let install_northstar_result = invoke("install_northstar_caller", { gamePath: game_install.game_path, northstarPackageName: ReleaseCanal.RELEASE });
+
+ appWindow.listen<InstallProgress>(
+ 'northstar-install-download-progress',
+ ({ event, payload }) => {
+ let typed_payload = payload;
+ console.log("current_downloaded:", typed_payload.current_downloaded);
+ console.log("total_size: ", typed_payload.total_size);
+ console.log("state: ", typed_payload.state);
+ }
+ );
await install_northstar_result
.then((message) => {
// Send notification