fmt clippy prettier
This commit is contained in:
parent
719aded698
commit
f9beea8ef2
@ -14,8 +14,8 @@ use crate::{
|
|||||||
/// (Does not include modrinth://)
|
/// (Does not include modrinth://)
|
||||||
pub async fn handle_url(sublink: &str) -> crate::Result<CommandPayload> {
|
pub async fn handle_url(sublink: &str) -> crate::Result<CommandPayload> {
|
||||||
Ok(match sublink.split_once('/') {
|
Ok(match sublink.split_once('/') {
|
||||||
Some(("shared_profile", link)) => {
|
Some(("shared_profile", link)) => CommandPayload::OpenSharedProfile {
|
||||||
CommandPayload::OpenSharedProfile { link: link.to_string() }
|
link: link.to_string(),
|
||||||
},
|
},
|
||||||
// /mod/{id} - Installs a mod of mod id
|
// /mod/{id} - Installs a mod of mod id
|
||||||
Some(("mod", id)) => CommandPayload::InstallMod { id: id.to_string() },
|
Some(("mod", id)) => CommandPayload::InstallMod { id: id.to_string() },
|
||||||
|
|||||||
@ -10,8 +10,8 @@ pub mod pack;
|
|||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod safety;
|
pub mod safety;
|
||||||
pub mod shared_profile;
|
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
pub mod shared_profile;
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
|
|
||||||
pub mod data {
|
pub mod data {
|
||||||
@ -30,7 +30,7 @@ pub mod prelude {
|
|||||||
event::CommandPayload,
|
event::CommandPayload,
|
||||||
jre, metadata, pack, process,
|
jre, metadata, pack, process,
|
||||||
profile::{self, create, Profile},
|
profile::{self, create, Profile},
|
||||||
settings,
|
settings, shared_profile,
|
||||||
state::JavaGlobals,
|
state::JavaGlobals,
|
||||||
state::{Dependency, ProfilePathId, ProjectPathId},
|
state::{Dependency, ProfilePathId, ProjectPathId},
|
||||||
util::{
|
util::{
|
||||||
@ -38,6 +38,5 @@ pub mod prelude {
|
|||||||
jre::JavaVersion,
|
jre::JavaVersion,
|
||||||
},
|
},
|
||||||
State,
|
State,
|
||||||
shared_profile,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -391,29 +391,30 @@ pub async fn set_profile_information(
|
|||||||
let project_id = description.project_id.clone();
|
let project_id = description.project_id.clone();
|
||||||
let version_id = description.version_id.clone();
|
let version_id = description.version_id.clone();
|
||||||
|
|
||||||
prof.metadata.linked_data = if project_id.is_some()
|
prof.metadata.linked_data =
|
||||||
&& version_id.is_some()
|
if project_id.is_some() && version_id.is_some() {
|
||||||
{
|
Some(LinkedData::ModrinthModpack {
|
||||||
Some(LinkedData::ModrinthModpack {
|
project_id,
|
||||||
project_id,
|
version_id,
|
||||||
version_id,
|
locked: if !ignore_lock {
|
||||||
locked: if !ignore_lock {
|
Some(true)
|
||||||
Some(true)
|
|
||||||
} else {
|
|
||||||
prof.metadata.linked_data.as_ref().and_then(|x| if let LinkedData::ModrinthModpack {
|
|
||||||
locked: Some(locked),
|
|
||||||
..
|
|
||||||
} = x
|
|
||||||
{
|
|
||||||
Some(*locked)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
prof.metadata.linked_data.as_ref().and_then(|x| {
|
||||||
})
|
if let LinkedData::ModrinthModpack {
|
||||||
},
|
locked: Some(locked),
|
||||||
})
|
..
|
||||||
} else {
|
} = x
|
||||||
None
|
{
|
||||||
};
|
Some(*locked)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
prof.metadata.icon = description.icon.clone();
|
prof.metadata.icon = description.icon.clone();
|
||||||
prof.metadata.game_version = game_version.clone();
|
prof.metadata.game_version = game_version.clone();
|
||||||
|
|||||||
@ -102,11 +102,14 @@ pub async fn profile_create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
profile.metadata.linked_data = linked_data;
|
profile.metadata.linked_data = linked_data;
|
||||||
if let Some(LinkedData::ModrinthModpack{ project_id, version_id, locked, .. }) = &mut profile.metadata.linked_data {
|
if let Some(LinkedData::ModrinthModpack {
|
||||||
*locked = Some(
|
project_id,
|
||||||
project_id.is_some()
|
version_id,
|
||||||
&& version_id.is_some(),
|
locked,
|
||||||
);
|
..
|
||||||
|
}) = &mut profile.metadata.linked_data
|
||||||
|
{
|
||||||
|
*locked = Some(project_id.is_some() && version_id.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
emit_profile(
|
emit_profile(
|
||||||
|
|||||||
@ -3,12 +3,12 @@
|
|||||||
use crate::event::emit::{
|
use crate::event::emit::{
|
||||||
emit_loading, init_loading, loading_try_for_each_concurrent,
|
emit_loading, init_loading, loading_try_for_each_concurrent,
|
||||||
};
|
};
|
||||||
use crate::state::LinkedData;
|
|
||||||
use crate::event::LoadingBarType;
|
use crate::event::LoadingBarType;
|
||||||
use crate::pack::install_from::{
|
use crate::pack::install_from::{
|
||||||
EnvType, PackDependency, PackFile, PackFileHash, PackFormat,
|
EnvType, PackDependency, PackFile, PackFileHash, PackFormat,
|
||||||
};
|
};
|
||||||
use crate::prelude::{JavaVersion, ProfilePathId, ProjectPathId};
|
use crate::prelude::{JavaVersion, ProfilePathId, ProjectPathId};
|
||||||
|
use crate::state::LinkedData;
|
||||||
use crate::state::{InnerProjectPathUnix, ProjectMetadata, SideType};
|
use crate::state::{InnerProjectPathUnix, ProjectMetadata, SideType};
|
||||||
|
|
||||||
use crate::util::fetch;
|
use crate::util::fetch;
|
||||||
@ -116,15 +116,14 @@ pub async fn get_mod_full_path(
|
|||||||
project_path: &ProjectPathId,
|
project_path: &ProjectPathId,
|
||||||
) -> crate::Result<PathBuf> {
|
) -> crate::Result<PathBuf> {
|
||||||
if get(profile_path, Some(true)).await?.is_some() {
|
if get(profile_path, Some(true)).await?.is_some() {
|
||||||
let full_path = io::canonicalize(
|
let full_path =
|
||||||
project_path.get_full_path(&profile_path).await?,
|
io::canonicalize(project_path.get_full_path(profile_path).await?)?;
|
||||||
)?;
|
|
||||||
return Ok(full_path);
|
return Ok(full_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(crate::ErrorKind::OtherError(format!(
|
Err(crate::ErrorKind::OtherError(format!(
|
||||||
"Tried to get the full path of a nonexistent or unloaded project at path {}!",
|
"Tried to get the full path of a nonexistent or unloaded project at path {}!",
|
||||||
project_path.get_full_path(&profile_path).await?.display()
|
project_path.get_full_path(profile_path).await?.display()
|
||||||
))
|
))
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
@ -873,13 +872,13 @@ pub async fn try_update_playtime(path: &ProfilePathId) -> crate::Result<()> {
|
|||||||
let res = if updated_recent_playtime > 0 {
|
let res = if updated_recent_playtime > 0 {
|
||||||
// Create update struct to send to Labrinth
|
// Create update struct to send to Labrinth
|
||||||
let modrinth_pack_version_id =
|
let modrinth_pack_version_id =
|
||||||
profile.metadata.linked_data.and_then(|l|
|
profile.metadata.linked_data.and_then(|l| {
|
||||||
if let LinkedData::ModrinthModpack {
|
if let LinkedData::ModrinthModpack { version_id, .. } = l {
|
||||||
version_id,
|
Some(version_id)
|
||||||
..
|
} else {
|
||||||
} = l {
|
None
|
||||||
Some(version_id)
|
}
|
||||||
} else { None });
|
});
|
||||||
let playtime_update_json = json!({
|
let playtime_update_json = json!({
|
||||||
"seconds": updated_recent_playtime,
|
"seconds": updated_recent_playtime,
|
||||||
"loader": profile.metadata.loader.to_string(),
|
"loader": profile.metadata.loader.to_string(),
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use crate::{
|
|||||||
pack::{self, install_from::generate_pack_from_version_id},
|
pack::{self, install_from::generate_pack_from_version_id},
|
||||||
prelude::{ProfilePathId, ProjectPathId},
|
prelude::{ProfilePathId, ProjectPathId},
|
||||||
profile::get,
|
profile::get,
|
||||||
state::{ProfileInstallStage, Project, LinkedData},
|
state::{LinkedData, ProfileInstallStage, Project},
|
||||||
LoadingBarType, State,
|
LoadingBarType, State,
|
||||||
};
|
};
|
||||||
use futures::try_join;
|
use futures::try_join;
|
||||||
@ -30,11 +30,12 @@ pub async fn update_managed_modrinth_version(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Extract modrinth pack information, if appropriate
|
// Extract modrinth pack information, if appropriate
|
||||||
let Some(LinkedData::ModrinthModpack{
|
let Some(LinkedData::ModrinthModpack {
|
||||||
project_id: Some(ref project_id),
|
project_id: Some(ref project_id),
|
||||||
version_id: Some(ref version_id),
|
version_id: Some(ref version_id),
|
||||||
..
|
..
|
||||||
}) = profile.metadata.linked_data else {
|
}) = profile.metadata.linked_data
|
||||||
|
else {
|
||||||
return Err(unmanaged_err().into());
|
return Err(unmanaged_err().into());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,11 +106,12 @@ pub async fn repair_managed_modrinth(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Extract modrinth pack information, if appropriate
|
// Extract modrinth pack information, if appropriate
|
||||||
let Some(LinkedData::ModrinthModpack{
|
let Some(LinkedData::ModrinthModpack {
|
||||||
project_id: Some(ref project_id),
|
project_id: Some(ref project_id),
|
||||||
version_id: Some(ref version_id),
|
version_id: Some(ref version_id),
|
||||||
..
|
..
|
||||||
}) = profile.metadata.linked_data else {
|
}) = profile.metadata.linked_data
|
||||||
|
else {
|
||||||
return Err(unmanaged_err().into());
|
return Err(unmanaged_err().into());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//! Theseus profile management interface
|
//! Theseus profile management interface
|
||||||
|
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{Path, PathBuf};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use io::IOError;
|
use io::IOError;
|
||||||
@ -10,7 +10,7 @@ use crate::{
|
|||||||
event::emit::{emit_loading, init_loading},
|
event::emit::{emit_loading, init_loading},
|
||||||
prelude::DirectoryInfo,
|
prelude::DirectoryInfo,
|
||||||
state::{self, Profiles},
|
state::{self, Profiles},
|
||||||
util::{io, fetch},
|
util::{fetch, io},
|
||||||
};
|
};
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
state::{
|
state::{
|
||||||
@ -109,7 +109,6 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
|||||||
let old_config_dir =
|
let old_config_dir =
|
||||||
state_write.directories.config_dir.read().await.clone();
|
state_write.directories.config_dir.read().await.clone();
|
||||||
|
|
||||||
|
|
||||||
// Reset file watcher
|
// Reset file watcher
|
||||||
tracing::trace!("Reset file watcher");
|
tracing::trace!("Reset file watcher");
|
||||||
let file_watcher = state::init_watcher().await?;
|
let file_watcher = state::init_watcher().await?;
|
||||||
@ -125,13 +124,17 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| IOError::with_path(e, &old_config_dir))?
|
.map_err(|e| IOError::with_path(e, &old_config_dir))?
|
||||||
{
|
{
|
||||||
|
|
||||||
let entry_path = entry.path();
|
let entry_path = entry.path();
|
||||||
if let Some(file_name) = entry_path.file_name() {
|
if let Some(file_name) = entry_path.file_name() {
|
||||||
// We are only moving the profiles and metadata folders
|
// We are only moving the profiles and metadata folders
|
||||||
if file_name == state::PROFILES_FOLDER_NAME || file_name == state::METADATA_FOLDER_NAME {
|
if file_name == state::PROFILES_FOLDER_NAME
|
||||||
|
|| file_name == state::METADATA_FOLDER_NAME
|
||||||
|
{
|
||||||
if across_drives {
|
if across_drives {
|
||||||
entries.extend(crate::pack::import::get_all_subfiles(&entry_path).await?);
|
entries.extend(
|
||||||
|
crate::pack::import::get_all_subfiles(&entry_path)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
deletable_entries.push(entry_path.clone());
|
deletable_entries.push(entry_path.clone());
|
||||||
} else {
|
} else {
|
||||||
entries.push(entry_path.clone());
|
entries.push(entry_path.clone());
|
||||||
@ -151,8 +154,7 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
io::rename(entry_path.clone(), new_path.clone()).await?;
|
io::rename(entry_path.clone(), new_path.clone()).await?;
|
||||||
}
|
}
|
||||||
emit_loading(&loading_bar, 80.0 * (1.0 / num_entries), None)
|
emit_loading(&loading_bar, 80.0 * (1.0 / num_entries), None).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::trace!("Setting configuration setting");
|
tracing::trace!("Setting configuration setting");
|
||||||
@ -199,7 +201,8 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
|||||||
&loading_bar,
|
&loading_bar,
|
||||||
10.0 * (1.0 / deletable_entries_len as f64),
|
10.0 * (1.0 / deletable_entries_len as f64),
|
||||||
None,
|
None,
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset file watcher
|
// Reset file watcher
|
||||||
@ -228,7 +231,6 @@ fn is_different_drive(path1: &Path, path2: &Path) -> bool {
|
|||||||
root1 != root2
|
root1 != root2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub async fn is_dir_writeable(new_config_dir: PathBuf) -> crate::Result<bool> {
|
pub async fn is_dir_writeable(new_config_dir: PathBuf) -> crate::Result<bool> {
|
||||||
let temp_path = new_config_dir.join(".tmp");
|
let temp_path = new_config_dir.join(".tmp");
|
||||||
match fs::write(temp_path.clone(), "test").await {
|
match fs::write(temp_path.clone(), "test").await {
|
||||||
|
|||||||
@ -1,9 +1,20 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use chrono::{DateTime,Utc};
|
use crate::{
|
||||||
|
config::MODRINTH_API_URL_INTERNAL,
|
||||||
|
prelude::{
|
||||||
|
LinkedData, ModLoader, ProfilePathId, ProjectMetadata, ProjectPathId,
|
||||||
|
},
|
||||||
|
profile,
|
||||||
|
state::{Profile, Profiles},
|
||||||
|
util::{
|
||||||
|
fetch::{fetch_advanced, REQWEST_CLIENT},
|
||||||
|
io,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::{config::MODRINTH_API_URL_INTERNAL, prelude::{LinkedData, ModLoader, ProfilePathId, ProjectMetadata, ProjectPathId}, profile, util::{fetch::{fetch_advanced, REQWEST_CLIENT}, io}, state::{Profile, Profiles}};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
pub struct SharedProfile {
|
pub struct SharedProfile {
|
||||||
@ -23,7 +34,7 @@ pub struct SharedProfile {
|
|||||||
pub overrides: Vec<SharedProfileOverride>,
|
pub overrides: Vec<SharedProfileOverride>,
|
||||||
|
|
||||||
pub share_links: Option<Vec<SharedProfileLink>>,
|
pub share_links: Option<Vec<SharedProfileLink>>,
|
||||||
pub users: Option<Vec<String>>
|
pub users: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
@ -49,23 +60,33 @@ pub struct SharedProfileOverrideHashes {
|
|||||||
// Create a new shared profile from ProfilePathId
|
// Create a new shared profile from ProfilePathId
|
||||||
// This converts the LinkedData to a SharedProfile and uploads it to the Labrinth API
|
// This converts the LinkedData to a SharedProfile and uploads it to the Labrinth API
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn create(
|
pub async fn create(profile_id: ProfilePathId) -> crate::Result<()> {
|
||||||
profile_id: ProfilePathId,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let state = crate::State::get().await?;
|
let state = crate::State::get().await?;
|
||||||
|
|
||||||
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
|
let profile: Profile =
|
||||||
|
profile::get(&profile_id, None).await?.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
|
||||||
|
})?;
|
||||||
let creds = state.credentials.read().await;
|
let creds = state.credentials.read().await;
|
||||||
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
let creds = creds
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
||||||
|
|
||||||
// Currently existing linked data should fail
|
// Currently existing linked data should fail
|
||||||
match profile.metadata.linked_data {
|
match profile.metadata.linked_data {
|
||||||
Some(LinkedData::SharedProfile { .. }) => {
|
Some(LinkedData::SharedProfile { .. }) => {
|
||||||
return Err(crate::ErrorKind::OtherError("Profile already linked to a shared profile".to_string()).as_error());
|
return Err(crate::ErrorKind::OtherError(
|
||||||
},
|
"Profile already linked to a shared profile".to_string(),
|
||||||
|
)
|
||||||
|
.as_error());
|
||||||
|
}
|
||||||
Some(LinkedData::ModrinthModpack { .. }) => {
|
Some(LinkedData::ModrinthModpack { .. }) => {
|
||||||
return Err(crate::ErrorKind::OtherError("Profile already linked to a modrinth project".to_string()).as_error());
|
return Err(crate::ErrorKind::OtherError(
|
||||||
},
|
"Profile already linked to a modrinth project".to_string(),
|
||||||
|
)
|
||||||
|
.as_error());
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -73,40 +94,59 @@ pub async fn create(
|
|||||||
let loader = profile.metadata.loader;
|
let loader = profile.metadata.loader;
|
||||||
let loader_version = profile.metadata.loader_version;
|
let loader_version = profile.metadata.loader_version;
|
||||||
let game_version = profile.metadata.game_version;
|
let game_version = profile.metadata.game_version;
|
||||||
|
|
||||||
let modrinth_projects : Vec<_> = profile.projects.iter()
|
|
||||||
.filter_map(|(_, project)|if let ProjectMetadata::Modrinth { ref version, .. } = project.metadata {
|
|
||||||
Some(&version.id)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
let override_files : Vec<_> = profile.projects.iter()
|
let modrinth_projects: Vec<_> = profile
|
||||||
.filter_map(|(id, project)|if let ProjectMetadata::Inferred { ..} = project.metadata {
|
.projects
|
||||||
Some(id)
|
.iter()
|
||||||
} else {
|
.filter_map(|(_, project)| {
|
||||||
None
|
if let ProjectMetadata::Modrinth { ref version, .. } =
|
||||||
}).collect();
|
project.metadata
|
||||||
|
{
|
||||||
|
Some(&version.id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let override_files: Vec<_> = profile
|
||||||
|
.projects
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(id, project)| {
|
||||||
|
if let ProjectMetadata::Inferred { .. } = project.metadata {
|
||||||
|
Some(id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Create the profile on the Labrinth API
|
// Create the profile on the Labrinth API
|
||||||
let response = REQWEST_CLIENT
|
let response = REQWEST_CLIENT
|
||||||
.post(
|
.post(format!("{MODRINTH_API_URL_INTERNAL}client/profile"))
|
||||||
format!("{MODRINTH_API_URL_INTERNAL}client/profile"),
|
.header("Authorization", &creds.session)
|
||||||
).header("Authorization", &creds.session)
|
.json(&serde_json::json!({
|
||||||
.json(&serde_json::json!({
|
"name": name,
|
||||||
"name": name,
|
"loader": loader.as_api_str(),
|
||||||
"loader": loader.as_api_str(),
|
"loader_version": loader_version.map(|x| x.id).unwrap_or_default(),
|
||||||
"loader_version": loader_version.map(|x| x.id).unwrap_or_default(),
|
"game": "minecraft-java",
|
||||||
"game": "minecraft-java",
|
"game_version": game_version,
|
||||||
"game_version": game_version,
|
"versions": modrinth_projects,
|
||||||
"versions": modrinth_projects,
|
}))
|
||||||
}))
|
.send()
|
||||||
.send().await?;
|
.await?;
|
||||||
|
|
||||||
let profile_response = response.json::<serde_json::Value>().await?;
|
let profile_response = response.json::<serde_json::Value>().await?;
|
||||||
|
|
||||||
// Extract the profile ID from the response
|
// Extract the profile ID from the response
|
||||||
let shared_profile_id = profile_response["id"].as_str().ok_or_else(|| crate::ErrorKind::OtherError("Could not parse response from Labrinth API".to_string()))?.to_string();
|
let shared_profile_id = profile_response["id"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::OtherError(
|
||||||
|
"Could not parse response from Labrinth API".to_string(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
// Unmanaged projects
|
// Unmanaged projects
|
||||||
let mut data = vec![]; // 'data' field, giving installation context to labrinth
|
let mut data = vec![]; // 'data' field, giving installation context to labrinth
|
||||||
@ -114,26 +154,39 @@ pub async fn create(
|
|||||||
|
|
||||||
for override_file in override_files {
|
for override_file in override_files {
|
||||||
let path = override_file.get_inner_path_unix();
|
let path = override_file.get_inner_path_unix();
|
||||||
let Some(name) = path.0.split('/').last().map(|x| x.to_string()) else { continue };
|
let Some(name) = path.0.split('/').last().map(|x| x.to_string()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
// Load override to file
|
// Load override to file
|
||||||
let full_path = &override_file.get_full_path(&profile_id).await?;
|
let full_path = &override_file.get_full_path(&profile_id).await?;
|
||||||
let file_bytes = io::read(full_path).await?;
|
let file_bytes = io::read(full_path).await?;
|
||||||
let ext = full_path.extension().and_then(|x| x.to_str()).unwrap_or_default();
|
let ext = full_path
|
||||||
let mime = project_file_type(ext).ok_or_else(|| crate::ErrorKind::OtherError(format!("Could not determine file type for {}", ext)))?;
|
.extension()
|
||||||
|
.and_then(|x| x.to_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mime = project_file_type(ext).ok_or_else(|| {
|
||||||
|
crate::ErrorKind::OtherError(format!(
|
||||||
|
"Could not determine file type for {}",
|
||||||
|
ext
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
data.push(serde_json::json!({
|
data.push(serde_json::json!({
|
||||||
"file_name": name.clone(),
|
"file_name": name.clone(),
|
||||||
"install_path": path
|
"install_path": path
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let part = reqwest::multipart::Part::bytes(file_bytes).file_name(name.clone()).mime_str(mime)?;
|
let part = reqwest::multipart::Part::bytes(file_bytes)
|
||||||
|
.file_name(name.clone())
|
||||||
|
.mime_str(mime)?;
|
||||||
parts.push((name.clone(), part));
|
parts.push((name.clone(), part));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build multipart with 'data' field first
|
// Build multipart with 'data' field first
|
||||||
let mut multipart = reqwest::multipart::Form::new().percent_encode_noop();
|
let mut multipart = reqwest::multipart::Form::new().percent_encode_noop();
|
||||||
let json_part = reqwest::multipart::Part::text(serde_json::to_string(&data)?);//mime_str("application/json")?;
|
let json_part =
|
||||||
|
reqwest::multipart::Part::text(serde_json::to_string(&data)?); //mime_str("application/json")?;
|
||||||
multipart = multipart.part("data", json_part);
|
multipart = multipart.part("data", json_part);
|
||||||
for (name, part) in parts {
|
for (name, part) in parts {
|
||||||
multipart = multipart.part(name, part);
|
multipart = multipart.part(name, part);
|
||||||
@ -154,7 +207,8 @@ pub async fn create(
|
|||||||
is_owner: true,
|
is_owner: true,
|
||||||
});
|
});
|
||||||
async { Ok(()) }
|
async { Ok(()) }
|
||||||
}).await?;
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Sync
|
// Sync
|
||||||
crate::State::sync().await?;
|
crate::State::sync().await?;
|
||||||
@ -175,7 +229,10 @@ pub fn project_file_type(ext: &str) -> Option<&str> {
|
|||||||
pub async fn get_all() -> crate::Result<Vec<SharedProfile>> {
|
pub async fn get_all() -> crate::Result<Vec<SharedProfile>> {
|
||||||
let state = crate::State::get().await?;
|
let state = crate::State::get().await?;
|
||||||
let creds = state.credentials.read().await;
|
let creds = state.credentials.read().await;
|
||||||
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
let creds = creds
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
||||||
|
|
||||||
// First, get list of shared profiles the user has access to
|
// First, get list of shared profiles the user has access to
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
@ -186,24 +243,24 @@ pub async fn get_all() -> crate::Result<Vec<SharedProfile>> {
|
|||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
pub updated: DateTime<Utc>,
|
pub updated: DateTime<Utc>,
|
||||||
pub icon_url: Option<String>,
|
pub icon_url: Option<String>,
|
||||||
|
|
||||||
pub loader: ModLoader,
|
pub loader: ModLoader,
|
||||||
pub game : String,
|
pub game: String,
|
||||||
|
|
||||||
pub loader_version: String,
|
pub loader_version: String,
|
||||||
pub game_version: String,
|
pub game_version: String,
|
||||||
|
|
||||||
// Present only if we are the owner
|
// Present only if we are the owner
|
||||||
pub share_links: Option<Vec<SharedProfileLink>>,
|
pub share_links: Option<Vec<SharedProfileLink>>,
|
||||||
pub users: Option<Vec<String>>,
|
pub users: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = REQWEST_CLIENT
|
let response = REQWEST_CLIENT
|
||||||
.get(
|
.get(format!("{MODRINTH_API_URL_INTERNAL}client/user"))
|
||||||
format!("{MODRINTH_API_URL_INTERNAL}client/user"),
|
.header("Authorization", &creds.session)
|
||||||
)
|
.send()
|
||||||
.header("Authorization", &creds.session)
|
.await?
|
||||||
.send().await?.error_for_status()?;
|
.error_for_status()?;
|
||||||
|
|
||||||
let profiles = response.json::<Vec<SharedProfileResponse>>().await?;
|
let profiles = response.json::<Vec<SharedProfileResponse>>().await?;
|
||||||
|
|
||||||
@ -223,18 +280,28 @@ pub async fn get_all() -> crate::Result<Vec<SharedProfile>> {
|
|||||||
|
|
||||||
let id = profile.id;
|
let id = profile.id;
|
||||||
let response = REQWEST_CLIENT
|
let response = REQWEST_CLIENT
|
||||||
.get(
|
.get(format!(
|
||||||
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{id}/files"),
|
"{MODRINTH_API_URL_INTERNAL}client/profile/{id}/files"
|
||||||
)
|
))
|
||||||
.header("Authorization", &creds.session)
|
.header("Authorization", &creds.session)
|
||||||
.send().await?.error_for_status()?;
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?;
|
||||||
|
|
||||||
let files = response.json::<SharedFiles>().await?;
|
let files = response.json::<SharedFiles>().await?;
|
||||||
|
|
||||||
shared_profiles.push(SharedProfile {
|
shared_profiles.push(SharedProfile {
|
||||||
id,
|
id,
|
||||||
name: profile.name,
|
name: profile.name,
|
||||||
is_owned: profile.owner_id == state.credentials.read().await.0.as_ref().map(|x| x.user.id.as_str()).unwrap_or_default(),
|
is_owned: profile.owner_id
|
||||||
|
== state
|
||||||
|
.credentials
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.user.id.as_str())
|
||||||
|
.unwrap_or_default(),
|
||||||
owner_id: profile.owner_id,
|
owner_id: profile.owner_id,
|
||||||
loader: profile.loader,
|
loader: profile.loader,
|
||||||
loader_version: profile.loader_version,
|
loader_version: profile.loader_version,
|
||||||
@ -253,7 +320,9 @@ pub async fn get_all() -> crate::Result<Vec<SharedProfile>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn install(shared_profile : SharedProfile) -> crate::Result<ProfilePathId> {
|
pub async fn install(
|
||||||
|
shared_profile: SharedProfile,
|
||||||
|
) -> crate::Result<ProfilePathId> {
|
||||||
let state = crate::State::get().await?;
|
let state = crate::State::get().await?;
|
||||||
|
|
||||||
let linked_data = LinkedData::SharedProfile {
|
let linked_data = LinkedData::SharedProfile {
|
||||||
@ -261,7 +330,7 @@ pub async fn install(shared_profile : SharedProfile) -> crate::Result<ProfilePat
|
|||||||
is_owner: shared_profile.is_owned,
|
is_owner: shared_profile.is_owned,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create new profile
|
// Create new profile
|
||||||
let profile_id = crate::profile::create::profile_create(
|
let profile_id = crate::profile::create::profile_create(
|
||||||
shared_profile.name,
|
shared_profile.name,
|
||||||
shared_profile.game_version,
|
shared_profile.game_version,
|
||||||
@ -272,10 +341,14 @@ pub async fn install(shared_profile : SharedProfile) -> crate::Result<ProfilePat
|
|||||||
Some(linked_data),
|
Some(linked_data),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Get the profile
|
// Get the profile
|
||||||
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
|
let profile: Profile =
|
||||||
|
profile::get(&profile_id, None).await?.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
|
||||||
|
})?;
|
||||||
let creds = state.credentials.read().await;
|
let creds = state.credentials.read().await;
|
||||||
|
|
||||||
// TODO: concurrent requests
|
// TODO: concurrent requests
|
||||||
@ -296,15 +369,15 @@ pub async fn install(shared_profile : SharedProfile) -> crate::Result<ProfilePat
|
|||||||
&creds,
|
&creds,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
profile.add_project_bytes_directly(&file_override.install_path, file).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
profile
|
||||||
|
.add_project_bytes_directly(&file_override.install_path, file)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(profile_id)
|
Ok(profile_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Structure repesenting a synchronization difference between a local profile and a shared profile
|
// Structure repesenting a synchronization difference between a local profile and a shared profile
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct SharedModpackFileUpdate {
|
pub struct SharedModpackFileUpdate {
|
||||||
@ -321,82 +394,116 @@ pub struct SharedModpackFileUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn check_updated(profile_id: &ProfilePathId, shared_profile : &SharedProfile) -> crate::Result<SharedModpackFileUpdate> {
|
pub async fn check_updated(
|
||||||
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
|
profile_id: &ProfilePathId,
|
||||||
|
shared_profile: &SharedProfile,
|
||||||
|
) -> crate::Result<SharedModpackFileUpdate> {
|
||||||
|
let profile: Profile =
|
||||||
|
profile::get(profile_id, None).await?.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
// Check if the metadata is the same- if different, we return false with no file updates
|
// Check if the metadata is the same- if different, we return false with no file updates
|
||||||
if profile.metadata.name != shared_profile.name ||
|
if profile.metadata.name != shared_profile.name
|
||||||
profile.metadata.loader != shared_profile.loader ||
|
|| profile.metadata.loader != shared_profile.loader
|
||||||
profile.metadata.loader_version.map(|x| x.id).unwrap_or_default() != shared_profile.loader_version ||
|
|| profile
|
||||||
profile.metadata.game_version != shared_profile.game_version {
|
.metadata
|
||||||
|
.loader_version
|
||||||
|
.map(|x| x.id)
|
||||||
|
.unwrap_or_default()
|
||||||
|
!= shared_profile.loader_version
|
||||||
|
|| profile.metadata.game_version != shared_profile.game_version
|
||||||
|
{
|
||||||
return Ok(SharedModpackFileUpdate::default());
|
return Ok(SharedModpackFileUpdate::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the projects are the same- we check each override by hash and each modrinth project by version id
|
// Check if the projects are the same- we check each override by hash and each modrinth project by version id
|
||||||
let mut modrinth_projects = shared_profile.versions.clone();
|
let mut modrinth_projects = shared_profile.versions.clone();
|
||||||
let mut overrides = shared_profile.overrides.clone();
|
let mut overrides = shared_profile.overrides.clone();
|
||||||
let unsynced_projects : Vec<_> = profile.projects.into_iter().filter_map(|(id, project)|{
|
let unsynced_projects: Vec<_> = profile
|
||||||
match project.metadata {
|
.projects
|
||||||
ProjectMetadata::Modrinth { ref version, .. } => {
|
.into_iter()
|
||||||
if modrinth_projects.contains(&version.id) {
|
.filter_map(|(id, project)| {
|
||||||
modrinth_projects.retain(|x| x != &version.id);
|
match project.metadata {
|
||||||
}
|
ProjectMetadata::Modrinth { ref version, .. } => {
|
||||||
else {
|
if modrinth_projects.contains(&version.id) {
|
||||||
return Some(id);
|
modrinth_projects.retain(|x| x != &version.id);
|
||||||
}
|
} else {
|
||||||
},
|
|
||||||
ProjectMetadata::Inferred { .. } => {
|
|
||||||
let Some(matching_override) = overrides.iter().position(|o| o.install_path.to_string_lossy().to_string() == id.get_inner_path_unix().0)
|
|
||||||
else {
|
|
||||||
return Some(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(o) = overrides.get(matching_override) {
|
|
||||||
if o.hashes.sha512 != project.sha512 {
|
|
||||||
return Some(id);
|
return Some(id);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
ProjectMetadata::Inferred { .. } => {
|
||||||
|
let Some(matching_override) =
|
||||||
|
overrides.iter().position(|o| {
|
||||||
|
o.install_path.to_string_lossy()
|
||||||
|
== id.get_inner_path_unix().0
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
return Some(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(o) = overrides.get(matching_override) {
|
||||||
|
if o.hashes.sha512 != project.sha512 {
|
||||||
|
return Some(id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Some(id);
|
||||||
|
}
|
||||||
|
overrides.remove(matching_override);
|
||||||
|
}
|
||||||
|
ProjectMetadata::Unknown => {
|
||||||
|
// TODO: What to do for unknown projects?
|
||||||
return Some(id);
|
return Some(id);
|
||||||
}
|
}
|
||||||
overrides.remove(matching_override);
|
|
||||||
}
|
}
|
||||||
ProjectMetadata::Unknown => {
|
None
|
||||||
// TODO: What to do for unknown projects?
|
})
|
||||||
return Some(id)
|
.collect();
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
Ok(SharedModpackFileUpdate {
|
Ok(SharedModpackFileUpdate {
|
||||||
is_synced: modrinth_projects.is_empty() && overrides.is_empty() && unsynced_projects.is_empty(),
|
is_synced: modrinth_projects.is_empty()
|
||||||
|
&& overrides.is_empty()
|
||||||
|
&& unsynced_projects.is_empty(),
|
||||||
unsynced_projects,
|
unsynced_projects,
|
||||||
missing_versions: modrinth_projects,
|
missing_versions: modrinth_projects,
|
||||||
missing_overrides: overrides,
|
missing_overrides: overrides,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates projects for a given ProfilePathId from a SharedProfile
|
// Updates projects for a given ProfilePathId from a SharedProfile
|
||||||
// This updates the local profile to match the shared profile on the Labrinth API
|
// This updates the local profile to match the shared profile on the Labrinth API
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn inbound_sync(
|
pub async fn inbound_sync(profile_id: ProfilePathId) -> crate::Result<()> {
|
||||||
profile_id: ProfilePathId,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let state = crate::State::get().await?;
|
let state = crate::State::get().await?;
|
||||||
|
|
||||||
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
|
let profile: Profile =
|
||||||
|
profile::get(&profile_id, None).await?.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
|
||||||
|
})?;
|
||||||
let creds = state.credentials.read().await;
|
let creds = state.credentials.read().await;
|
||||||
|
|
||||||
// Get linked
|
// Get linked
|
||||||
let shared_profile = match profile.metadata.linked_data {
|
let shared_profile = match profile.metadata.linked_data {
|
||||||
Some(LinkedData::SharedProfile { ref profile_id, .. }) => profile_id,
|
Some(LinkedData::SharedProfile { ref profile_id, .. }) => profile_id,
|
||||||
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
|
_ => {
|
||||||
|
return Err(crate::ErrorKind::OtherError(
|
||||||
|
"Profile is not linked to a shared profile".to_string(),
|
||||||
|
)
|
||||||
|
.as_error())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get updated shared profile
|
// Get updated shared profile
|
||||||
let shared_profile = get_all().await?.into_iter().find(|x| &x.id == shared_profile).ok_or_else(|| crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()))?;
|
let shared_profile = get_all()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.find(|x| &x.id == shared_profile)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::OtherError(
|
||||||
|
"Profile is not linked to a shared profile".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let update_data = check_updated(&profile_id, &shared_profile).await?;
|
let update_data = check_updated(&profile_id, &shared_profile).await?;
|
||||||
if update_data.is_synced {
|
if update_data.is_synced {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -425,8 +532,10 @@ pub async fn inbound_sync(
|
|||||||
&creds,
|
&creds,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
profile.add_project_bytes_directly(&file_override.install_path, file).await?;
|
profile
|
||||||
|
.add_project_bytes_directly(&file_override.install_path, file)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -435,27 +544,47 @@ pub async fn inbound_sync(
|
|||||||
// Updates metadata for a given ProfilePathId to the Labrinth API
|
// Updates metadata for a given ProfilePathId to the Labrinth API
|
||||||
// Must be an owner of the shared profile
|
// Must be an owner of the shared profile
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn outbound_sync(
|
pub async fn outbound_sync(profile_id: ProfilePathId) -> crate::Result<()> {
|
||||||
profile_id: ProfilePathId,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let state = crate::State::get().await?;
|
let state = crate::State::get().await?;
|
||||||
|
|
||||||
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
|
let profile: Profile =
|
||||||
|
profile::get(&profile_id, None).await?.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
|
||||||
|
})?;
|
||||||
let creds = state.credentials.read().await;
|
let creds = state.credentials.read().await;
|
||||||
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
let creds = creds
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
||||||
|
|
||||||
// Get linked
|
// Get linked
|
||||||
let shared_profile = match profile.metadata.linked_data {
|
let shared_profile = match profile.metadata.linked_data {
|
||||||
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
|
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
|
||||||
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
|
_ => {
|
||||||
|
return Err(crate::ErrorKind::OtherError(
|
||||||
|
"Profile is not linked to a shared profile".to_string(),
|
||||||
|
)
|
||||||
|
.as_error())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get updated shared profile
|
// Get updated shared profile
|
||||||
let shared_profile = get_all().await?.into_iter().find(|x| x.id == shared_profile).ok_or_else(|| crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()))?;
|
let shared_profile = get_all()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.find(|x| x.id == shared_profile)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::OtherError(
|
||||||
|
"Profile is not linked to a shared profile".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
// Check owner
|
// Check owner
|
||||||
if !shared_profile.is_owned {
|
if !shared_profile.is_owned {
|
||||||
return Err(crate::ErrorKind::OtherError("Profile is not owned by the current user".to_string()).as_error());
|
return Err(crate::ErrorKind::OtherError(
|
||||||
|
"Profile is not owned by the current user".to_string(),
|
||||||
|
)
|
||||||
|
.as_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we are synced
|
// Check if we are synced
|
||||||
@ -466,20 +595,35 @@ pub async fn outbound_sync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let unsynced = update_data.unsynced_projects;
|
let unsynced = update_data.unsynced_projects;
|
||||||
let projects : Vec<_> = profile.projects.clone().into_iter().filter(|(id, _)| unsynced.contains(id)).collect();
|
let projects: Vec<_> = profile
|
||||||
let unsynced_modrinth_projects : Vec<_> = projects.iter()
|
.projects
|
||||||
.filter_map(|(_, project)|if let ProjectMetadata::Modrinth { ref version, .. } = project.metadata {
|
.clone()
|
||||||
Some(&version.id)
|
.into_iter()
|
||||||
} else {
|
.filter(|(id, _)| unsynced.contains(id))
|
||||||
None
|
.collect();
|
||||||
}).collect();
|
let unsynced_modrinth_projects: Vec<_> = projects
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, project)| {
|
||||||
|
if let ProjectMetadata::Modrinth { ref version, .. } =
|
||||||
|
project.metadata
|
||||||
|
{
|
||||||
|
Some(&version.id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let unsynced_override_files : Vec<_> = projects.iter()
|
let unsynced_override_files: Vec<_> = projects
|
||||||
.filter_map(|(id, project)|if let ProjectMetadata::Inferred { ..} = project.metadata {
|
.iter()
|
||||||
Some(id)
|
.filter_map(|(id, project)| {
|
||||||
} else {
|
if let ProjectMetadata::Inferred { .. } = project.metadata {
|
||||||
None
|
Some(id)
|
||||||
}).collect();
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Generate new version set
|
// Generate new version set
|
||||||
let mut new_version_set = shared_profile.versions;
|
let mut new_version_set = shared_profile.versions;
|
||||||
@ -511,35 +655,49 @@ pub async fn outbound_sync(
|
|||||||
let mut data = vec![]; // 'data' field, giving installation context to labrinth
|
let mut data = vec![]; // 'data' field, giving installation context to labrinth
|
||||||
for override_file in unsynced_override_files {
|
for override_file in unsynced_override_files {
|
||||||
let path = override_file.get_inner_path_unix();
|
let path = override_file.get_inner_path_unix();
|
||||||
let Some(name) = path.0.split('/').last().map(|x| x.to_string()) else { continue };
|
let Some(name) = path.0.split('/').last().map(|x| x.to_string()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
// Load override to file
|
// Load override to file
|
||||||
let full_path = &override_file.get_full_path(&profile_id).await?;
|
let full_path = &override_file.get_full_path(&profile_id).await?;
|
||||||
let file_bytes = io::read(full_path).await?;
|
let file_bytes = io::read(full_path).await?;
|
||||||
let ext = full_path.extension().and_then(|x| x.to_str()).unwrap_or_default();
|
let ext = full_path
|
||||||
let mime = project_file_type(ext).ok_or_else(|| crate::ErrorKind::OtherError(format!("Could not determine file type for {}", ext)))?;
|
.extension()
|
||||||
|
.and_then(|x| x.to_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mime = project_file_type(ext).ok_or_else(|| {
|
||||||
|
crate::ErrorKind::OtherError(format!(
|
||||||
|
"Could not determine file type for {}",
|
||||||
|
ext
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
data.push(serde_json::json!({
|
data.push(serde_json::json!({
|
||||||
"file_name": name.clone(),
|
"file_name": name.clone(),
|
||||||
"install_path": path
|
"install_path": path
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let part = reqwest::multipart::Part::bytes(file_bytes).file_name(name.clone()).mime_str(mime)?;
|
let part = reqwest::multipart::Part::bytes(file_bytes)
|
||||||
|
.file_name(name.clone())
|
||||||
|
.mime_str(mime)?;
|
||||||
parts.push((name.clone(), part));
|
parts.push((name.clone(), part));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build multipart with 'data' field first
|
// Build multipart with 'data' field first
|
||||||
let mut multipart = reqwest::multipart::Form::new().percent_encode_noop();
|
let mut multipart = reqwest::multipart::Form::new().percent_encode_noop();
|
||||||
let json_part = reqwest::multipart::Part::text(serde_json::to_string(&data)?);//mime_str("application/json")?;
|
let json_part =
|
||||||
|
reqwest::multipart::Part::text(serde_json::to_string(&data)?); //mime_str("application/json")?;
|
||||||
multipart = multipart.part("data", json_part);
|
multipart = multipart.part("data", json_part);
|
||||||
for (name, part) in parts {
|
for (name, part) in parts {
|
||||||
multipart = multipart.part(name, part);
|
multipart = multipart.part(name, part);
|
||||||
}
|
}
|
||||||
let response = REQWEST_CLIENT.post(
|
let response = REQWEST_CLIENT
|
||||||
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{id}/override"),
|
.post(format!(
|
||||||
)
|
"{MODRINTH_API_URL_INTERNAL}client/profile/{id}/override"
|
||||||
.header("Authorization", &creds.session)
|
))
|
||||||
.multipart(multipart);
|
.header("Authorization", &creds.session)
|
||||||
|
.multipart(multipart);
|
||||||
|
|
||||||
response.send().await?.error_for_status()?;
|
response.send().await?.error_for_status()?;
|
||||||
|
|
||||||
@ -555,25 +713,38 @@ pub async fn remove_shared_profile_users(
|
|||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let state = crate::State::get().await?;
|
let state = crate::State::get().await?;
|
||||||
|
|
||||||
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
|
let profile: Profile =
|
||||||
|
profile::get(&profile_id, None).await?.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
|
||||||
|
})?;
|
||||||
let creds = state.credentials.read().await;
|
let creds = state.credentials.read().await;
|
||||||
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
let creds = creds
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
||||||
|
|
||||||
let shared_profile = match profile.metadata.linked_data {
|
let shared_profile = match profile.metadata.linked_data {
|
||||||
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
|
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
|
||||||
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
|
_ => {
|
||||||
|
return Err(crate::ErrorKind::OtherError(
|
||||||
|
"Profile is not linked to a shared profile".to_string(),
|
||||||
|
)
|
||||||
|
.as_error())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
REQWEST_CLIENT
|
REQWEST_CLIENT
|
||||||
.patch(
|
.patch(format!(
|
||||||
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}"),
|
"{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}"
|
||||||
)
|
))
|
||||||
.header("Authorization", &creds.session)
|
.header("Authorization", &creds.session)
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"remove_users": users,
|
"remove_users": users,
|
||||||
}))
|
}))
|
||||||
.send().await?.error_for_status()?;
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,24 +754,37 @@ pub async fn remove_shared_profile_links(
|
|||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let state = crate::State::get().await?;
|
let state = crate::State::get().await?;
|
||||||
|
|
||||||
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
|
let profile: Profile =
|
||||||
|
profile::get(&profile_id, None).await?.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
|
||||||
|
})?;
|
||||||
let creds = state.credentials.read().await;
|
let creds = state.credentials.read().await;
|
||||||
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
let creds = creds
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
||||||
|
|
||||||
let shared_profile = match profile.metadata.linked_data {
|
let shared_profile = match profile.metadata.linked_data {
|
||||||
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
|
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
|
||||||
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
|
_ => {
|
||||||
|
return Err(crate::ErrorKind::OtherError(
|
||||||
|
"Profile is not linked to a shared profile".to_string(),
|
||||||
|
)
|
||||||
|
.as_error())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
REQWEST_CLIENT
|
REQWEST_CLIENT
|
||||||
.patch(
|
.patch(format!(
|
||||||
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}"),
|
"{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}"
|
||||||
)
|
))
|
||||||
.header("Authorization", &creds.session)
|
.header("Authorization", &creds.session)
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
"remove_links": links,
|
"remove_links": links,
|
||||||
}))
|
}))
|
||||||
.send().await?.error_for_status()?;
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -610,47 +794,61 @@ pub async fn generate_share_link(
|
|||||||
) -> crate::Result<String> {
|
) -> crate::Result<String> {
|
||||||
let state = crate::State::get().await?;
|
let state = crate::State::get().await?;
|
||||||
|
|
||||||
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
|
let profile: Profile =
|
||||||
|
profile::get(&profile_id, None).await?.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
|
||||||
|
})?;
|
||||||
let creds = state.credentials.read().await;
|
let creds = state.credentials.read().await;
|
||||||
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
let creds = creds
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
||||||
|
|
||||||
let shared_profile = match profile.metadata.linked_data {
|
let shared_profile = match profile.metadata.linked_data {
|
||||||
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
|
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
|
||||||
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
|
_ => {
|
||||||
|
return Err(crate::ErrorKind::OtherError(
|
||||||
|
"Profile is not linked to a shared profile".to_string(),
|
||||||
|
)
|
||||||
|
.as_error())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = REQWEST_CLIENT
|
let response = REQWEST_CLIENT
|
||||||
.post(
|
.post(format!(
|
||||||
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}/share"),
|
"{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}/share"
|
||||||
)
|
))
|
||||||
.header("Authorization", &creds.session)
|
.header("Authorization", &creds.session)
|
||||||
.send().await?.error_for_status()?;
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?;
|
||||||
|
|
||||||
let link = response.json::<SharedProfileLink>().await?;
|
let link = response.json::<SharedProfileLink>().await?;
|
||||||
|
|
||||||
Ok(generate_deep_link(&link))
|
Ok(generate_deep_link(&link))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_deep_link(
|
fn generate_deep_link(link: &SharedProfileLink) -> String {
|
||||||
link: &SharedProfileLink
|
|
||||||
) -> String {
|
|
||||||
format!("modrinth://shared_profile/{}", link.id)
|
format!("modrinth://shared_profile/{}", link.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn accept_share_link(
|
pub async fn accept_share_link(link: String) -> crate::Result<()> {
|
||||||
link: String,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let state = crate::State::get().await?;
|
let state = crate::State::get().await?;
|
||||||
|
|
||||||
let creds = state.credentials.read().await;
|
let creds = state.credentials.read().await;
|
||||||
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
let creds = creds
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
|
||||||
|
|
||||||
REQWEST_CLIENT
|
REQWEST_CLIENT
|
||||||
.post(
|
.post(format!(
|
||||||
format!("{MODRINTH_API_URL_INTERNAL}client/profile/share/{link}/accept"),
|
"{MODRINTH_API_URL_INTERNAL}client/profile/share/{link}/accept"
|
||||||
)
|
))
|
||||||
.header("Authorization", &creds.session)
|
.header("Authorization", &creds.session)
|
||||||
.send().await?.error_for_status()?;
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
//! Configuration structs
|
//! Configuration structs
|
||||||
|
|
||||||
pub const MODRINTH_API_URL: &str = "https://staging-api.modrinth.com/v2/";
|
pub const MODRINTH_API_URL: &str = "https://staging-api.modrinth.com/v2/";
|
||||||
pub const MODRINTH_API_URL_INTERNAL: &str = "https://staging-api.modrinth.com/_internal/";
|
pub const MODRINTH_API_URL_INTERNAL: &str =
|
||||||
|
"https://staging-api.modrinth.com/_internal/";
|
||||||
|
|||||||
@ -606,21 +606,18 @@ impl Profile {
|
|||||||
relative_path: &Path,
|
relative_path: &Path,
|
||||||
bytes: bytes::Bytes,
|
bytes: bytes::Bytes,
|
||||||
) -> crate::Result<ProjectPathId> {
|
) -> crate::Result<ProjectPathId> {
|
||||||
|
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
let file_path = self
|
let file_path = self.get_profile_full_path().await?.join(relative_path);
|
||||||
.get_profile_full_path()
|
let project_path_id = ProjectPathId::new(relative_path);
|
||||||
.await?
|
|
||||||
.join(relative_path);
|
|
||||||
let project_path_id = ProjectPathId::new(&relative_path);
|
|
||||||
write(&file_path, &bytes, &state.io_semaphore).await?;
|
write(&file_path, &bytes, &state.io_semaphore).await?;
|
||||||
|
|
||||||
let file_name = relative_path
|
let file_name = relative_path
|
||||||
.file_name()
|
.file_name()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
crate::ErrorKind::InputError(
|
crate::ErrorKind::InputError(format!(
|
||||||
format!("Could not find file name for {:?}", relative_path),
|
"Could not find file name for {:?}",
|
||||||
)
|
relative_path
|
||||||
|
))
|
||||||
})?
|
})?
|
||||||
.to_string_lossy();
|
.to_string_lossy();
|
||||||
|
|
||||||
@ -945,13 +942,15 @@ impl Profiles {
|
|||||||
{
|
{
|
||||||
let profiles = state.profiles.read().await;
|
let profiles = state.profiles.read().await;
|
||||||
for (profile_path, profile) in profiles.0.iter() {
|
for (profile_path, profile) in profiles.0.iter() {
|
||||||
if let Some(LinkedData::ModrinthModpack { project_id, .. }) = &profile.metadata.linked_data {
|
if let Some(LinkedData::ModrinthModpack {
|
||||||
if let Some(linked_project) = &project_id {
|
project_id: Some(ref linked_project),
|
||||||
modrinth_updatables.push((
|
..
|
||||||
profile_path.clone(),
|
}) = &profile.metadata.linked_data
|
||||||
linked_project.clone(),
|
{
|
||||||
));
|
modrinth_updatables.push((
|
||||||
}
|
profile_path.clone(),
|
||||||
|
linked_project.clone(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -990,7 +989,9 @@ impl Profiles {
|
|||||||
});
|
});
|
||||||
if let Some(recent_version) = recent_version {
|
if let Some(recent_version) = recent_version {
|
||||||
profile.sync_update_version =
|
profile.sync_update_version =
|
||||||
Some(ProfileUpdateData::ModrinthModpack(recent_version.id.clone()));
|
Some(ProfileUpdateData::ModrinthModpack(
|
||||||
|
recent_version.id.clone(),
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
profile.sync_update_version = None;
|
profile.sync_update_version = None;
|
||||||
}
|
}
|
||||||
@ -1022,7 +1023,7 @@ impl Profiles {
|
|||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
#[theseus_macros::debug_pin]
|
#[theseus_macros::debug_pin]
|
||||||
pub async fn update_shared_projects() {
|
pub async fn update_shared_projects() {
|
||||||
let res = async {
|
let res = async {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
// Next, get updates for all shared profiles
|
// Next, get updates for all shared profiles
|
||||||
@ -1032,20 +1033,33 @@ impl Profiles {
|
|||||||
{
|
{
|
||||||
let profiles = state.profiles.read().await;
|
let profiles = state.profiles.read().await;
|
||||||
for (profile_path, profile) in profiles.0.iter() {
|
for (profile_path, profile) in profiles.0.iter() {
|
||||||
if let Some(LinkedData::SharedProfile { profile_id, .. }) = &profile.metadata.linked_data {
|
if let Some(LinkedData::SharedProfile {
|
||||||
if let Some(shared_profile) = shared_profiles.iter().find(|x| x.id == *profile_id) {
|
profile_id, ..
|
||||||
|
}) = &profile.metadata.linked_data
|
||||||
|
{
|
||||||
|
if let Some(shared_profile) =
|
||||||
|
shared_profiles.iter().find(|x| x.id == *profile_id)
|
||||||
|
{
|
||||||
// Check for update
|
// Check for update
|
||||||
let update = shared_profile::check_updated(profile_path, shared_profile).await?;
|
let update = shared_profile::check_updated(
|
||||||
update_profiles.insert(profile_path.clone(), update);
|
profile_path,
|
||||||
|
shared_profile,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
update_profiles
|
||||||
|
.insert(profile_path.clone(), update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let mut new_profiles = state.profiles.write().await;
|
let mut new_profiles = state.profiles.write().await;
|
||||||
for (profile_path, update) in update_profiles.iter() {
|
for (profile_path, update) in update_profiles.iter() {
|
||||||
if let Some(profile) = new_profiles.0.get_mut(&profile_path) {
|
if let Some(profile) = new_profiles.0.get_mut(profile_path)
|
||||||
profile.sync_update_version = Some(ProfileUpdateData::SharedProfile(update.clone()));
|
{
|
||||||
|
profile.sync_update_version = Some(
|
||||||
|
ProfileUpdateData::SharedProfile(update.clone()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1054,10 +1068,12 @@ impl Profiles {
|
|||||||
profiles.sync().await?;
|
profiles.sync().await?;
|
||||||
|
|
||||||
for (profile_path, _) in update_profiles.iter() {
|
for (profile_path, _) in update_profiles.iter() {
|
||||||
let Some(profile) = profiles.0.get(&profile_path) else { continue; };
|
let Some(profile) = profiles.0.get(profile_path) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
emit_profile(
|
emit_profile(
|
||||||
profile.uuid,
|
profile.uuid,
|
||||||
&profile_path,
|
profile_path,
|
||||||
&profile.metadata.name,
|
&profile.metadata.name,
|
||||||
ProfilePayloadType::Edited,
|
ProfilePayloadType::Edited,
|
||||||
)
|
)
|
||||||
@ -1065,13 +1081,14 @@ impl Profiles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok::<(), crate::Error>(())
|
Ok::<(), crate::Error>(())
|
||||||
}.await;
|
}
|
||||||
|
.await;
|
||||||
match res {
|
match res {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!("Unable to update modrinth versions: {err}")
|
tracing::warn!("Unable to update modrinth versions: {err}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self, profile))]
|
#[tracing::instrument(skip(self, profile))]
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
// IO error
|
// IO error
|
||||||
// A wrapper around the tokio IO functions that adds the path to the error message, instead of the uninformative std::io::Error.
|
// A wrapper around the tokio IO functions that adds the path to the error message, instead of the uninformative std::io::Error.
|
||||||
|
|
||||||
use std::{path::Path, io::Write};
|
use std::{io::Write, path::Path};
|
||||||
|
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
use tauri::async_runtime::spawn_blocking;
|
use tauri::async_runtime::spawn_blocking;
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum IOError {
|
pub enum IOError {
|
||||||
@ -137,12 +137,13 @@ fn sync_write(
|
|||||||
data: impl AsRef<[u8]>,
|
data: impl AsRef<[u8]>,
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
) -> Result<(), std::io::Error> {
|
) -> Result<(), std::io::Error> {
|
||||||
let mut tempfile = NamedTempFile::new_in(path.as_ref().parent().ok_or_else(|| {
|
let mut tempfile =
|
||||||
std::io::Error::new(
|
NamedTempFile::new_in(path.as_ref().parent().ok_or_else(|| {
|
||||||
std::io::ErrorKind::Other,
|
std::io::Error::new(
|
||||||
"could not get parent directory for temporary file",
|
std::io::ErrorKind::Other,
|
||||||
)
|
"could not get parent directory for temporary file",
|
||||||
})?)?;
|
)
|
||||||
|
})?)?;
|
||||||
tempfile.write_all(data.as_ref())?;
|
tempfile.write_all(data.as_ref())?;
|
||||||
let tmp_path = tempfile.into_temp_path();
|
let tmp_path = tempfile.into_temp_path();
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|||||||
@ -12,10 +12,10 @@ pub mod pack;
|
|||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod profile_create;
|
pub mod profile_create;
|
||||||
|
pub mod profile_share;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod profile_share;
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
|
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
|
||||||
|
|
||||||
|
|||||||
@ -19,10 +19,8 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
|||||||
|
|
||||||
// invoke('plugin:profile_share|profile_share_get_all',profile)
|
// invoke('plugin:profile_share|profile_share_get_all',profile)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn profile_share_get_all(
|
pub async fn profile_share_get_all() -> Result<Vec<SharedProfile>> {
|
||||||
) -> Result<Vec<SharedProfile>> {
|
let res = shared_profile::get_all().await?;
|
||||||
let res = shared_profile::get_all()
|
|
||||||
.await?;
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,57 +28,46 @@ pub async fn profile_share_get_all(
|
|||||||
pub async fn profile_share_install(
|
pub async fn profile_share_install(
|
||||||
profile: SharedProfile,
|
profile: SharedProfile,
|
||||||
) -> Result<ProfilePathId> {
|
) -> Result<ProfilePathId> {
|
||||||
let res = shared_profile::install(profile)
|
let res = shared_profile::install(profile).await?;
|
||||||
.await?;
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn profile_share_create(
|
pub async fn profile_share_create(path: ProfilePathId) -> Result<()> {
|
||||||
path: ProfilePathId
|
shared_profile::create(path).await?;
|
||||||
) -> Result<()> {
|
|
||||||
shared_profile::create(path)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn profile_share_inbound_sync(
|
|
||||||
path: ProfilePathId
|
|
||||||
) -> Result<()> {
|
|
||||||
shared_profile::inbound_sync(path)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn profile_share_outbound_sync(
|
pub async fn profile_share_inbound_sync(path: ProfilePathId) -> Result<()> {
|
||||||
path : ProfilePathId
|
shared_profile::inbound_sync(path).await?;
|
||||||
) -> Result<()> {
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn profile_share_outbound_sync(path: ProfilePathId) -> Result<()> {
|
||||||
shared_profile::outbound_sync(path).await?;
|
shared_profile::outbound_sync(path).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn profile_share_generate_share_link(
|
pub async fn profile_share_generate_share_link(
|
||||||
path : ProfilePathId
|
path: ProfilePathId,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let res = shared_profile::generate_share_link(path).await?;
|
let res = shared_profile::generate_share_link(path).await?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn profile_share_accept_share_link(
|
pub async fn profile_share_accept_share_link(link: String) -> Result<()> {
|
||||||
link : String
|
|
||||||
) -> Result<()> {
|
|
||||||
shared_profile::accept_share_link(link).await?;
|
shared_profile::accept_share_link(link).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn profile_share_remove_users(
|
pub async fn profile_share_remove_users(
|
||||||
path : ProfilePathId,
|
path: ProfilePathId,
|
||||||
users: Vec<String>
|
users: Vec<String>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
shared_profile::remove_shared_profile_users(path, users).await?;
|
shared_profile::remove_shared_profile_users(path, users).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -88,9 +75,9 @@ pub async fn profile_share_remove_users(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn profile_share_remove_links(
|
pub async fn profile_share_remove_links(
|
||||||
path : ProfilePathId,
|
path: ProfilePathId,
|
||||||
links : Vec<String>
|
links: Vec<String>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
shared_profile::remove_shared_profile_links(path, links).await?;
|
shared_profile::remove_shared_profile_links(path, links).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,9 +14,9 @@ defineExpose({
|
|||||||
async show(event) {
|
async show(event) {
|
||||||
linkId.value = event.id
|
linkId.value = event.id
|
||||||
sharedProfile.value = await useFetch(
|
sharedProfile.value = await useFetch(
|
||||||
`https://staging-api.modrinth.com/_internal/share/${encodeURIComponent(event.id)}`,
|
`https://staging-api.modrinth.com/_internal/share/${encodeURIComponent(event.id)}`,
|
||||||
'shared profile'
|
'shared profile'
|
||||||
)
|
)
|
||||||
|
|
||||||
confirmModal.value.show()
|
confirmModal.value.show()
|
||||||
},
|
},
|
||||||
|
|||||||
@ -82,7 +82,9 @@ const install = async (e) => {
|
|||||||
packs.length === 0 ||
|
packs.length === 0 ||
|
||||||
!packs
|
!packs
|
||||||
.map((value) => value.metadata)
|
.map((value) => value.metadata)
|
||||||
.find((pack) => pack.linked_data?.modrinth_modpack?.project_id === props.instance.project_id)
|
.find(
|
||||||
|
(pack) => pack.linked_data?.modrinth_modpack?.project_id === props.instance.project_id
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
modLoading.value = true
|
modLoading.value = true
|
||||||
await pack_install(
|
await pack_install(
|
||||||
|
|||||||
@ -247,9 +247,9 @@ const check_valid = computed(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
<div
|
<div
|
||||||
v-tooltip="
|
v-tooltip="
|
||||||
(profile.metadata.linked_data?.modrinth_modpack.locked
|
(profile.metadata.linked_data?.modrinth_modpack.locked ||
|
||||||
|| profile.metadata.linked_data?.shared_profile
|
profile.metadata.linked_data?.shared_profile) &&
|
||||||
) && !profile.installedMod
|
!profile.installedMod
|
||||||
? 'Unpair or unlock an instance to add mods.'
|
? 'Unpair or unlock an instance to add mods.'
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
@ -267,7 +267,8 @@ const check_valid = computed(() => {
|
|||||||
? 'Installing...'
|
? 'Installing...'
|
||||||
: profile.installedMod
|
: profile.installedMod
|
||||||
? 'Installed'
|
? 'Installed'
|
||||||
: profile.metadata.linked_data?.modrinth_modpack.locked || profile.metadata.linked_data?.shared_profile
|
: profile.metadata.linked_data?.modrinth_modpack.locked ||
|
||||||
|
profile.metadata.linked_data?.shared_profile
|
||||||
? 'Paired'
|
? 'Paired'
|
||||||
: 'Install'
|
: 'Install'
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -28,7 +28,9 @@ const filteredVersions = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const modpackVersionModal = ref(null)
|
const modpackVersionModal = ref(null)
|
||||||
const installedVersion = computed(() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id)
|
const installedVersion = computed(
|
||||||
|
() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id
|
||||||
|
)
|
||||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||||
const inProgress = ref(false)
|
const inProgress = ref(false)
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,9 @@ defineExpose({
|
|||||||
'version'
|
'version'
|
||||||
)
|
)
|
||||||
project.value = await useFetch(
|
project.value = await useFetch(
|
||||||
`https://staging-api.modrinth.com/v2/project/${encodeURIComponent(version.value.project_id)}`,
|
`https://staging-api.modrinth.com/v2/project/${encodeURIComponent(
|
||||||
|
version.value.project_id
|
||||||
|
)}`,
|
||||||
'project'
|
'project'
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -33,7 +35,9 @@ defineExpose({
|
|||||||
'project'
|
'project'
|
||||||
)
|
)
|
||||||
version.value = await useFetch(
|
version.value = await useFetch(
|
||||||
`https://staging-api.modrinth.com/v2/version/${encodeURIComponent(project.value.versions[0])}`,
|
`https://staging-api.modrinth.com/v2/version/${encodeURIComponent(
|
||||||
|
project.value.versions[0]
|
||||||
|
)}`,
|
||||||
'version'
|
'version'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,4 +49,4 @@ export async function inbound_sync(path) {
|
|||||||
// only allowed if profile is owned by user
|
// only allowed if profile is owned by user
|
||||||
export async function outbound_sync(path) {
|
export async function outbound_sync(path) {
|
||||||
return await invoke('plugin:profile_share|profile_share_outbound_sync', { path })
|
return await invoke('plugin:profile_share|profile_share_outbound_sync', { path })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,9 @@ const getInstances = async () => {
|
|||||||
let filters = []
|
let filters = []
|
||||||
for (const instance of recentInstances.value) {
|
for (const instance of recentInstances.value) {
|
||||||
if (instance.metadata.linked_data?.modrinth_modpack?.project_id) {
|
if (instance.metadata.linked_data?.modrinth_modpack?.project_id) {
|
||||||
filters.push(`NOT"project_id"="${instance.metadata.linked_data?.modrinth_modpack?.project_id}"`)
|
filters.push(
|
||||||
|
`NOT"project_id"="${instance.metadata.linked_data?.modrinth_modpack?.project_id}"`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filter.value = filters.join(' AND ')
|
filter.value = filters.join(' AND ')
|
||||||
|
|||||||
@ -413,7 +413,10 @@
|
|||||||
<XIcon /> Unpair
|
<XIcon /> Unpair
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.instance.metadata.linked_data?.modrinth_modpack?.project_id" class="adjacent-input">
|
<div
|
||||||
|
v-if="props.instance.metadata.linked_data?.modrinth_modpack?.project_id"
|
||||||
|
class="adjacent-input"
|
||||||
|
>
|
||||||
<label for="change-modpack-version">
|
<label for="change-modpack-version">
|
||||||
<span class="label__title">Change modpack version</span>
|
<span class="label__title">Change modpack version</span>
|
||||||
<span class="label__description">
|
<span class="label__description">
|
||||||
@ -452,14 +455,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="installedSharedProfileData.is_owned" class="adjacent-input">
|
<div v-if="installedSharedProfileData.is_owned" class="adjacent-input">
|
||||||
<label for="share-links">
|
<label for="share-links">
|
||||||
<span class="label__title">Generate share link</span>
|
<span class="label__title">Generate share link</span>
|
||||||
<span class="label__description">
|
<span class="label__description">
|
||||||
Creates a share link to share this modpack with others. This allows them to install your instance, as well as stay up to date with any changes you make.
|
Creates a share link to share this modpack with others. This allows them to install your
|
||||||
</span>
|
instance, as well as stay up to date with any changes you make.
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<Button id="share-links" @click="generateShareLink">
|
<Button id="share-links" @click="generateShareLink"> <GlobeIcon /> Share </Button>
|
||||||
<GlobeIcon /> Share
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shareLink" class="adjacent-input">
|
<div v-if="shareLink" class="adjacent-input">
|
||||||
Generated link: <code>{{ shareLink }}</code>
|
Generated link: <code>{{ shareLink }}</code>
|
||||||
@ -467,16 +469,10 @@
|
|||||||
<div v-if="installedSharedProfileData.is_owned" class="table">
|
<div v-if="installedSharedProfileData.is_owned" class="table">
|
||||||
<div class="table-row table-head">
|
<div class="table-row table-head">
|
||||||
<div class="table-cell table-text name-cell actions-cell">
|
<div class="table-cell table-text name-cell actions-cell">
|
||||||
<Button class="transparent">
|
<Button class="transparent"> Name </Button>
|
||||||
Name
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-for="user in installedSharedProfileData.users" :key="user" class="table-row">
|
||||||
v-for="user in installedSharedProfileData.users"
|
|
||||||
:key="user"
|
|
||||||
class="table-row"
|
|
||||||
>
|
|
||||||
<div class="table-cell table-text name-cell">
|
<div class="table-cell table-text name-cell">
|
||||||
<div class="user-content">
|
<div class="user-content">
|
||||||
<span v-tooltip="`${user}`" class="title">{{ user }}</span>
|
<span v-tooltip="`${user}`" class="title">{{ user }}</span>
|
||||||
@ -484,7 +480,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="table-cell table-text manage">
|
<div class="table-cell table-text manage">
|
||||||
<div v-tooltip="'Remove user'">
|
<div v-tooltip="'Remove user'">
|
||||||
<Button icon-only @click="removeSharedPackUser(user)" :disabled="user === installedSharedProfileData.owner_id">
|
<Button
|
||||||
|
icon-only
|
||||||
|
@click="removeSharedPackUser(user)"
|
||||||
|
:disabled="user === installedSharedProfileData.owner_id"
|
||||||
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -494,38 +494,45 @@
|
|||||||
<div v-if="installedSharedProfileData.is_owned" class="adjacent-input">
|
<div v-if="installedSharedProfileData.is_owned" class="adjacent-input">
|
||||||
your project
|
your project
|
||||||
{{ props.instance.sync_update_version }}
|
{{ props.instance.sync_update_version }}
|
||||||
:)
|
:)
|
||||||
<label for="share-sync">
|
<label for="share-sync">
|
||||||
<span class="label__title">Sync shared profile</span>
|
<span class="label__title">Sync shared profile</span>
|
||||||
<span class="label__description" v-if="props.instance.sync_update_version?.is_synced">
|
<span class="label__description" v-if="props.instance.sync_update_version?.is_synced">
|
||||||
You are up to date with the shared profile.
|
You are up to date with the shared profile.
|
||||||
</span>
|
</span>
|
||||||
<span class="label__description" v-else>
|
<span class="label__description" v-else>
|
||||||
You have changes that have not been synced to the shared profile. Click the button to upload your changes.
|
You have changes that have not been synced to the shared profile. Click the button to
|
||||||
</span>
|
upload your changes.
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<Button id="share-sync-sync" @click="outboundSyncSharedProfile" :disabled="props.instance.sync_update_version?.is_synced">
|
<Button
|
||||||
<GlobeIcon /> Sync
|
id="share-sync-sync"
|
||||||
</Button>
|
@click="outboundSyncSharedProfile"
|
||||||
<Button id="share-sync-revert" @click="inboundSyncSharedProfile" :disabled="props.instance.sync_update_version?.is_synced">
|
:disabled="props.instance.sync_update_version?.is_synced"
|
||||||
<GlobeIcon /> Revert
|
>
|
||||||
</Button>
|
<GlobeIcon /> Sync
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
id="share-sync-revert"
|
||||||
|
@click="inboundSyncSharedProfile"
|
||||||
|
:disabled="props.instance.sync_update_version?.is_synced"
|
||||||
|
>
|
||||||
|
<GlobeIcon /> Revert
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
not yours
|
not yours
|
||||||
{{ props.instance.sync_update_version }}
|
{{ props.instance.sync_update_version }}
|
||||||
<label for="share-sync">
|
<label for="share-sync">
|
||||||
<span class="label__title">Sync shared profile</span>
|
<span class="label__title">Sync shared profile</span>
|
||||||
<span class="label__description" v-if="props.instance.sync_update_version?.is_synced">
|
<span class="label__description" v-if="props.instance.sync_update_version?.is_synced">
|
||||||
You are up to date with the shared profile.
|
You are up to date with the shared profile.
|
||||||
</span>
|
</span>
|
||||||
<span class="label__description" v-else>
|
<span class="label__description" v-else>
|
||||||
You are not up to date with the shared profile. Click the button to update your instance.
|
You are not up to date with the shared profile. Click the button to update your instance.
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<Button id="share-sync-sync" @click="inboundSyncSharedProfile">
|
<Button id="share-sync-sync" @click="inboundSyncSharedProfile"> <GlobeIcon /> Sync </Button>
|
||||||
<GlobeIcon /> Sync
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
@ -750,16 +757,20 @@ const unlinkModpack = ref(false)
|
|||||||
|
|
||||||
const inProgress = ref(false)
|
const inProgress = ref(false)
|
||||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||||
const installedVersion = computed(() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id)
|
const installedVersion = computed(
|
||||||
|
() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id
|
||||||
|
)
|
||||||
const installedVersionData = computed(() => {
|
const installedVersionData = computed(() => {
|
||||||
if (!installedVersion.value) return null
|
if (!installedVersion.value) return null
|
||||||
return props.versions.find((version) => version.id === installedVersion.value)
|
return props.versions.find((version) => version.id === installedVersion.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
const sharedProfiles = await get_all();
|
const sharedProfiles = await get_all()
|
||||||
const installedSharedProfileData = computed(() => {
|
const installedSharedProfileData = computed(() => {
|
||||||
if (!props.instance.metadata.linked_data?.shared_profile) return null
|
if (!props.instance.metadata.linked_data?.shared_profile) return null
|
||||||
return sharedProfiles.find((profile) => profile.id === props.instance.metadata.linked_data?.shared_profile?.profile_id)
|
return sharedProfiles.find(
|
||||||
|
(profile) => profile.id === props.instance.metadata.linked_data?.shared_profile?.profile_id
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -1046,7 +1057,6 @@ async function generateShareLink() {
|
|||||||
async function removeSharedPackUser(user) {
|
async function removeSharedPackUser(user) {
|
||||||
await remove_users(props.instance.path, [user]).catch(handleError)
|
await remove_users(props.instance.path, [user]).catch(handleError)
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@ -1145,7 +1155,6 @@ async function removeSharedPackUser(user) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: var(--color-contrast);
|
color: var(--color-contrast);
|
||||||
@ -1153,6 +1162,4 @@ async function removeSharedPackUser(user) {
|
|||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -317,7 +317,10 @@ async function fetchProjectData() {
|
|||||||
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}`, 'project'),
|
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}`, 'project'),
|
||||||
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/version`, 'project'),
|
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/version`, 'project'),
|
||||||
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/members`, 'project'),
|
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/members`, 'project'),
|
||||||
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/dependencies`, 'project'),
|
useFetch(
|
||||||
|
`https://staging-api.modrinth.com/v2/project/${route.params.id}/dependencies`,
|
||||||
|
'project'
|
||||||
|
),
|
||||||
get_categories().catch(handleError),
|
get_categories().catch(handleError),
|
||||||
route.query.i ? getInstance(route.query.i, false).catch(handleError) : Promise.resolve(),
|
route.query.i ? getInstance(route.query.i, false).catch(handleError) : Promise.resolve(),
|
||||||
])
|
])
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user