You've already forked AstralRinth
Merge tag 'v0.14.6' into beta
v0.14.6
This commit is contained in:
@@ -30,11 +30,13 @@ macro_rules! impl_cache_methods {
|
||||
|
||||
impl_cache_methods!(
|
||||
(Project, Project),
|
||||
(ProjectV3, ProjectV3),
|
||||
(Version, Version),
|
||||
(User, User),
|
||||
(Team, Vec<TeamMember>),
|
||||
(Organization, Organization),
|
||||
(SearchResults, SearchResults)
|
||||
(SearchResults, SearchResults),
|
||||
(SearchResultsV3, SearchResultsV3)
|
||||
);
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
@@ -42,6 +44,8 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_project,
|
||||
get_project_many,
|
||||
get_project_v3,
|
||||
get_project_v3_many,
|
||||
get_version,
|
||||
get_version_many,
|
||||
get_user,
|
||||
@@ -52,7 +56,10 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
get_organization_many,
|
||||
get_search_results,
|
||||
get_search_results_many,
|
||||
get_search_results_v3,
|
||||
get_search_results_v3_many,
|
||||
purge_cache_types,
|
||||
get_project_versions,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -61,3 +68,14 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
pub async fn purge_cache_types(cache_types: Vec<CacheValueType>) -> Result<()> {
|
||||
Ok(theseus::cache::purge_cache_types(&cache_types).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_project_versions(
|
||||
project_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> Result<Option<Vec<Version>>> {
|
||||
Ok(
|
||||
theseus::cache::get_project_versions(project_id, cache_behaviour)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
use crate::api::Result;
|
||||
use async_zip::base::read::seek::ZipFileReader;
|
||||
use serde::Serialize;
|
||||
use std::io::Cursor;
|
||||
use tauri::Runtime;
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
use theseus::profile::get_full_path;
|
||||
|
||||
pub fn init<R: Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("files")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
file_extract_zip,
|
||||
file_save_as,
|
||||
file_read_dragged_file,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ExtractDryRunResult {
|
||||
modpack_name: Option<String>,
|
||||
conflicting_files: Vec<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn file_read_dragged_file(path: String) -> Result<Vec<u8>> {
|
||||
let metadata = tokio::fs::metadata(&path).await?;
|
||||
if !metadata.is_file() {
|
||||
return Err(theseus::Error::from(theseus::ErrorKind::OtherError(
|
||||
"Dropped path is not a file".to_string(),
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(tokio::fs::read(path).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn file_extract_zip(
|
||||
instance_path: &str,
|
||||
file_path: &str,
|
||||
override_conflicts: bool,
|
||||
dry_run: bool,
|
||||
) -> Result<Option<ExtractDryRunResult>> {
|
||||
let base = get_full_path(instance_path).await?;
|
||||
let zip_path = base.join(file_path);
|
||||
let canonical_zip = tokio::fs::canonicalize(&zip_path).await?;
|
||||
let canonical_base = tokio::fs::canonicalize(&base).await?;
|
||||
if !canonical_zip.starts_with(&canonical_base) {
|
||||
return Err(theseus::Error::from(theseus::ErrorKind::OtherError(
|
||||
"file_path escapes the instance directory".to_string(),
|
||||
))
|
||||
.into());
|
||||
}
|
||||
let extract_dir = zip_path
|
||||
.parent()
|
||||
.map(|p| p.to_path_buf())
|
||||
.unwrap_or_else(|| base.clone());
|
||||
|
||||
let file_bytes = tokio::fs::read(&zip_path).await?;
|
||||
let reader = Cursor::new(file_bytes);
|
||||
|
||||
let zip_reader = ZipFileReader::with_tokio(reader).await.map_err(|e| {
|
||||
theseus::Error::from(theseus::ErrorKind::OtherError(format!(
|
||||
"Failed to read zip file: {e}"
|
||||
)))
|
||||
})?;
|
||||
|
||||
let entries: Vec<(usize, String)> = zip_reader
|
||||
.file()
|
||||
.entries()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, entry)| {
|
||||
let name = entry.filename().as_str().ok()?.to_string();
|
||||
if name.ends_with('/') {
|
||||
None
|
||||
} else {
|
||||
Some((i, name))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if dry_run {
|
||||
let mut conflicting_files = Vec::new();
|
||||
let canonical_extract = tokio::fs::canonicalize(&extract_dir).await?;
|
||||
for (_, name) in &entries {
|
||||
let target = extract_dir.join(name);
|
||||
if let Some(parent) = target.parent() {
|
||||
let normalized = parent
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| extract_dir.join(parent));
|
||||
if !normalized.starts_with(&canonical_extract) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if target.exists() {
|
||||
conflicting_files.push(name.clone());
|
||||
}
|
||||
}
|
||||
return Ok(Some(ExtractDryRunResult {
|
||||
modpack_name: None,
|
||||
conflicting_files,
|
||||
}));
|
||||
}
|
||||
|
||||
let canonical_extract_dir = tokio::fs::canonicalize(&extract_dir).await?;
|
||||
let mut zip_reader = zip_reader;
|
||||
for (index, name) in &entries {
|
||||
let target = extract_dir.join(name);
|
||||
|
||||
if !override_conflicts && target.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(parent) = target.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
let canonical_parent = tokio::fs::canonicalize(parent).await?;
|
||||
if !canonical_parent.starts_with(&canonical_extract_dir) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut file_bytes = Vec::new();
|
||||
let mut entry_reader =
|
||||
zip_reader.reader_with_entry(*index).await.map_err(|e| {
|
||||
theseus::Error::from(theseus::ErrorKind::OtherError(format!(
|
||||
"Failed to read zip entry: {e}"
|
||||
)))
|
||||
})?;
|
||||
entry_reader
|
||||
.read_to_end_checked(&mut file_bytes)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
theseus::Error::from(theseus::ErrorKind::OtherError(format!(
|
||||
"Failed to extract zip entry: {e}"
|
||||
)))
|
||||
})?;
|
||||
|
||||
tokio::fs::write(&target, &file_bytes).await?;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn file_save_as<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
instance_path: &str,
|
||||
file_path: &str,
|
||||
) -> Result<()> {
|
||||
let base = get_full_path(instance_path).await?;
|
||||
let source = base.join(file_path);
|
||||
let file_name = source
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
app.dialog()
|
||||
.file()
|
||||
.set_file_name(&file_name)
|
||||
.save_file(|path| {
|
||||
let _ = tx.send(path);
|
||||
});
|
||||
|
||||
if let Some(dest) = rx.await.unwrap_or(None) {
|
||||
let dest_path = std::path::PathBuf::try_from(dest).map_err(|e| {
|
||||
theseus::Error::from(theseus::ErrorKind::OtherError(format!(
|
||||
"Invalid save path: {e}"
|
||||
)))
|
||||
})?;
|
||||
tokio::fs::copy(&source, &dest_path).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,11 +2,6 @@ use std::path::PathBuf;
|
||||
|
||||
use crate::api::Result;
|
||||
use theseus::pack::import::ImportLauncherType;
|
||||
use theseus::pack::import::curseforge_profile::{
|
||||
CurseForgeProfileMetadata,
|
||||
fetch_curseforge_profile_metadata as fetch_cf_metadata,
|
||||
import_curseforge_profile as import_cf_profile,
|
||||
};
|
||||
|
||||
use theseus::pack::import;
|
||||
|
||||
@@ -17,8 +12,6 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
import_instance,
|
||||
is_valid_importable_instance,
|
||||
get_default_launcher_path,
|
||||
fetch_curseforge_profile_metadata,
|
||||
import_curseforge_profile,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -75,24 +68,3 @@ pub async fn get_default_launcher_path(
|
||||
) -> Result<Option<PathBuf>> {
|
||||
Ok(import::get_default_launcher_path(launcher_type))
|
||||
}
|
||||
|
||||
/// Fetch CurseForge profile metadata from profile code
|
||||
/// eg: fetch_curseforge_profile_metadata("eSrNlKNo")
|
||||
#[tauri::command]
|
||||
pub async fn fetch_curseforge_profile_metadata(
|
||||
profile_code: String,
|
||||
) -> Result<CurseForgeProfileMetadata> {
|
||||
Ok(fetch_cf_metadata(&profile_code).await?)
|
||||
}
|
||||
|
||||
/// Import a CurseForge profile from profile code
|
||||
/// profile_path should be a blank profile for this purpose- if the function fails, it will be deleted
|
||||
/// eg: import_curseforge_profile("profile-path", "eSrNlKNo")
|
||||
#[tauri::command]
|
||||
pub async fn import_curseforge_profile(
|
||||
profile_path: String,
|
||||
profile_code: String,
|
||||
) -> Result<()> {
|
||||
import_cf_profile(&profile_code, &profile_path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
logs_delete_logs,
|
||||
logs_delete_logs_by_filename,
|
||||
logs_get_latest_log_cursor,
|
||||
logs_get_live_log_buffer,
|
||||
logs_clear_live_log_buffer,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -83,3 +85,18 @@ pub async fn logs_get_latest_log_cursor(
|
||||
) -> Result<LatestLogCursor> {
|
||||
Ok(logs::get_latest_log_cursor(profile_path, cursor).await?)
|
||||
}
|
||||
|
||||
/// Get all buffered live log lines for a profile
|
||||
#[tauri::command]
|
||||
pub async fn logs_get_live_log_buffer(
|
||||
profile_path: &str,
|
||||
) -> Result<CensoredString> {
|
||||
Ok(logs::get_live_log_buffer(profile_path).await?)
|
||||
}
|
||||
|
||||
/// Clear the live log buffer for a profile
|
||||
#[tauri::command]
|
||||
pub async fn logs_clear_live_log_buffer(profile_path: &str) -> Result<()> {
|
||||
logs::clear_live_log_buffer(profile_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
get_available_capes,
|
||||
get_available_skins,
|
||||
add_and_equip_custom_skin,
|
||||
set_default_cape,
|
||||
equip_skin,
|
||||
remove_custom_skin,
|
||||
save_custom_skin,
|
||||
set_custom_skin_order,
|
||||
unequip_skin,
|
||||
flush_pending_skin_change,
|
||||
flush_pending_skin_change_for_profile,
|
||||
normalize_skin_texture,
|
||||
get_dragged_skin_data,
|
||||
])
|
||||
@@ -37,29 +40,19 @@ pub async fn get_available_skins() -> Result<Vec<Skin>> {
|
||||
Ok(minecraft_skins::get_available_skins().await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|add_and_equip_custom_skin', texture_blob, variant, cape_override)`
|
||||
/// `invoke('plugin:minecraft-skins|add_and_equip_custom_skin', texture_blob, variant, cape)`
|
||||
///
|
||||
/// See also: [minecraft_skins::add_and_equip_custom_skin]
|
||||
#[tauri::command]
|
||||
pub async fn add_and_equip_custom_skin(
|
||||
texture_blob: Bytes,
|
||||
variant: MinecraftSkinVariant,
|
||||
cape_override: Option<Cape>,
|
||||
) -> Result<()> {
|
||||
Ok(minecraft_skins::add_and_equip_custom_skin(
|
||||
texture_blob,
|
||||
variant,
|
||||
cape_override,
|
||||
cape: Option<Cape>,
|
||||
) -> Result<Skin> {
|
||||
Ok(
|
||||
minecraft_skins::add_and_equip_custom_skin(texture_blob, variant, cape)
|
||||
.await?,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|set_default_cape', cape)`
|
||||
///
|
||||
/// See also: [minecraft_skins::set_default_cape]
|
||||
#[tauri::command]
|
||||
pub async fn set_default_cape(cape: Option<Cape>) -> Result<()> {
|
||||
Ok(minecraft_skins::set_default_cape(cape).await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|equip_skin', skin)`
|
||||
@@ -78,6 +71,35 @@ pub async fn remove_custom_skin(skin: Skin) -> Result<()> {
|
||||
Ok(minecraft_skins::remove_custom_skin(skin).await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|save_custom_skin', skin, texture_blob, variant, cape, replace_texture)`
|
||||
///
|
||||
/// See also: [minecraft_skins::save_custom_skin]
|
||||
#[tauri::command]
|
||||
pub async fn save_custom_skin(
|
||||
skin: Skin,
|
||||
texture_blob: Bytes,
|
||||
variant: MinecraftSkinVariant,
|
||||
cape: Option<Cape>,
|
||||
replace_texture: bool,
|
||||
) -> Result<Skin> {
|
||||
Ok(minecraft_skins::save_custom_skin(
|
||||
skin,
|
||||
texture_blob,
|
||||
variant,
|
||||
cape,
|
||||
replace_texture,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|set_custom_skin_order', texture_keys)`
|
||||
///
|
||||
/// See also: [minecraft_skins::set_custom_skin_order]
|
||||
#[tauri::command]
|
||||
pub async fn set_custom_skin_order(texture_keys: Vec<String>) -> Result<()> {
|
||||
Ok(minecraft_skins::set_custom_skin_order(texture_keys).await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|unequip_skin')`
|
||||
///
|
||||
/// See also: [minecraft_skins::unequip_skin]
|
||||
@@ -86,6 +108,27 @@ pub async fn unequip_skin() -> Result<()> {
|
||||
Ok(minecraft_skins::unequip_skin().await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|flush_pending_skin_change')`
|
||||
///
|
||||
/// See also: [minecraft_skins::flush_pending_skin_change]
|
||||
#[tauri::command]
|
||||
pub async fn flush_pending_skin_change() -> Result<()> {
|
||||
Ok(minecraft_skins::flush_pending_skin_change().await?)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|flush_pending_skin_change_for_profile', profile_id)`
|
||||
///
|
||||
/// See also: [minecraft_skins::flush_pending_skin_change_for_profile]
|
||||
#[tauri::command]
|
||||
pub async fn flush_pending_skin_change_for_profile(
|
||||
profile_id: uuid::Uuid,
|
||||
) -> Result<()> {
|
||||
Ok(
|
||||
minecraft_skins::flush_pending_skin_change_for_profile(profile_id)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
/// `invoke('plugin:minecraft-skins|normalize_skin_texture')`
|
||||
///
|
||||
/// See also: [minecraft_skins::normalize_skin_texture]
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod tags;
|
||||
pub mod utils;
|
||||
|
||||
pub mod cache;
|
||||
pub mod files;
|
||||
pub mod friends;
|
||||
pub mod worlds;
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -26,8 +26,8 @@ pub async fn pack_install(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pack_get_profile_from_pack(
|
||||
pub async fn pack_get_profile_from_pack(
|
||||
location: CreatePackLocation,
|
||||
) -> Result<CreatePackProfile> {
|
||||
Ok(pack::install_from::get_profile_from_pack(location))
|
||||
Ok(pack::install_from::get_profile_from_pack(location).await?)
|
||||
}
|
||||
|
||||
+117
-3
@@ -4,8 +4,11 @@ use path_util::SafeRelativeUtf8UnixPathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use theseus::DownloadReason;
|
||||
use theseus::data::{ContentItem, Dependency, LinkedModpackInfo};
|
||||
use theseus::prelude::*;
|
||||
use theseus::profile::QuickPlayType;
|
||||
use theseus::server_address::ServerAddress;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("profile")
|
||||
@@ -14,11 +17,17 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
profile_get,
|
||||
profile_get_many,
|
||||
profile_get_projects,
|
||||
profile_get_installed_project_ids,
|
||||
profile_get_content_items,
|
||||
profile_get_dependencies_as_content_items,
|
||||
profile_get_linked_modpack_info,
|
||||
profile_get_linked_modpack_content,
|
||||
profile_get_optimal_jre_key,
|
||||
profile_get_full_path,
|
||||
profile_get_mod_full_path,
|
||||
profile_list,
|
||||
profile_check_installed,
|
||||
profile_check_installed_batch,
|
||||
profile_install,
|
||||
profile_update_all,
|
||||
profile_update_project,
|
||||
@@ -70,6 +79,68 @@ pub async fn profile_get_projects(
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn profile_get_installed_project_ids(
|
||||
path: &str,
|
||||
) -> Result<Vec<String>> {
|
||||
let res = profile::get_installed_project_ids(path).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get content items with rich metadata for a profile
|
||||
///
|
||||
/// Returns content items filtered to exclude modpack files (if linked),
|
||||
/// sorted alphabetically by project name.
|
||||
#[tauri::command]
|
||||
pub async fn profile_get_content_items(
|
||||
path: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> Result<Vec<ContentItem>> {
|
||||
let res = profile::get_content_items(path, cache_behaviour).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Convert a list of dependencies into ContentItems with rich metadata
|
||||
#[tauri::command]
|
||||
pub async fn profile_get_dependencies_as_content_items(
|
||||
dependencies: Vec<Dependency>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> Result<Vec<ContentItem>> {
|
||||
let res = profile::get_dependencies_as_content_items(
|
||||
dependencies,
|
||||
cache_behaviour,
|
||||
)
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get linked modpack info for a profile
|
||||
///
|
||||
/// Returns project, version, and owner information for the linked modpack,
|
||||
/// or None if the profile is not linked to a modpack.
|
||||
#[tauri::command]
|
||||
pub async fn profile_get_linked_modpack_info(
|
||||
path: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> Result<Option<LinkedModpackInfo>> {
|
||||
let res = profile::get_linked_modpack_info(path, cache_behaviour).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get content items that are part of the linked modpack
|
||||
///
|
||||
/// Returns the modpack's dependencies as ContentItem list.
|
||||
/// Returns empty vec if the profile is not linked to a modpack.
|
||||
#[tauri::command]
|
||||
pub async fn profile_get_linked_modpack_content(
|
||||
path: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> Result<Vec<ContentItem>> {
|
||||
let res =
|
||||
profile::get_linked_modpack_content(path, cache_behaviour).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Get a profile's full path
|
||||
// invoke('plugin:profile|profile_get_full_path',path)
|
||||
#[tauri::command]
|
||||
@@ -126,6 +197,28 @@ pub async fn profile_check_installed(
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn profile_check_installed_batch(
|
||||
project_id: &str,
|
||||
) -> Result<HashMap<String, bool>> {
|
||||
let profiles = profile::list().await?;
|
||||
let mut result = HashMap::new();
|
||||
for p in profiles {
|
||||
let installed =
|
||||
if let Ok(projects) = profile::get_projects(&p.path, None).await {
|
||||
projects.into_iter().any(|(_, pf)| {
|
||||
pf.metadata
|
||||
.as_ref()
|
||||
.is_some_and(|m| m.project_id == project_id)
|
||||
})
|
||||
} else {
|
||||
false
|
||||
};
|
||||
result.insert(p.path.clone(), installed);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Installs/Repairs a profile
|
||||
/// invoke('plugin:profile|profile_install')
|
||||
#[tauri::command]
|
||||
@@ -157,8 +250,16 @@ pub async fn profile_update_project(
|
||||
pub async fn profile_add_project_from_version(
|
||||
path: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
dependent_on_version_id: Option<String>,
|
||||
) -> Result<String> {
|
||||
Ok(profile::add_project_from_version(path, version_id).await?)
|
||||
Ok(profile::add_project_from_version(
|
||||
path,
|
||||
version_id,
|
||||
reason,
|
||||
dependent_on_version_id,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
// Adds a project to a profile from a path
|
||||
@@ -250,8 +351,15 @@ pub async fn profile_get_pack_export_candidates(
|
||||
// for the actual Child in the state.
|
||||
// invoke('plugin:profile|profile_run', path)
|
||||
#[tauri::command]
|
||||
pub async fn profile_run(path: &str) -> Result<ProcessMetadata> {
|
||||
let process = profile::run(path, QuickPlayType::None).await?;
|
||||
pub async fn profile_run(
|
||||
path: &str,
|
||||
server_address: Option<String>,
|
||||
) -> Result<ProcessMetadata> {
|
||||
let quick_play = match server_address {
|
||||
Some(addr) => QuickPlayType::Server(ServerAddress::Unresolved(addr)),
|
||||
None => QuickPlayType::None,
|
||||
};
|
||||
let process = profile::run(path, quick_play).await?;
|
||||
|
||||
Ok(process)
|
||||
}
|
||||
@@ -284,6 +392,7 @@ pub struct EditProfile {
|
||||
with = "serde_with::rust::double_option"
|
||||
)]
|
||||
pub linked_data: Option<Option<LinkedData>>,
|
||||
pub preferred_update_channel: Option<ReleaseChannel>,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
@@ -348,6 +457,11 @@ pub async fn profile_edit(path: &str, edit_profile: EditProfile) -> Result<()> {
|
||||
if let Some(linked_data) = edit_profile.linked_data.clone() {
|
||||
prof.linked_data = linked_data;
|
||||
}
|
||||
if let Some(preferred_update_channel) =
|
||||
edit_profile.preferred_update_channel
|
||||
{
|
||||
prof.preferred_update_channel = preferred_update_channel;
|
||||
}
|
||||
if let Some(groups) = edit_profile.groups.clone() {
|
||||
prof.groups = groups;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ pub async fn profile_create(
|
||||
loader_version: Option<String>, // the modloader version to use, set to "latest", "stable", or the ID of your chosen loader
|
||||
icon: Option<String>, // the icon for the profile
|
||||
skip_install: Option<bool>,
|
||||
linked_data: Option<LinkedData>,
|
||||
) -> Result<String> {
|
||||
let res = profile::create::profile_create(
|
||||
name,
|
||||
@@ -27,7 +28,7 @@ pub async fn profile_create(
|
||||
modloader,
|
||||
loader_version,
|
||||
icon,
|
||||
None,
|
||||
linked_data,
|
||||
skip_install,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::api::Result;
|
||||
use tauri::Runtime;
|
||||
use theseus::prelude::*;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
@@ -28,7 +29,10 @@ pub async fn settings_set(settings: Settings) -> Result<()> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn cancel_directory_change() -> Result<()> {
|
||||
settings::cancel_directory_change().await?;
|
||||
pub async fn cancel_directory_change<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> Result<()> {
|
||||
let identifier = &app.config().identifier;
|
||||
settings::cancel_directory_change(identifier).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+14
-16
@@ -16,7 +16,6 @@ use url::Url;
|
||||
pub fn init<R: Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("utils")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
apply_migration_fix,
|
||||
init_update_launcher,
|
||||
get_os,
|
||||
is_network_metered,
|
||||
@@ -30,13 +29,6 @@ pub fn init<R: Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
.build()
|
||||
}
|
||||
|
||||
// This code is modified by AstralRinth
|
||||
#[tauri::command]
|
||||
pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
|
||||
let result = utils::apply_migration_fix(eol).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// This code is modified by AstralRinth
|
||||
#[tauri::command]
|
||||
pub async fn init_update_launcher(
|
||||
@@ -111,13 +103,17 @@ pub async fn should_disable_mouseover() -> bool {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn highlight_in_folder<R: Runtime>(
|
||||
pub async fn highlight_in_folder<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
path: PathBuf,
|
||||
) {
|
||||
if let Err(e) = app.opener().reveal_item_in_dir(path) {
|
||||
tracing::error!("Failed to highlight file in folder: {}", e);
|
||||
}
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
if let Err(e) = app.opener().reveal_item_in_dir(path) {
|
||||
tracing::error!("Failed to highlight file in folder: {}", e);
|
||||
}
|
||||
})
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -135,10 +131,12 @@ pub async fn open_path<R: Runtime>(app: tauri::AppHandle<R>, path: PathBuf) {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn show_launcher_logs_folder<R: Runtime>(app: tauri::AppHandle<R>) {
|
||||
let path = DirectoryInfo::launcher_logs_dir().unwrap_or_default();
|
||||
// failure to get folder just opens filesystem
|
||||
// (ie: if in debug mode only and launcher_logs never created)
|
||||
open_path(app, path).await;
|
||||
if let Some(d) = DirectoryInfo::global_handle_if_ready() {
|
||||
let path = d.launcher_logs_dir().unwrap_or_default();
|
||||
// failure to get folder just opens filesystem
|
||||
// (ie: if in debug mode only and launcher_logs never created)
|
||||
open_path(app, path).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Get opening command
|
||||
|
||||
@@ -151,12 +151,20 @@ pub async fn add_server_to_profile(
|
||||
name: String,
|
||||
address: String,
|
||||
pack_status: ServerPackStatus,
|
||||
project_id: Option<String>,
|
||||
content_kind: Option<String>,
|
||||
) -> Result<usize> {
|
||||
let path = get_full_path(path).await?;
|
||||
Ok(
|
||||
worlds::add_server_to_profile(&path, name, address, pack_status)
|
||||
.await?,
|
||||
let full_path = get_full_path(path).await?;
|
||||
Ok(worlds::add_server_to_profile(
|
||||
&full_path,
|
||||
path,
|
||||
name,
|
||||
address,
|
||||
pack_status,
|
||||
project_id,
|
||||
content_kind,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
+83
-20
@@ -6,7 +6,9 @@
|
||||
|
||||
use native_dialog::{DialogBuilder, MessageLevel};
|
||||
use std::env;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tauri::{Listener, Manager};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
use theseus::prelude::*;
|
||||
|
||||
mod api;
|
||||
@@ -27,14 +29,17 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||
tracing::info!("Initializing app event state...");
|
||||
theseus::EventState::init(app.clone()).await?;
|
||||
tracing::info!("Initializing app state...");
|
||||
State::init().await?;
|
||||
tracing::info!("AstralRinth state successfully initialized.");
|
||||
State::init(app.config().identifier.clone()).await?;
|
||||
|
||||
let state = State::get().await?;
|
||||
app.asset_protocol_scope()
|
||||
.allow_directory(state.directories.caches_dir(), true)?;
|
||||
app.asset_protocol_scope()
|
||||
.allow_directory(state.directories.caches_dir().join("icons"), true)?;
|
||||
app.fs_scope()
|
||||
.allow_directory(state.directories.profiles_dir(), true)?;
|
||||
|
||||
tracing::info!("AstralRinth state successfully initialized.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -92,6 +97,17 @@ fn restart_app(app: tauri::AppHandle) {
|
||||
app.restart();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn set_restart_after_pending_update(
|
||||
should_restart: bool,
|
||||
) -> api::Result<()> {
|
||||
let state = State::get().await?;
|
||||
state
|
||||
.restart_after_pending_update
|
||||
.store(should_restart, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// if Tauri app is called with arguments, then those arguments will be treated as commands
|
||||
// ie: deep links or filepaths for .mrpacks
|
||||
fn main() {
|
||||
@@ -109,7 +125,10 @@ fn main() {
|
||||
RUST_LOG="theseus=trace" {run command}
|
||||
|
||||
*/
|
||||
let _log_guard = theseus::start_logger();
|
||||
|
||||
let tauri_context = tauri::generate_context!();
|
||||
|
||||
let _log_guard = theseus::start_logger(&tauri_context.config().identifier);
|
||||
|
||||
tracing::info!("Initialized tracing subscriber. Loading AstralRinth App!");
|
||||
|
||||
@@ -133,6 +152,7 @@ fn main() {
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(
|
||||
tauri_plugin_window_state::Builder::default()
|
||||
@@ -223,6 +243,8 @@ fn main() {
|
||||
.plugin(api::tags::init())
|
||||
.plugin(api::utils::init())
|
||||
.plugin(api::cache::init())
|
||||
.plugin(api::files::init())
|
||||
// .plugin(api::ads::init())
|
||||
.plugin(api::friends::init())
|
||||
.plugin(api::worlds::init())
|
||||
.manage(PendingUpdateData::default())
|
||||
@@ -233,24 +255,41 @@ fn main() {
|
||||
get_update_size,
|
||||
enqueue_update_for_installation,
|
||||
remove_enqueued_update,
|
||||
set_restart_after_pending_update,
|
||||
toggle_decorations,
|
||||
show_window,
|
||||
restart_app,
|
||||
]);
|
||||
|
||||
tracing::info!("Initializing app...");
|
||||
let app = builder.build(tauri::generate_context!());
|
||||
let app = builder.build(tauri_context);
|
||||
|
||||
match app {
|
||||
Ok(app) => {
|
||||
app.run(|app, event| {
|
||||
#[cfg(not(any(feature = "updater", target_os = "macos")))]
|
||||
drop((app, event));
|
||||
let _ = app;
|
||||
|
||||
if matches!(&event, tauri::RunEvent::ExitRequested { .. })
|
||||
&& let Err(error) = tauri::async_runtime::block_on(
|
||||
theseus::minecraft_skins::flush_pending_skin_change(),
|
||||
)
|
||||
{
|
||||
tracing::warn!(
|
||||
"Failed to flush pending Minecraft skin change before exit: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "updater")]
|
||||
if matches!(event, tauri::RunEvent::Exit) {
|
||||
if matches!(&event, tauri::RunEvent::Exit) {
|
||||
let update_data = app.state::<PendingUpdateData>().inner();
|
||||
if let Some((update, data)) = &*update_data.0.lock().unwrap() {
|
||||
let should_restart = State::get_if_initialized()
|
||||
.map(|s| {
|
||||
s.restart_after_pending_update.load(Ordering::Relaxed)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if let Some((update, data)) = &*update_data.0.lock().unwrap()
|
||||
{
|
||||
fn set_changelog_toast(version: Option<String>) {
|
||||
let toast_result: theseus::Result<()> = tauri::async_runtime::block_on(async move {
|
||||
let mut settings = settings::get().await?;
|
||||
@@ -259,27 +298,51 @@ fn main() {
|
||||
Ok(())
|
||||
});
|
||||
if let Err(e) = toast_result {
|
||||
tracing::warn!("Failed to set pending_update_toast: {e}")
|
||||
tracing::warn!(
|
||||
"Failed to set pending_update_toast: {e}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
set_changelog_toast(Some(update.version.clone()));
|
||||
if let Err(e) = update.install(data) {
|
||||
tracing::error!("Error while updating: {e}");
|
||||
set_changelog_toast(None);
|
||||
let update = if should_restart {
|
||||
(**update).clone()
|
||||
} else {
|
||||
(**update).clone().restart_after_install(false)
|
||||
};
|
||||
match update.install(data) {
|
||||
Ok(()) => {
|
||||
if should_restart {
|
||||
tracing::info!(
|
||||
"Pending update installed successfully (version {}); restarting because user requested reload",
|
||||
update.version
|
||||
);
|
||||
app.restart();
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Pending update installed successfully (version {}); exiting without relaunch (user did not request reload)",
|
||||
update.version
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Pending update install failed (version {}): {e}",
|
||||
update.version
|
||||
);
|
||||
set_changelog_toast(None);
|
||||
|
||||
DialogBuilder::message()
|
||||
.set_level(MessageLevel::Error)
|
||||
.set_title("Update error")
|
||||
.set_text(format!("Failed to install update due to an error:\n{e}"))
|
||||
.alert()
|
||||
.show()
|
||||
.unwrap();
|
||||
DialogBuilder::message()
|
||||
.set_level(MessageLevel::Error)
|
||||
.set_title("Update error")
|
||||
.set_text(format!("Failed to install update due to an error:\n{e}"))
|
||||
.alert()
|
||||
.show()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
app.restart();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let tauri::RunEvent::Opened { urls } = event {
|
||||
tracing::info!("Handling webview open {urls:?}");
|
||||
|
||||
Reference in New Issue
Block a user