forked from didirus/AstralRinth
feat: split wrapper command on linux (#4427)
* feat: split wrapper command on linux * feat: use code from #3900 * feat: also use shlex on Windows * feat: add a version number to global settings * feat(app): add settings v2, where wrapper command are split
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -9463,6 +9463,7 @@ dependencies = [
|
||||
"serde_with",
|
||||
"sha1_smol",
|
||||
"sha2",
|
||||
"shlex",
|
||||
"sqlx",
|
||||
"sysinfo",
|
||||
"tauri",
|
||||
|
||||
@@ -158,6 +158,7 @@ serde-xml-rs = "0.8.1" # Also an XML (de)serializer, consider dropping yaserde
|
||||
sha1 = "0.10.6"
|
||||
sha1_smol = { version = "1.0.1", features = ["std"] }
|
||||
sha2 = "0.10.9"
|
||||
shlex = "1.3.0"
|
||||
spdx = "0.12.0"
|
||||
sqlx = { version = "0.8.6", default-features = false }
|
||||
sysinfo = { version = "0.37.2", default-features = false }
|
||||
|
||||
@@ -67,6 +67,8 @@ export type AppSettings = {
|
||||
skipped_update: string | null
|
||||
pending_update_toast_for_version: string | null
|
||||
auto_download_updates: boolean | null
|
||||
|
||||
version: number
|
||||
}
|
||||
|
||||
// Get full settings object
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, default_page, collapsed_navigation, hide_nametag_skins_page, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry, personalized_ads,\n onboarded,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar,\n skipped_update, pending_update_toast_for_version, auto_download_updates\n FROM settings\n ",
|
||||
"query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, default_page, collapsed_navigation, hide_nametag_skins_page, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry, personalized_ads,\n onboarded,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar,\n skipped_update, pending_update_toast_for_version, auto_download_updates,\n version\n FROM settings\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -157,6 +157,11 @@
|
||||
"name": "auto_download_updates",
|
||||
"ordinal": 30,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"ordinal": 31,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -193,8 +198,9 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7dc83d7ffa3d583fc5ffaf13811a8dab4d0b9ded6200f827b9de7ac32e5318d5"
|
||||
"hash": "07ea3a644644de61c4ed7c30ee711d29fd49f10534230b1b03097275a30cb50f"
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
{
|
||||
"name": "display_claims!: serde_json::Value",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
"type_info": "Null"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n default_page = $4,\n collapsed_navigation = $5,\n advanced_rendering = $6,\n native_decorations = $7,\n\n discord_rpc = $8,\n developer_mode = $9,\n telemetry = $10,\n personalized_ads = $11,\n\n onboarded = $12,\n\n extra_launch_args = jsonb($13),\n custom_env_vars = jsonb($14),\n mc_memory_max = $15,\n mc_force_fullscreen = $16,\n mc_game_resolution_x = $17,\n mc_game_resolution_y = $18,\n hide_on_process_start = $19,\n\n hook_pre_launch = $20,\n hook_wrapper = $21,\n hook_post_exit = $22,\n\n custom_dir = $23,\n prev_custom_dir = $24,\n migrated = $25,\n\n toggle_sidebar = $26,\n feature_flags = $27,\n hide_nametag_skins_page = $28,\n\n skipped_update = $29,\n pending_update_toast_for_version = $30,\n auto_download_updates = $31\n ",
|
||||
"query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n default_page = $4,\n collapsed_navigation = $5,\n advanced_rendering = $6,\n native_decorations = $7,\n\n discord_rpc = $8,\n developer_mode = $9,\n telemetry = $10,\n personalized_ads = $11,\n\n onboarded = $12,\n\n extra_launch_args = jsonb($13),\n custom_env_vars = jsonb($14),\n mc_memory_max = $15,\n mc_force_fullscreen = $16,\n mc_game_resolution_x = $17,\n mc_game_resolution_y = $18,\n hide_on_process_start = $19,\n\n hook_pre_launch = $20,\n hook_wrapper = $21,\n hook_post_exit = $22,\n\n custom_dir = $23,\n prev_custom_dir = $24,\n migrated = $25,\n\n toggle_sidebar = $26,\n feature_flags = $27,\n hide_nametag_skins_page = $28,\n\n skipped_update = $29,\n pending_update_toast_for_version = $30,\n auto_download_updates = $31,\n\n version = $32\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 31
|
||||
"Right": 32
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "eb95fac3043d0ffd10caef69cc469474cc5c0d36cc0698c4cc0852da81fed158"
|
||||
"hash": "a40e60da6dd1312d4a1ed52fa8fd2394e7ad21de1cb44cf8b93c4b1459cdc716"
|
||||
}
|
||||
@@ -73,6 +73,7 @@ serde_json = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
sha1_smol = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
shlex = { workspace = true }
|
||||
sqlx = { workspace = true, features = [
|
||||
"json",
|
||||
"macros",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE settings ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
|
||||
@@ -666,7 +666,14 @@ async fn run_credentials(
|
||||
.filter(|hook_command| !hook_command.is_empty());
|
||||
if let Some(hook) = pre_launch_hooks {
|
||||
// TODO: hook parameters
|
||||
let mut cmd = hook.split(' ');
|
||||
let mut cmd = shlex::split(hook)
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid pre-launch command: {hook}",
|
||||
))
|
||||
})?
|
||||
.into_iter();
|
||||
|
||||
if let Some(command) = cmd.next() {
|
||||
let full_path = get_full_path(&profile.path).await?;
|
||||
let result = Command::new(command)
|
||||
|
||||
@@ -567,7 +567,19 @@ pub async fn launch_minecraft(
|
||||
let args = version_info.arguments.clone().unwrap_or_default();
|
||||
let mut command = match wrapper {
|
||||
Some(hook) => {
|
||||
let mut command = Command::new(hook);
|
||||
let mut cmd = shlex::split(hook)
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid wrapper command: {hook}",
|
||||
))
|
||||
})?
|
||||
.into_iter();
|
||||
let mut command = Command::new(cmd.next().ok_or(
|
||||
crate::ErrorKind::LauncherError(
|
||||
"Empty wrapper command".to_owned(),
|
||||
),
|
||||
)?);
|
||||
command.args(cmd);
|
||||
command.arg(&java_version.path);
|
||||
command
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ impl State {
|
||||
let res = tokio::try_join!(
|
||||
state.discord_rpc.clear_to_default(true),
|
||||
Profile::refresh_all(),
|
||||
Settings::migrate(&state.pool),
|
||||
ModrinthCredentials::refresh_all(),
|
||||
);
|
||||
|
||||
|
||||
@@ -743,7 +743,14 @@ impl Process {
|
||||
// We do not wait on the post exist command to finish running! We let it spawn + run on its own.
|
||||
// This behaviour may be changed in the future
|
||||
if let Some(hook) = post_exit_command {
|
||||
let mut cmd = hook.split(' ');
|
||||
let mut cmd = shlex::split(&hook)
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid post-exit command: {hook}",
|
||||
))
|
||||
})?
|
||||
.into_iter();
|
||||
|
||||
if let Some(command) = cmd.next() {
|
||||
let mut command = Command::new(command);
|
||||
command.args(cmd).current_dir(
|
||||
|
||||
@@ -103,10 +103,11 @@ impl ProfileInstallStage {
|
||||
pub enum LauncherFeatureVersion {
|
||||
None,
|
||||
MigratedServerLastPlayTime,
|
||||
MigratedLaunchHooks,
|
||||
}
|
||||
|
||||
impl LauncherFeatureVersion {
|
||||
pub const MOST_RECENT: Self = Self::MigratedServerLastPlayTime;
|
||||
pub const MOST_RECENT: Self = Self::MigratedLaunchHooks;
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match *self {
|
||||
@@ -114,6 +115,7 @@ impl LauncherFeatureVersion {
|
||||
Self::MigratedServerLastPlayTime => {
|
||||
"migrated_server_last_play_time"
|
||||
}
|
||||
Self::MigratedLaunchHooks => "migrated_launch_hooks",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +125,7 @@ impl LauncherFeatureVersion {
|
||||
"migrated_server_last_play_time" => {
|
||||
Self::MigratedServerLastPlayTime
|
||||
}
|
||||
"migrated_launch_hooks" => Self::MigratedLaunchHooks,
|
||||
_ => Self::None,
|
||||
}
|
||||
}
|
||||
@@ -785,6 +788,30 @@ impl Profile {
|
||||
self.launcher_feature_version =
|
||||
LauncherFeatureVersion::MigratedServerLastPlayTime;
|
||||
}
|
||||
LauncherFeatureVersion::MigratedServerLastPlayTime => {
|
||||
let quoter = shlex::Quoter::new().allow_nul(true);
|
||||
|
||||
// Previously split by spaces
|
||||
if let Some(pre_launch) = self.hooks.pre_launch.as_ref() {
|
||||
self.hooks.pre_launch =
|
||||
Some(quoter.join(pre_launch.split(' ')).unwrap())
|
||||
}
|
||||
|
||||
// Previously treated as complete path to command
|
||||
if let Some(wrapper) = self.hooks.wrapper.as_ref() {
|
||||
self.hooks.wrapper =
|
||||
Some(quoter.quote(wrapper).unwrap().to_string())
|
||||
}
|
||||
|
||||
// Previously split by spaces
|
||||
if let Some(post_exit) = self.hooks.post_exit.as_ref() {
|
||||
self.hooks.post_exit =
|
||||
Some(quoter.join(post_exit.split(' ')).unwrap())
|
||||
}
|
||||
|
||||
self.launcher_feature_version =
|
||||
LauncherFeatureVersion::MigratedLaunchHooks;
|
||||
}
|
||||
LauncherFeatureVersion::MOST_RECENT => unreachable!(
|
||||
"LauncherFeatureVersion::MOST_RECENT was not updated"
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Theseus settings file
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Types
|
||||
@@ -42,6 +43,8 @@ pub struct Settings {
|
||||
pub skipped_update: Option<String>,
|
||||
pub pending_update_toast_for_version: Option<String>,
|
||||
pub auto_download_updates: Option<bool>,
|
||||
|
||||
pub version: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, Hash, PartialEq)]
|
||||
@@ -54,6 +57,8 @@ pub enum FeatureFlag {
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
const CURRENT_VERSION: usize = 2;
|
||||
|
||||
pub async fn get(
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<Self> {
|
||||
@@ -68,7 +73,8 @@ impl Settings {
|
||||
mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,
|
||||
hook_pre_launch, hook_wrapper, hook_post_exit,
|
||||
custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar,
|
||||
skipped_update, pending_update_toast_for_version, auto_download_updates
|
||||
skipped_update, pending_update_toast_for_version, auto_download_updates,
|
||||
version
|
||||
FROM settings
|
||||
"
|
||||
)
|
||||
@@ -126,6 +132,7 @@ impl Settings {
|
||||
pending_update_toast_for_version: res
|
||||
.pending_update_toast_for_version,
|
||||
auto_download_updates: res.auto_download_updates.map(|x| x == 1),
|
||||
version: res.version as usize,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -140,6 +147,7 @@ impl Settings {
|
||||
let extra_launch_args = serde_json::to_string(&self.extra_launch_args)?;
|
||||
let custom_env_vars = serde_json::to_string(&self.custom_env_vars)?;
|
||||
let feature_flags = serde_json::to_string(&self.feature_flags)?;
|
||||
let version = self.version as i64;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -183,7 +191,9 @@ impl Settings {
|
||||
|
||||
skipped_update = $29,
|
||||
pending_update_toast_for_version = $30,
|
||||
auto_download_updates = $31
|
||||
auto_download_updates = $31,
|
||||
|
||||
version = $32
|
||||
",
|
||||
max_concurrent_writes,
|
||||
max_concurrent_downloads,
|
||||
@@ -216,12 +226,75 @@ impl Settings {
|
||||
self.skipped_update,
|
||||
self.pending_update_toast_for_version,
|
||||
self.auto_download_updates,
|
||||
version,
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate(exec: &Pool<Sqlite>) -> crate::Result<()> {
|
||||
let mut settings = Self::get(exec).await?;
|
||||
|
||||
if settings.version < Settings::CURRENT_VERSION {
|
||||
tracing::info!(
|
||||
"Migrating settings version {} to {:?}",
|
||||
settings.version,
|
||||
Settings::CURRENT_VERSION
|
||||
);
|
||||
}
|
||||
while settings.version < Settings::CURRENT_VERSION {
|
||||
if let Err(err) = settings.perform_migration() {
|
||||
tracing::error!(
|
||||
"Failed to migrate settings from version {}: {}",
|
||||
settings.version,
|
||||
err
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
settings.update(exec).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn perform_migration(&mut self) -> crate::Result<()> {
|
||||
match self.version {
|
||||
1 => {
|
||||
let quoter = shlex::Quoter::new().allow_nul(true);
|
||||
|
||||
// Previously split by spaces
|
||||
if let Some(pre_launch) = self.hooks.pre_launch.as_ref() {
|
||||
self.hooks.pre_launch =
|
||||
Some(quoter.join(pre_launch.split(' ')).unwrap())
|
||||
}
|
||||
|
||||
// Previously treated as complete path to command
|
||||
if let Some(wrapper) = self.hooks.wrapper.as_ref() {
|
||||
self.hooks.wrapper =
|
||||
Some(quoter.quote(wrapper).unwrap().to_string())
|
||||
}
|
||||
|
||||
// Previously split by spaces
|
||||
if let Some(post_exit) = self.hooks.post_exit.as_ref() {
|
||||
self.hooks.post_exit =
|
||||
Some(quoter.join(post_exit.split(' ')).unwrap())
|
||||
}
|
||||
|
||||
self.version = 2;
|
||||
}
|
||||
version => {
|
||||
return Err(crate::ErrorKind::OtherError(format!(
|
||||
"Invalid settings version: {version}"
|
||||
))
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Theseus theme
|
||||
|
||||
Reference in New Issue
Block a user