feat: less bad launches
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 974 B |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 903 B |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 14 KiB |
BIN
rust/icons/slow.png
Normal file
After Width: | Height: | Size: 23 KiB |
@ -9,18 +9,21 @@ use crate::profile::Profile;
|
|||||||
use crate::appdata::AppData;
|
use crate::appdata::AppData;
|
||||||
use crate::{liner, start};
|
use crate::{liner, start};
|
||||||
|
|
||||||
use tauri::State;
|
use tauri::{AppHandle, Manager, State};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn startline(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
|
pub async fn startline(app: AppHandle) -> Result<(), String> {
|
||||||
log::debug!("invoke: startline");
|
log::debug!("invoke: startline");
|
||||||
|
|
||||||
|
let app_copy = app.clone();
|
||||||
|
let state = app.state::<Mutex<AppData>>();
|
||||||
let appd = state.lock().await;
|
let appd = state.lock().await;
|
||||||
|
|
||||||
if let Some(p) = &appd.profile {
|
if let Some(p) = &appd.profile {
|
||||||
// TODO if p.needsUpdate
|
// TODO if p.needsUpdate
|
||||||
liner::line_up(p).await.expect("Line-up failed");
|
liner::line_up(p).await.expect("Line-up failed");
|
||||||
start::start(p).map_err(|e| { log::error!("Error launching: {}", e.to_string()); e.to_string() }).map(|_| ())
|
start::start(p, app_copy)
|
||||||
|
.map_err(|e| { log::error!("Error launching: {}", e.to_string()); e.to_string() })
|
||||||
//Ok(())
|
//Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("No profile".to_owned())
|
Err("No profile".to_owned())
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -6,7 +6,7 @@ use tauri::{AppHandle, Emitter};
|
|||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
use crate::model::rainy;
|
use crate::model::rainy;
|
||||||
use crate::pkg::{Package, PkgKey};
|
use crate::pkg::{Package, PkgKey, Remote};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::download_handler::DownloadHandler;
|
use crate::download_handler::DownloadHandler;
|
||||||
|
|
||||||
@ -134,12 +134,14 @@ impl PackageStore {
|
|||||||
pkg: key.to_owned()
|
pkg: key.to_owned()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let rmt = pkg.rmt.as_ref() //clone()
|
let rmt = pkg.rmt.as_ref()
|
||||||
.ok_or_else(|| anyhow!("Attempted to install a pkg without remote data"))?;
|
.ok_or_else(|| anyhow!("Attempted to install a pkg without remote data"))?;
|
||||||
|
|
||||||
if install_deps {
|
if install_deps {
|
||||||
for dep in &rmt.dependencies {
|
let mut set = HashSet::new();
|
||||||
Box::pin(self.install_package(&dep, false, true)).await?;
|
self.resolve_deps(rmt.clone(), &mut set)?;
|
||||||
|
for dep in set {
|
||||||
|
Box::pin(self.install_package(&dep, false, false)).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,4 +246,18 @@ impl PackageStore {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_deps(&self, rmt: Remote, set: &mut HashSet<PkgKey>) -> Result<()> {
|
||||||
|
for d in rmt.dependencies {
|
||||||
|
set.insert(d.clone());
|
||||||
|
let subrmt = self.store.get(&d)
|
||||||
|
.ok_or_else(|| anyhow!("Attempted to delete a nonexistent pkg"))?
|
||||||
|
.rmt
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| anyhow!("Attempted to resolve deps without fetching"))?;
|
||||||
|
self.resolve_deps(subrmt, set)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,32 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
use tauri::{AppHandle, Emitter};
|
||||||
use crate::profile::Profile;
|
use crate::profile::Profile;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn start(p: &Profile) -> Result<()> {
|
pub fn start(p: &Profile, app: AppHandle) -> Result<()> {
|
||||||
Command::new(p.wine_runtime.as_ref().unwrap())
|
let p = p.clone();
|
||||||
.env(
|
tauri::async_runtime::spawn(async move {
|
||||||
"SEGATOOLS_CONFIG_PATH",
|
let rv = Command::new(p.wine_runtime.as_ref().unwrap())
|
||||||
util::profile_dir(&p).join("segatools.ini"),
|
.env(
|
||||||
)
|
"SEGATOOLS_CONFIG_PATH",
|
||||||
.env("WINEPREFIX", p.wine_prefix.as_ref().unwrap())
|
util::profile_dir(&p).join("segatools.ini"),
|
||||||
.arg(p.exe_dir.join("start.bat"))
|
)
|
||||||
.spawn()?;
|
.env("WINEPREFIX", p.wine_prefix.as_ref().unwrap())
|
||||||
|
.arg(p.exe_dir.join("start.bat"))
|
||||||
|
.spawn();
|
||||||
|
match rv {
|
||||||
|
Ok(mut child) => {
|
||||||
|
_ = child.wait().await;
|
||||||
|
log::debug!("Fin");
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Fail: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = app.emit("launch-end", "");
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -72,6 +86,8 @@ pub fn start(p: &Profile) -> Result<()> {
|
|||||||
set.join_next().await.expect("No spawn").expect("No result");
|
set.join_next().await.expect("No spawn").expect("No result");
|
||||||
|
|
||||||
log::debug!("Fin");
|
log::debug!("Fin");
|
||||||
|
|
||||||
|
app.emit("launch-end", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -42,12 +42,6 @@
|
|||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
"icon": [
|
"icon": ["icons/slow.png"]
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import TabPanel from 'primevue/tabpanel';
|
|||||||
import TabPanels from 'primevue/tabpanels';
|
import TabPanels from 'primevue/tabpanels';
|
||||||
import Tabs from 'primevue/tabs';
|
import Tabs from 'primevue/tabs';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { onOpenUrl } from '@tauri-apps/plugin-deep-link';
|
import { onOpenUrl } from '@tauri-apps/plugin-deep-link';
|
||||||
import { open } from '@tauri-apps/plugin-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
import ModList from './ModList.vue';
|
import ModList from './ModList.vue';
|
||||||
@ -19,6 +20,7 @@ const store = usePkgStore();
|
|||||||
store.setupListeners();
|
store.setupListeners();
|
||||||
|
|
||||||
const currentTab = ref('3');
|
const currentTab = ref('3');
|
||||||
|
const startEnabled = ref(true);
|
||||||
|
|
||||||
const loadProfile = async () => {
|
const loadProfile = async () => {
|
||||||
await store.reloadProfile();
|
await store.reloadProfile();
|
||||||
@ -49,9 +51,8 @@ const loadProfile = async () => {
|
|||||||
const isProfileDisabled = computed(() => store.profile === null);
|
const isProfileDisabled = computed(() => store.profile === null);
|
||||||
|
|
||||||
const startline = async () => {
|
const startline = async () => {
|
||||||
|
startEnabled.value = false;
|
||||||
await invoke('startline');
|
await invoke('startline');
|
||||||
|
|
||||||
//startDisabled.value = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onOpenUrl((urls) => {
|
onOpenUrl((urls) => {
|
||||||
@ -61,6 +62,10 @@ onOpenUrl((urls) => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadProfile();
|
await loadProfile();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
listen('launch-end', () => {
|
||||||
|
startEnabled.value = true;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -82,7 +87,7 @@ onMounted(async () => {
|
|||||||
></Tab>
|
></Tab>
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
<Button
|
<Button
|
||||||
:disabled="false"
|
:disabled="!startEnabled"
|
||||||
icon="pi pi-play"
|
icon="pi pi-play"
|
||||||
label="START"
|
label="START"
|
||||||
aria-label="start"
|
aria-label="start"
|
||||||
|
@ -4,6 +4,7 @@ import ToggleSwitch from 'primevue/toggleswitch';
|
|||||||
import { open } from '@tauri-apps/plugin-shell';
|
import { open } from '@tauri-apps/plugin-shell';
|
||||||
import InstallButton from './InstallButton.vue';
|
import InstallButton from './InstallButton.vue';
|
||||||
import ModTitlecard from './ModTitlecard.vue';
|
import ModTitlecard from './ModTitlecard.vue';
|
||||||
|
import UpdateButton from './UpdateButton.vue';
|
||||||
import { usePkgStore } from '../stores';
|
import { usePkgStore } from '../stores';
|
||||||
import { Package } from '../types';
|
import { Package } from '../types';
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ const toggle = async (value: boolean) => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ModTitlecard showVersion :pkg="pkg" />
|
<ModTitlecard showVersion :pkg="pkg" />
|
||||||
|
<UpdateButton :pkg="pkg" />
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
class="scale-[1.33] shrink-0"
|
class="scale-[1.33] shrink-0"
|
||||||
inputId="switch"
|
inputId="switch"
|
||||||
@ -28,7 +30,7 @@ const toggle = async (value: boolean) => {
|
|||||||
:modelValue="store.isEnabled(pkg)"
|
:modelValue="store.isEnabled(pkg)"
|
||||||
v-on:value-change="toggle"
|
v-on:value-change="toggle"
|
||||||
/>
|
/>
|
||||||
<InstallButton />
|
<InstallButton :pkg="pkg" />
|
||||||
<Button
|
<Button
|
||||||
rounded
|
rounded
|
||||||
icon="pi pi-folder"
|
icon="pi pi-folder"
|
||||||
|
@ -11,7 +11,12 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-for="p in pkgs.allRemote" class="flex flex-row">
|
<div
|
||||||
|
v-for="p in pkgs.allRemote.sort((p1, p2) =>
|
||||||
|
p1.name.localeCompare(p2.name)
|
||||||
|
)"
|
||||||
|
class="flex flex-row"
|
||||||
|
>
|
||||||
<ModStoreEntry :pkg="p" />
|
<ModStoreEntry :pkg="p" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
import { Package } from '../types';
|
import { Package } from '../types';
|
||||||
|
import { needsUpdate } from '../util';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pkg: Object as () => Package,
|
pkg: Object as () => Package,
|
||||||
@ -39,11 +40,19 @@ const iconSrc = () => {
|
|||||||
>
|
>
|
||||||
by {{ pkg.namespace }}
|
by {{ pkg.namespace }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span class="m-2">
|
||||||
v-if="showVersion && pkg?.loc?.version"
|
<span
|
||||||
class="text-sm opacity-75 m-2"
|
v-if="showVersion && pkg?.loc?.version"
|
||||||
>
|
class="text-sm opacity-75"
|
||||||
{{ pkg?.loc?.version ?? '?.?.?' }}
|
>
|
||||||
|
{{ pkg?.loc?.version ?? '?.?.?' }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="showVersion && needsUpdate(pkg)"
|
||||||
|
class="text-sm opacity-75"
|
||||||
|
>
|
||||||
|
-> {{ pkg?.rmt?.version ?? '?.?.?' }}</span
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm opacity-75">
|
<div class="text-sm opacity-75">
|
||||||
|
41
src/components/UpdateButton.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Button from 'primevue/button';
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { Package } from '../types';
|
||||||
|
import { needsUpdate, pkgKey } from '../util';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
pkg: Object as () => Package,
|
||||||
|
});
|
||||||
|
|
||||||
|
const install = async () => {
|
||||||
|
if (props.pkg === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await invoke('install_package', { key: pkgKey(props.pkg) });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
if (props.pkg !== undefined) {
|
||||||
|
props.pkg.js.busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (rv === 'Deferred') { /* download progress */ }
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
v-if="needsUpdate(pkg) && !pkg?.js.busy"
|
||||||
|
rounded
|
||||||
|
icon="pi pi-sync"
|
||||||
|
severity="success"
|
||||||
|
aria-label="remove"
|
||||||
|
size="small"
|
||||||
|
class="self-center mr-4"
|
||||||
|
style="width: 2rem; height: 2rem"
|
||||||
|
v-on:click="install()"
|
||||||
|
/>
|
||||||
|
</template>
|
20
src/util.ts
@ -20,3 +20,23 @@ export const changePrimaryColor = (game: 'Ongeki' | 'Chunithm') => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const pkgKey = (pkg: Package) => `${pkg.namespace}-${pkg.name}`;
|
export const pkgKey = (pkg: Package) => `${pkg.namespace}-${pkg.name}`;
|
||||||
|
|
||||||
|
export const needsUpdate = (pkg: Package | undefined) => {
|
||||||
|
const loc = pkg?.loc?.version;
|
||||||
|
const rmt = pkg?.rmt?.version;
|
||||||
|
|
||||||
|
if (loc === undefined || rmt === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [l1, l2, l3] = loc.split('.');
|
||||||
|
const [r1, r2, r3] = rmt.split('.');
|
||||||
|
|
||||||
|
if (l1 === r1) {
|
||||||
|
if (l2 === r2) {
|
||||||
|
return l3 < r3;
|
||||||
|
}
|
||||||
|
return l2 < r2;
|
||||||
|
}
|
||||||
|
return l1 < r1;
|
||||||
|
};
|
||||||
|