Files
modrinth/apps/app/src/api/instance.rs
T
Calum H.andtdgao 7c67cca7a7 feat(instance-sync): screenshots sync (#7218)
* pnpm prepr

* feat(instance-sync): screenshots sync

* fix: prepr + fmt

* fix: qa

* feat: screenshit editor

* feat: screenshot* editor qa

* fix: qa

* fix: lint

* fix: aecsocket rev

* qa: a11y tab navigation focus is cut off

* qa: Change “Edited” badge to be bg-highlight-green

* qa: friends list input style wrong

* qa: toolbar width + labels

* qa: multiselect changes

* qa: anim changes

* qa: card hover colors

* qa: copy feedback

* qa: hide date

* qa: instance icon in overflow/contextmenu

* qa: spacing

* qa: group empty state text

* qa: drag preview badge

* qa: group deletion skip warning if no screenshots

* qa: redesign editor

* qa: remove screenshot parent/edited badge stuff

* qa: control font size consistency

* qa: viewer copy control

* qa: editor fixes

* qa: redirect on disable screenshot sync

* qa: margin police

* fix: crop

* fix: qa

* feat: initial start on basic instance file syncing (#7220)

* qa: final

* qa: final 2

* fix: lint + prepr

* fix: copy

* fix: screenshot editing outside of app causing ghost files in db + sync fail rollback impl

* chore: split up

* fix: fmt

---------

Co-authored-by: tdgao <mr.trumgao@gmail.com>
2026-08-27 16:49:46 +00:00

1325 lines
39 KiB
Rust

use crate::api::Result;
use dashmap::DashMap;
use path_util::SafeRelativeUtf8UnixPathBuf;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tauri::{AppHandle, Manager, Runtime};
use tauri_plugin_fs::FsExt;
use tauri_plugin_opener::OpenerExt;
use theseus::DownloadReason;
use theseus::data::{
AppliedContentSetPatch, ContentItem, Dependency,
EditInstance as CoreEditInstance, InstanceInstallCandidate,
InstanceInstallTarget, InstanceLaunchOverridesPatch,
InstanceLink as CoreInstanceLink, InstanceMetadata, LinkedModpackInfo,
SharedInstanceAttachment as CoreSharedInstanceAttachment,
SharedInstanceRole,
};
use theseus::instance::InstallProjectWithDependenciesRequest;
use theseus::instance::QuickPlayType;
use theseus::prelude::*;
use theseus::server_address::ServerAddress;
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
tauri::plugin::Builder::new("instance")
.invoke_handler(tauri::generate_handler![
instance_remove,
instance_get,
instance_get_many,
instance_list,
instance_list_groups,
instance_create_group,
instance_rename_group,
instance_delete_group,
instance_set_group_order,
instance_set_group_memberships,
instance_get_projects,
instance_get_installed_project_ids,
instance_get_install_candidates,
instance_content,
instance_get_content_items,
instance_refresh_content_updates,
instance_get_dependencies_as_content_items,
instance_get_linked_modpack_info,
instance_get_linked_modpack_content,
instance_get_optimal_jre_key,
instance_get_full_path,
instance_get_mod_full_path,
instance_list_screenshots,
instance_list_all_screenshots,
instance_list_synced_screenshots,
instance_save_edited_screenshot,
instance_list_screenshot_groups,
instance_create_screenshot_group,
instance_rename_screenshot_group,
instance_delete_screenshot_group,
instance_set_screenshot_group_memberships,
instance_import_screenshot_groups,
instance_delete_screenshots,
instance_export_screenshots,
instance_move_screenshots,
instance_open_screenshot,
instance_set_synced_option,
instance_get_synced_option_join_preview,
instance_get_synced_options_overview,
instance_get_global_synced_options,
instance_set_global_synced_option,
instance_get_command_history,
instance_set_command_history,
instance_open_synced_options_folder,
instance_list_synced_servers,
instance_update_synced_server,
instance_remove_synced_server,
instance_rebuild_synced_options,
instance_check_installed,
instance_update_all,
instance_update_project,
instance_add_project_from_version,
instance_install_project_with_dependencies,
instance_switch_project_version_with_dependencies,
instance_add_project_from_path,
instance_is_file_on_modrinth,
instance_toggle_disable_project,
instance_set_project_locked,
instance_remove_project,
instance_update_managed_modrinth_version,
instance_repair_managed_modrinth,
instance_run,
instance_kill,
instance_edit,
instance_edit_icon,
instance_edit_generated_icon,
instance_cache_generated_icon,
instance_get_recent_icon_configs,
instance_share_can_current_user_use,
instance_share_get_users,
instance_share_invite_users,
instance_share_create_invite_link,
instance_share_get_invites,
instance_share_revoke_invite,
instance_share_remove_users,
instance_share_get_publish_preview,
instance_share_publish,
instance_share_unlink,
instance_share_unpublish,
instance_export_mrpack,
instance_get_pack_export_candidates,
])
.build()
}
#[derive(Serialize, Debug, Clone)]
pub struct Instance {
pub id: String,
pub path: String,
pub install_stage: String,
pub launcher_feature_version: String,
pub name: String,
pub icon_path: Option<String>,
pub icon_config: Option<theseus::data::InstanceIconConfig>,
pub game_version: String,
pub protocol_version: Option<u32>,
pub loader: ModLoader,
pub loader_version: Option<String>,
pub group_ids: Vec<String>,
pub synced_options: InstanceSyncedOptions,
pub link: Option<InstanceLink>,
pub shared_instance: Option<SharedInstanceAttachment>,
pub quarantined: bool,
pub update_channel: ReleaseChannel,
pub created: chrono::DateTime<chrono::Utc>,
pub modified: chrono::DateTime<chrono::Utc>,
pub last_played: Option<chrono::DateTime<chrono::Utc>>,
pub submitted_time_played: u64,
pub recent_time_played: u64,
pub java_path: Option<String>,
pub extra_launch_args: Option<Vec<String>>,
pub custom_env_vars: Option<Vec<(String, String)>>,
pub memory: Option<MemorySettings>,
pub force_fullscreen: Option<bool>,
pub game_resolution: Option<WindowSize>,
pub hooks: Hooks,
}
#[derive(Serialize, Debug, Clone)]
pub struct InstanceScreenshot {
pub id: String,
pub instance_id: String,
pub instance_name: String,
pub file_name: String,
pub created_at: chrono::DateTime<chrono::Utc>,
pub modified_at: i64,
pub group_id: Option<String>,
pub path: PathBuf,
pub url: url::Url,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum InstanceLink {
ModrinthModpack {
project_id: String,
version_id: String,
},
ServerProject {
project_id: String,
},
ServerProjectModpack {
server_project_id: String,
content_project_id: Option<String>,
content_version_id: String,
project_id: Option<String>,
version_id: Option<String>,
},
ImportedModpack {
project_id: Option<String>,
version_id: Option<String>,
name: Option<String>,
version_number: Option<String>,
filename: Option<String>,
},
ModrinthHosting {
server_id: String,
instance_ids: Vec<String>,
active_instance_id: Option<String>,
},
SharedInstance {
modpack_project_id: Option<String>,
modpack_version_id: Option<String>,
},
}
#[derive(Serialize, Debug, Clone)]
pub struct SharedInstanceAttachment {
pub id: String,
pub role: SharedInstanceRole,
pub manager_id: Option<String>,
pub server_manager_name: Option<String>,
pub server_manager_icon_url: Option<String>,
pub linked_user_id: Option<String>,
pub status: String,
pub applied_version: Option<i32>,
pub latest_version: Option<i32>,
}
impl From<CoreSharedInstanceAttachment> for SharedInstanceAttachment {
fn from(attachment: CoreSharedInstanceAttachment) -> Self {
Self {
id: attachment.id.to_string(),
role: attachment.role,
manager_id: attachment.manager_id,
server_manager_name: attachment.server_manager_name,
server_manager_icon_url: attachment.server_manager_icon_url,
linked_user_id: attachment.linked_user_id,
status: attachment.status.as_str().to_string(),
applied_version: attachment.applied_version,
latest_version: attachment.latest_version,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EditInstance {
pub name: Option<String>,
pub game_version: Option<String>,
pub loader: Option<ModLoader>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_with::rust::double_option"
)]
pub loader_version: Option<Option<String>>,
pub group_ids: Option<Vec<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_with::rust::double_option"
)]
pub link: Option<Option<InstanceLink>>,
pub update_channel: Option<ReleaseChannel>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_with::rust::double_option"
)]
pub java_path: Option<Option<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_with::rust::double_option"
)]
pub extra_launch_args: Option<Option<Vec<String>>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_with::rust::double_option"
)]
pub custom_env_vars: Option<Option<Vec<(String, String)>>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_with::rust::double_option"
)]
pub memory: Option<Option<MemorySettings>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_with::rust::double_option"
)]
pub force_fullscreen: Option<Option<bool>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_with::rust::double_option"
)]
pub game_resolution: Option<Option<WindowSize>>,
pub hooks: Option<Hooks>,
}
impl From<InstanceMetadata> for Instance {
fn from(metadata: InstanceMetadata) -> Self {
Self {
id: metadata.instance.id,
path: metadata.instance.path,
install_stage: metadata.instance.install_stage.as_str().to_string(),
launcher_feature_version: metadata
.instance
.launcher_feature_version
.as_str()
.to_string(),
name: metadata.instance.name,
icon_path: metadata.instance.icon_path,
icon_config: metadata.icon_config,
game_version: metadata.applied_content_set.game_version,
protocol_version: metadata.applied_content_set.protocol_version,
loader: metadata.applied_content_set.loader,
loader_version: metadata.applied_content_set.loader_version,
group_ids: metadata.group_ids,
synced_options: metadata.synced_options,
link: InstanceLink::from_core(metadata.link),
shared_instance: metadata.shared_instance.map(Into::into),
quarantined: metadata.quarantined,
update_channel: metadata.instance.update_channel,
created: metadata.instance.created,
modified: metadata.instance.modified,
last_played: metadata.instance.last_played,
submitted_time_played: metadata.instance.submitted_time_played,
recent_time_played: metadata.instance.recent_time_played,
java_path: metadata.launch_overrides.java_path,
extra_launch_args: metadata.launch_overrides.extra_launch_args,
custom_env_vars: metadata.launch_overrides.custom_env_vars,
memory: metadata.launch_overrides.memory,
force_fullscreen: metadata.launch_overrides.force_fullscreen,
game_resolution: metadata.launch_overrides.game_resolution,
hooks: metadata.launch_overrides.hooks,
}
}
}
impl InstanceLink {
fn from_core(link: CoreInstanceLink) -> Option<Self> {
match link {
CoreInstanceLink::Unmanaged => None,
CoreInstanceLink::ModrinthModpack {
project_id,
version_id,
} => Some(Self::ModrinthModpack {
project_id,
version_id,
}),
CoreInstanceLink::ServerProject { project_id } => {
Some(Self::ServerProject { project_id })
}
CoreInstanceLink::ServerProjectModpack {
server_project_id,
content_project_id,
content_version_id,
} => Some(Self::ServerProjectModpack {
project_id: Some(server_project_id.clone()),
version_id: Some(content_version_id.clone()),
server_project_id,
content_project_id: Some(content_project_id),
content_version_id,
}),
CoreInstanceLink::ImportedModpack {
project_id,
version_id,
name,
version_number,
filename,
} => Some(Self::ImportedModpack {
project_id,
version_id,
name,
version_number,
filename,
}),
CoreInstanceLink::ModrinthHosting {
server_id,
instance_ids,
active_instance_id,
} => Some(Self::ModrinthHosting {
server_id: server_id.to_string(),
instance_ids: instance_ids
.into_iter()
.map(|id| id.to_string())
.collect(),
active_instance_id: active_instance_id.map(|id| id.to_string()),
}),
CoreInstanceLink::SharedInstance {
modpack_project_id,
modpack_version_id,
} => Some(Self::SharedInstance {
modpack_project_id,
modpack_version_id,
}),
}
}
pub(crate) fn into_core(self) -> Result<CoreInstanceLink> {
match self {
Self::ModrinthModpack {
project_id,
version_id,
} => Ok(CoreInstanceLink::ModrinthModpack {
project_id,
version_id,
}),
Self::ServerProject { project_id } => {
Ok(CoreInstanceLink::ServerProject { project_id })
}
Self::ServerProjectModpack {
server_project_id,
content_project_id,
content_version_id,
..
} => Ok(CoreInstanceLink::ServerProjectModpack {
server_project_id,
content_project_id: content_project_id.unwrap_or_default(),
content_version_id,
}),
Self::ImportedModpack {
project_id,
version_id,
name,
version_number,
filename,
} => Ok(CoreInstanceLink::ImportedModpack {
project_id,
version_id,
name,
version_number,
filename,
}),
Self::ModrinthHosting {
server_id,
instance_ids,
active_instance_id,
} => Ok(CoreInstanceLink::ModrinthHosting {
server_id: server_id.parse().map_err(|err| {
theseus::Error::from(theseus::ErrorKind::InputError(
format!("Invalid server id: {err}"),
))
})?,
instance_ids: instance_ids
.into_iter()
.map(|id| {
id.parse().map_err(|err| {
theseus::Error::from(
theseus::ErrorKind::InputError(format!(
"Invalid hosted instance id: {err}"
)),
)
})
})
.collect::<std::result::Result<Vec<_>, _>>()?,
active_instance_id: active_instance_id
.map(|id| {
id.parse().map_err(|err| {
theseus::Error::from(
theseus::ErrorKind::InputError(format!(
"Invalid active instance id: {err}"
)),
)
})
})
.transpose()?,
}),
Self::SharedInstance {
modpack_project_id,
modpack_version_id,
} => Ok(CoreInstanceLink::SharedInstance {
modpack_project_id,
modpack_version_id,
}),
}
}
}
fn edit_to_core(edit_instance: EditInstance) -> Result<CoreEditInstance> {
Ok(CoreEditInstance {
install_stage: None,
launcher_feature_version: None,
name: edit_instance.name,
icon_path: None,
icon_config: None,
update_channel: edit_instance.update_channel,
group_ids: edit_instance.group_ids,
link: edit_instance
.link
.map(|link| match link {
Some(link) => link.into_core(),
None => Ok(CoreInstanceLink::Unmanaged),
})
.transpose()?,
launch_overrides: Some(InstanceLaunchOverridesPatch {
java_path: edit_instance.java_path,
extra_launch_args: edit_instance.extra_launch_args,
custom_env_vars: edit_instance.custom_env_vars,
memory: edit_instance.memory,
force_fullscreen: edit_instance.force_fullscreen,
game_resolution: edit_instance.game_resolution,
hooks: edit_instance.hooks,
}),
content_set_patch: Some(AppliedContentSetPatch {
source_kind: None,
game_version: edit_instance.game_version,
protocol_version: Some(None),
loader: edit_instance.loader,
loader_version: edit_instance.loader_version,
}),
last_played: None,
submitted_time_played: None,
recent_time_played: None,
})
}
#[tauri::command]
pub async fn instance_remove(instance_id: &str) -> Result<()> {
theseus::instance::remove(instance_id).await?;
Ok(())
}
#[tauri::command]
pub async fn instance_get(instance_id: &str) -> Result<Option<Instance>> {
Ok(theseus::instance::get(instance_id)
.await?
.map(Instance::from))
}
#[tauri::command]
pub async fn instance_get_many(
instance_ids: Vec<String>,
) -> Result<Vec<Instance>> {
let ids = instance_ids.iter().map(|x| &**x).collect::<Vec<&str>>();
Ok(theseus::instance::get_many(&ids)
.await?
.into_iter()
.map(Instance::from)
.collect())
}
#[tauri::command]
pub async fn instance_list() -> Result<Vec<Instance>> {
Ok(theseus::instance::list()
.await?
.into_iter()
.map(Instance::from)
.collect())
}
#[tauri::command]
pub async fn instance_list_groups()
-> Result<Vec<theseus::instance::InstanceGroup>> {
Ok(theseus::instance::list_groups().await?)
}
#[tauri::command]
pub async fn instance_create_group(
name: String,
) -> Result<theseus::instance::InstanceGroup> {
Ok(theseus::instance::create_group(name).await?)
}
#[tauri::command]
pub async fn instance_rename_group(
id: String,
new_name: String,
) -> Result<theseus::instance::InstanceGroup> {
Ok(theseus::instance::rename_group(id, new_name).await?)
}
#[tauri::command]
pub async fn instance_delete_group(id: String) -> Result<()> {
Ok(theseus::instance::delete_group(id).await?)
}
#[tauri::command]
pub async fn instance_set_group_order(group_ids: Vec<String>) -> Result<()> {
Ok(theseus::instance::set_group_order(group_ids).await?)
}
#[tauri::command]
pub async fn instance_set_group_memberships(
updates: Vec<theseus::instance::InstanceGroupMembershipUpdate>,
) -> Result<()> {
Ok(theseus::instance::set_group_memberships(updates).await?)
}
#[tauri::command]
pub async fn instance_get_projects(
instance_id: &str,
cache_behaviour: Option<CacheBehaviour>,
) -> Result<DashMap<String, ContentFile>> {
Ok(theseus::instance::get_projects(instance_id, cache_behaviour).await?)
}
#[tauri::command]
pub async fn instance_get_installed_project_ids(
instance_id: &str,
) -> Result<Vec<String>> {
Ok(theseus::instance::get_installed_project_ids(instance_id).await?)
}
#[tauri::command]
pub async fn instance_get_install_candidates(
project_id: &str,
project_type: ProjectType,
targets: Vec<InstanceInstallTarget>,
) -> Result<Vec<InstanceInstallCandidate>> {
Ok(theseus::instance::get_install_candidates(
project_id,
project_type,
targets,
)
.await?)
}
#[tauri::command]
pub async fn instance_content(
instance_id: &str,
cache_behaviour: Option<CacheBehaviour>,
) -> Result<Vec<ContentItem>> {
instance_get_content_items(instance_id, cache_behaviour).await
}
#[tauri::command]
pub async fn instance_get_content_items(
instance_id: &str,
cache_behaviour: Option<CacheBehaviour>,
) -> Result<Vec<ContentItem>> {
Ok(
theseus::instance::get_content_items(instance_id, cache_behaviour)
.await?,
)
}
#[tauri::command]
pub async fn instance_refresh_content_updates(instance_id: &str) -> Result<()> {
Ok(theseus::instance::refresh_content_updates(instance_id).await?)
}
#[tauri::command]
pub async fn instance_get_dependencies_as_content_items(
dependencies: Vec<Dependency>,
cache_behaviour: Option<CacheBehaviour>,
) -> Result<Vec<ContentItem>> {
Ok(theseus::instance::get_dependencies_as_content_items(
dependencies,
cache_behaviour,
)
.await?)
}
#[tauri::command]
pub async fn instance_get_linked_modpack_info(
instance_id: &str,
cache_behaviour: Option<CacheBehaviour>,
) -> Result<Option<LinkedModpackInfo>> {
Ok(
theseus::instance::get_linked_modpack_info(
instance_id,
cache_behaviour,
)
.await?,
)
}
#[tauri::command]
pub async fn instance_get_linked_modpack_content(
instance_id: &str,
cache_behaviour: Option<CacheBehaviour>,
) -> Result<Vec<ContentItem>> {
Ok(theseus::instance::get_linked_modpack_content(
instance_id,
cache_behaviour,
)
.await?)
}
#[tauri::command]
pub async fn instance_get_full_path(instance_id: &str) -> Result<PathBuf> {
Ok(theseus::instance::get_full_path(instance_id).await?)
}
#[tauri::command]
pub async fn instance_get_mod_full_path(
instance_id: &str,
project_path: &str,
) -> Result<PathBuf> {
Ok(theseus::instance::get_mod_full_path(instance_id, project_path).await?)
}
#[tauri::command]
pub async fn instance_list_screenshots<R: Runtime>(
app_handle: AppHandle<R>,
instance_id: &str,
) -> Result<Vec<InstanceScreenshot>> {
let screenshots = theseus::instance::list_screenshots(instance_id).await?;
serialize_screenshots(&app_handle, screenshots)
}
#[tauri::command]
pub async fn instance_list_all_screenshots<R: Runtime>(
app_handle: AppHandle<R>,
) -> Result<Vec<InstanceScreenshot>> {
let screenshots = theseus::instance::list_all_screenshots().await?;
serialize_screenshots(&app_handle, screenshots)
}
#[tauri::command]
pub async fn instance_list_synced_screenshots<R: Runtime>(
app_handle: AppHandle<R>,
) -> Result<Vec<InstanceScreenshot>> {
let screenshots = theseus::instance::list_synced_screenshots().await?;
serialize_screenshots(&app_handle, screenshots)
}
#[tauri::command]
pub async fn instance_save_edited_screenshot<R: Runtime>(
app_handle: AppHandle<R>,
key: theseus::instance::ScreenshotKey,
png_bytes: Vec<u8>,
mode: theseus::instance::ScreenshotEditSaveMode,
) -> Result<InstanceScreenshot> {
let screenshot =
theseus::instance::save_edited_screenshot(key, png_bytes, mode).await?;
serialize_screenshot(&app_handle, screenshot)
}
#[tauri::command]
pub async fn instance_list_screenshot_groups()
-> Result<Vec<theseus::instance::ScreenshotGroup>> {
Ok(theseus::instance::list_screenshot_groups().await?)
}
#[tauri::command]
pub async fn instance_create_screenshot_group(
name: String,
screenshot_ids: Vec<String>,
) -> Result<theseus::instance::ScreenshotGroup> {
Ok(
theseus::instance::create_screenshot_group(name, screenshot_ids)
.await?,
)
}
#[tauri::command]
pub async fn instance_rename_screenshot_group(
id: String,
new_name: String,
) -> Result<theseus::instance::ScreenshotGroup> {
Ok(theseus::instance::rename_screenshot_group(id, new_name).await?)
}
#[tauri::command]
pub async fn instance_delete_screenshot_group(id: String) -> Result<()> {
Ok(theseus::instance::delete_screenshot_group(id).await?)
}
#[tauri::command]
pub async fn instance_set_screenshot_group_memberships(
updates: Vec<theseus::instance::ScreenshotGroupMembershipUpdate>,
) -> Result<()> {
Ok(theseus::instance::set_screenshot_group_memberships(updates).await?)
}
#[tauri::command]
pub async fn instance_import_screenshot_groups(
groups: Vec<theseus::instance::ScreenshotGroupImport>,
) -> Result<()> {
Ok(theseus::instance::import_screenshot_groups(groups).await?)
}
#[tauri::command]
pub async fn instance_delete_screenshots(
keys: Vec<theseus::instance::ScreenshotKey>,
) -> Result<()> {
Ok(theseus::instance::delete_screenshots(&keys).await?)
}
#[tauri::command]
pub async fn instance_export_screenshots(
keys: Vec<theseus::instance::ScreenshotKey>,
export_path: PathBuf,
) -> Result<()> {
Ok(theseus::instance::export_screenshots(&keys, export_path).await?)
}
#[tauri::command]
pub async fn instance_move_screenshots(
keys: Vec<theseus::instance::ScreenshotKey>,
target_instance_id: &str,
) -> Result<Vec<theseus::instance::ScreenshotKey>> {
Ok(theseus::instance::move_screenshots(&keys, target_instance_id).await?)
}
#[tauri::command]
pub async fn instance_open_screenshot<R: Runtime>(
app_handle: AppHandle<R>,
key: theseus::instance::ScreenshotKey,
) -> Result<()> {
let path = theseus::instance::get_screenshot_path(&key).await?;
app_handle
.opener()
.reveal_item_in_dir(path)
.map_err(|error| std::io::Error::other(error.to_string()))?;
Ok(())
}
#[tauri::command]
pub async fn instance_set_synced_option(
instance_id: &str,
option: InstanceSyncedOption,
enabled: bool,
resolution: Option<theseus::instance::SyncedOptionJoinResolution>,
) -> Result<Instance> {
Ok(Instance::from(
theseus::instance::set_synced_option(
instance_id,
option,
enabled,
resolution,
)
.await?,
))
}
#[tauri::command]
pub async fn instance_get_synced_option_join_preview(
instance_id: &str,
option: InstanceSyncedOption,
) -> Result<theseus::instance::SyncedOptionJoinPreview> {
Ok(
theseus::instance::get_synced_option_join_preview(instance_id, option)
.await?,
)
}
#[tauri::command]
pub async fn instance_get_synced_options_overview(
instance_id: &str,
) -> Result<theseus::instance::SyncedOptionsOverview> {
Ok(theseus::instance::get_synced_options_overview(instance_id).await?)
}
#[tauri::command]
pub async fn instance_get_global_synced_options()
-> Result<theseus::instance::GlobalSyncedOptions> {
Ok(theseus::instance::get_global_synced_options().await?)
}
#[tauri::command]
pub async fn instance_set_global_synced_option(
option: InstanceSyncedOption,
enabled: bool,
) -> Result<theseus::instance::GlobalSyncedOptions> {
Ok(theseus::instance::set_global_synced_option(option, enabled).await?)
}
#[tauri::command]
pub async fn instance_get_command_history() -> Result<String> {
Ok(theseus::instance::get_command_history().await?)
}
#[tauri::command]
pub async fn instance_set_command_history(contents: &str) -> Result<String> {
Ok(theseus::instance::set_command_history(contents).await?)
}
#[tauri::command]
pub async fn instance_open_synced_options_folder<R: Runtime>(
app_handle: AppHandle<R>,
) -> Result<()> {
let path = theseus::instance::get_synced_options_folder().await?;
app_handle
.opener()
.open_path(path.to_string_lossy(), None::<&str>)
.map_err(|error| std::io::Error::other(error.to_string()))?;
Ok(())
}
#[tauri::command]
pub async fn instance_list_synced_servers()
-> Result<Vec<theseus::instance::SyncedServer>> {
Ok(theseus::instance::list_synced_servers().await?)
}
#[tauri::command]
pub async fn instance_update_synced_server(
server: theseus::instance::SyncedServer,
) -> Result<()> {
Ok(theseus::instance::update_synced_server(server).await?)
}
#[tauri::command]
pub async fn instance_remove_synced_server(server_id: &str) -> Result<()> {
Ok(theseus::instance::remove_synced_server(server_id).await?)
}
#[tauri::command]
pub async fn instance_rebuild_synced_options(
instance_id: Option<&str>,
) -> Result<()> {
if let Some(instance_id) = instance_id {
theseus::instance::reconcile_instance_synced_options(instance_id)
.await?;
} else {
theseus::instance::reconcile_all_synced_options().await?;
}
Ok(())
}
fn serialize_screenshots<R: Runtime>(
app_handle: &AppHandle<R>,
screenshots: Vec<theseus::instance::InstanceScreenshot>,
) -> Result<Vec<InstanceScreenshot>> {
let mut result = Vec::with_capacity(screenshots.len());
for screenshot in screenshots {
result.push(serialize_screenshot(app_handle, screenshot)?);
}
Ok(result)
}
fn serialize_screenshot<R: Runtime>(
app_handle: &AppHandle<R>,
screenshot: theseus::instance::InstanceScreenshot,
) -> Result<InstanceScreenshot> {
app_handle
.asset_protocol_scope()
.allow_file(&screenshot.path)
.map_err(|error| std::io::Error::other(error.to_string()))?;
app_handle
.fs_scope()
.allow_file(&screenshot.path)
.map_err(|error| std::io::Error::other(error.to_string()))?;
let mut url = super::utils::tauri_convert_file_src(&screenshot.path)?;
url.query_pairs_mut()
.append_pair("revision", &screenshot.modified_at.to_string());
Ok(InstanceScreenshot {
id: screenshot.id,
instance_id: screenshot.instance_id,
instance_name: screenshot.instance_name,
file_name: screenshot.file_name,
created_at: screenshot.created_at,
modified_at: screenshot.modified_at,
group_id: screenshot.group_id,
path: screenshot.path,
url,
})
}
#[tauri::command]
pub async fn instance_get_optimal_jre_key(
instance_id: &str,
) -> Result<Option<JavaVersion>> {
Ok(theseus::instance::get_optimal_jre_key(instance_id).await?)
}
#[tauri::command]
pub async fn instance_check_installed(
instance_id: &str,
project_id: &str,
) -> Result<bool> {
let check_project_id = project_id;
if let Ok(projects) =
theseus::instance::get_projects(instance_id, None).await
{
Ok(projects.into_iter().any(|(_, project)| {
project
.metadata
.as_ref()
.is_some_and(|metadata| check_project_id == metadata.project_id)
}))
} else {
Ok(false)
}
}
#[tauri::command]
pub async fn instance_update_all(
instance_id: &str,
) -> Result<HashMap<String, String>> {
Ok(theseus::instance::update_all_projects(instance_id).await?)
}
#[tauri::command]
pub async fn instance_update_project(
instance_id: &str,
project_path: &str,
) -> Result<String> {
Ok(
theseus::instance::update_project(instance_id, project_path, None)
.await?,
)
}
#[tauri::command]
pub async fn instance_add_project_from_version(
instance_id: &str,
version_id: &str,
reason: DownloadReason,
dependent_on_version_id: Option<String>,
) -> Result<String> {
Ok(theseus::instance::add_project_from_version(
instance_id,
version_id,
reason,
dependent_on_version_id,
)
.await?)
}
#[tauri::command]
pub async fn instance_install_project_with_dependencies(
instance_id: &str,
request: InstallProjectWithDependenciesRequest,
) -> Result<ResolveContentPlan> {
Ok(theseus::instance::install_project_with_dependencies(
instance_id,
request,
)
.await?)
}
#[tauri::command]
pub async fn instance_switch_project_version_with_dependencies(
instance_id: &str,
project_path: &str,
version_id: &str,
) -> Result<String> {
Ok(theseus::instance::switch_project_version_with_dependencies(
instance_id,
project_path,
version_id,
)
.await?)
}
#[tauri::command]
pub async fn instance_add_project_from_path(
instance_id: &str,
project_path: &Path,
project_type: Option<ProjectType>,
) -> Result<String> {
Ok(theseus::instance::add_project_from_path(
instance_id,
project_path,
project_type,
)
.await?)
}
#[tauri::command]
pub async fn instance_is_file_on_modrinth(project_path: &Path) -> Result<bool> {
Ok(theseus::instance::is_file_on_modrinth(project_path).await?)
}
#[tauri::command]
pub async fn instance_toggle_disable_project(
instance_id: &str,
project_path: &str,
desired_enabled: Option<bool>,
) -> Result<String> {
Ok(theseus::instance::toggle_disable_project(
instance_id,
project_path,
desired_enabled,
)
.await?)
}
#[tauri::command]
pub async fn instance_set_project_locked(
instance_id: &str,
project_path: &str,
locked: bool,
) -> Result<()> {
theseus::instance::set_project_locked(instance_id, project_path, locked)
.await?;
Ok(())
}
#[tauri::command]
pub async fn instance_remove_project(
instance_id: &str,
project_path: &str,
) -> Result<()> {
theseus::instance::remove_project(instance_id, project_path).await?;
Ok(())
}
#[tauri::command]
pub async fn instance_update_managed_modrinth_version(
instance_id: String,
version_id: String,
) -> Result<theseus::install::InstallJobSnapshot> {
Ok(theseus::instance::update_managed_modrinth_version(
&instance_id,
&version_id,
)
.await?)
}
#[tauri::command]
pub async fn instance_repair_managed_modrinth(
instance_id: &str,
) -> Result<theseus::install::InstallJobSnapshot> {
Ok(theseus::instance::repair_managed_modrinth(instance_id).await?)
}
#[tauri::command]
pub async fn instance_export_mrpack(
instance_id: &str,
export_location: PathBuf,
included_overrides: Vec<String>,
excluded_overrides: Vec<String>,
version_id: Option<String>,
description: Option<String>,
name: Option<String>,
) -> Result<()> {
theseus::instance::export_mrpack(
instance_id,
export_location,
included_overrides,
excluded_overrides,
version_id,
description,
name,
)
.await?;
Ok(())
}
#[tauri::command]
pub async fn instance_get_pack_export_candidates(
instance_id: &str,
parent: Option<SafeRelativeUtf8UnixPathBuf>,
) -> Result<Vec<theseus::instance::PackExportCandidate>> {
Ok(theseus::instance::get_pack_export_candidates_for_parent(
instance_id,
parent,
)
.await?)
}
#[tauri::command]
pub async fn instance_run(
instance_id: &str,
server_address: Option<String>,
) -> Result<ProcessMetadata> {
let quick_play = match server_address {
Some(addr) => QuickPlayType::Server(ServerAddress::Unresolved(addr)),
None => QuickPlayType::None,
};
Ok(theseus::instance::run(instance_id, quick_play).await?)
}
#[tauri::command]
pub async fn instance_kill(instance_id: &str) -> Result<()> {
theseus::instance::kill(instance_id).await?;
Ok(())
}
#[tauri::command]
pub async fn instance_edit(
instance_id: &str,
edit_instance: EditInstance,
) -> Result<()> {
theseus::instance::edit(instance_id, edit_to_core(edit_instance)?).await?;
Ok(())
}
#[tauri::command]
pub async fn instance_edit_icon(
instance_id: &str,
icon_path: Option<&Path>,
) -> Result<()> {
theseus::instance::edit_icon(instance_id, icon_path).await?;
Ok(())
}
#[tauri::command]
pub async fn instance_edit_generated_icon(
instance_id: &str,
config: theseus::data::InstanceIconConfig,
symbol_bytes: Vec<u8>,
only_if_empty: Option<bool>,
) -> Result<Option<String>> {
if only_if_empty.unwrap_or(false) {
return Ok(theseus::instance::edit_generated_icon_if_empty(
instance_id,
config,
symbol_bytes,
)
.await?);
}
Ok(Some(
theseus::instance::edit_generated_icon(
instance_id,
config,
symbol_bytes,
)
.await?,
))
}
#[tauri::command]
pub async fn instance_cache_generated_icon(
config: theseus::data::InstanceIconConfig,
symbol_bytes: Vec<u8>,
add_to_recents: bool,
) -> Result<String> {
Ok(theseus::instance::cache_generated_icon(
config,
symbol_bytes,
add_to_recents,
)
.await?)
}
#[tauri::command]
pub async fn instance_get_recent_icon_configs()
-> Result<Vec<theseus::data::InstanceIconConfig>> {
Ok(theseus::instance::get_recent_icon_configs().await?)
}
#[tauri::command]
pub async fn instance_share_can_current_user_use() -> Result<bool> {
Ok(theseus::instance::can_active_user_use_shared_instances().await?)
}
#[tauri::command]
pub async fn instance_share_get_users(
instance_id: &str,
) -> Result<theseus::instance::SharedInstanceUsers> {
Ok(theseus::instance::get_shared_instance_users(instance_id).await?)
}
#[tauri::command]
pub async fn instance_share_invite_users(
instance_id: &str,
user_ids: Vec<String>,
) -> Result<theseus::instance::SharedInstanceUsers> {
Ok(
theseus::instance::invite_shared_instance_users(instance_id, user_ids)
.await?,
)
}
#[tauri::command]
pub async fn instance_share_create_invite_link(
instance_id: &str,
max_age_seconds: Option<i32>,
max_uses: Option<i32>,
replace_invite_id: Option<String>,
) -> Result<theseus::instance::SharedInstanceInviteLink> {
Ok(theseus::instance::create_shared_instance_invite_link(
instance_id,
max_age_seconds,
max_uses,
replace_invite_id,
)
.await?)
}
#[tauri::command]
pub async fn instance_share_get_invites(
instance_id: &str,
) -> Result<Vec<theseus::instance::SharedInstanceInvite>> {
Ok(theseus::instance::get_shared_instance_invites(instance_id).await?)
}
#[tauri::command]
pub async fn instance_share_revoke_invite(
instance_id: &str,
invite_id: String,
) -> Result<()> {
Ok(
theseus::instance::revoke_shared_instance_invite(
instance_id,
invite_id,
)
.await?,
)
}
#[tauri::command]
pub async fn instance_share_remove_users(
instance_id: &str,
user_ids: Vec<String>,
has_pending_recipients: bool,
) -> Result<theseus::instance::SharedInstanceUsers> {
Ok(theseus::instance::remove_shared_instance_users(
instance_id,
user_ids,
has_pending_recipients,
)
.await?)
}
#[tauri::command]
pub async fn instance_share_get_publish_preview(
instance_id: &str,
) -> Result<Option<theseus::instance::SharedInstancePublishPreview>> {
Ok(
theseus::instance::get_shared_instance_publish_preview(instance_id)
.await?,
)
}
#[tauri::command]
pub async fn instance_share_publish(
instance_id: &str,
config_paths: Vec<String>,
) -> Result<SharedInstanceAttachment> {
Ok(
theseus::instance::publish_shared_instance(instance_id, config_paths)
.await?
.into(),
)
}
#[tauri::command]
pub async fn instance_share_unlink(instance_id: &str) -> Result<()> {
theseus::instance::unlink_shared_instance(instance_id).await?;
Ok(())
}
#[tauri::command]
pub async fn instance_share_unpublish(instance_id: &str) -> Result<()> {
theseus::instance::unpublish_shared_instance(instance_id).await?;
Ok(())
}