Files
modrinth/apps/app/src/api/instance.rs
T
Calum H. 06b7c44dcc refactor: content tab styling (#7091)
* refactor: managed content card

* fix: update filter pill styling to be chips-like

* fix: modpack exporting

* fixed: vanilla server projs + header

* refactor: filters

* refactor: detect external file icons + other metadata inside

* refactor: support json5 metadata

* fix: qa

* fix: qa

* fix: fmt + prepr

* fix: fmt

* fix: qa

* fix: headers

* feat: locking

* fix: clippy

* fix: qa

* fix: frozen loc

* fix: shared instances diff calc break

* fix: env filter + remove categories
2026-08-13 14:55:35 +00:00

923 lines
27 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 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_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_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_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 game_version: String,
pub protocol_version: Option<u32>,
pub loader: ModLoader,
pub loader_version: Option<String>,
pub groups: Vec<String>,
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, 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 groups: 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,
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,
groups: metadata.groups,
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,
update_channel: edit_instance.update_channel,
groups: edit_instance.groups,
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_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_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_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(())
}