You've already forked AstralRinth
forked from didirus/AstralRinth
@@ -6,7 +6,10 @@ use tokio::task::JoinHandle;
|
||||
// A wrapper over the authentication task that allows it to be called from the frontend
|
||||
// without caching the task handle in the frontend
|
||||
|
||||
pub struct AuthTask(Option<JoinHandle<crate::Result<Credentials>>>);
|
||||
pub struct AuthTask(
|
||||
#[allow(clippy::type_complexity)]
|
||||
Option<JoinHandle<crate::Result<(Credentials, Option<String>)>>>,
|
||||
);
|
||||
|
||||
impl AuthTask {
|
||||
pub fn new() -> AuthTask {
|
||||
@@ -37,7 +40,8 @@ impl AuthTask {
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
pub async fn await_auth_completion() -> crate::Result<Credentials> {
|
||||
pub async fn await_auth_completion(
|
||||
) -> crate::Result<(Credentials, Option<String>)> {
|
||||
// Gets the task handle from the state, replacing with None
|
||||
let task = {
|
||||
let state = crate::State::get().await?;
|
||||
|
||||
@@ -52,6 +52,9 @@ pub use self::safe_processes::*;
|
||||
mod discord;
|
||||
pub use self::discord::*;
|
||||
|
||||
mod mr_auth;
|
||||
pub use self::mr_auth::*;
|
||||
|
||||
// Global state
|
||||
// RwLock on state only has concurrent reads, except for config dir change which takes control of the State
|
||||
static LAUNCHER_STATE: OnceCell<RwLock<State>> = OnceCell::const_new();
|
||||
@@ -77,16 +80,20 @@ pub struct State {
|
||||
pub settings: RwLock<Settings>,
|
||||
/// Reference to minecraft process children
|
||||
pub children: RwLock<Children>,
|
||||
/// Authentication flow
|
||||
pub auth_flow: RwLock<AuthTask>,
|
||||
/// Launcher profile metadata
|
||||
pub(crate) profiles: RwLock<Profiles>,
|
||||
/// Launcher user account info
|
||||
pub(crate) users: RwLock<Users>,
|
||||
/// Launcher tags
|
||||
pub(crate) tags: RwLock<Tags>,
|
||||
/// Launcher processes that should be safely exited on shutdown
|
||||
pub(crate) safety_processes: RwLock<SafeProcesses>,
|
||||
/// Launcher user account info
|
||||
pub(crate) users: RwLock<Users>,
|
||||
/// Authentication flow
|
||||
pub auth_flow: RwLock<AuthTask>,
|
||||
/// Modrinth Credentials Store
|
||||
pub credentials: RwLock<CredentialsStore>,
|
||||
/// Modrinth auth flow
|
||||
pub modrinth_auth_flow: RwLock<Option<ModrinthAuthFlow>>,
|
||||
|
||||
/// Discord RPC
|
||||
pub discord_rpc: DiscordGuard,
|
||||
@@ -159,15 +166,18 @@ impl State {
|
||||
!is_offline,
|
||||
&io_semaphore,
|
||||
&fetch_semaphore,
|
||||
&CredentialsStore(None),
|
||||
);
|
||||
let users_fut = Users::init(&directories, &io_semaphore);
|
||||
let creds_fut = CredentialsStore::init(&directories, &io_semaphore);
|
||||
// Launcher data
|
||||
let (metadata, profiles, tags, users) = loading_join! {
|
||||
let (metadata, profiles, tags, users, creds) = loading_join! {
|
||||
Some(&loading_bar), 70.0, Some("Loading metadata");
|
||||
metadata_fut,
|
||||
profiles_fut,
|
||||
tags_fut,
|
||||
users_fut,
|
||||
creds_fut,
|
||||
}?;
|
||||
|
||||
let children = Children::new();
|
||||
@@ -198,10 +208,12 @@ impl State {
|
||||
users: RwLock::new(users),
|
||||
children: RwLock::new(children),
|
||||
auth_flow: RwLock::new(auth_flow),
|
||||
credentials: RwLock::new(creds),
|
||||
tags: RwLock::new(tags),
|
||||
discord_rpc,
|
||||
safety_processes: RwLock::new(safety_processes),
|
||||
file_watcher: RwLock::new(file_watcher),
|
||||
modrinth_auth_flow: RwLock::new(None),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -222,6 +234,11 @@ impl State {
|
||||
|
||||
/// Updates state with data from the web, if we are online
|
||||
pub fn update() {
|
||||
tokio::task::spawn(Metadata::update());
|
||||
tokio::task::spawn(Tags::update());
|
||||
tokio::task::spawn(Profiles::update_projects());
|
||||
tokio::task::spawn(Profiles::update_modrinth_versions());
|
||||
tokio::task::spawn(CredentialsStore::update_creds());
|
||||
tokio::task::spawn(async {
|
||||
if let Ok(state) = crate::State::get().await {
|
||||
if !*state.offline.read().await {
|
||||
@@ -230,8 +247,9 @@ impl State {
|
||||
let res3 = Metadata::update();
|
||||
let res4 = Profiles::update_projects();
|
||||
let res5 = Settings::update_java();
|
||||
let res6 = CredentialsStore::update_creds();
|
||||
|
||||
let _ = join!(res1, res2, res3, res4, res5);
|
||||
let _ = join!(res1, res2, res3, res4, res5, res6);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
398
theseus/src/state/mr_auth.rs
Normal file
398
theseus/src/state/mr_auth.rs
Normal file
@@ -0,0 +1,398 @@
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
use crate::state::DirectoryInfo;
|
||||
use crate::util::fetch::{
|
||||
fetch_advanced, read_json, write, FetchSemaphore, IoSemaphore,
|
||||
};
|
||||
use crate::State;
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use futures::TryStreamExt;
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const AUTH_JSON: &str = "auth.json";
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ModrinthUser {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub created: DateTime<Utc>,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ModrinthCredentials {
|
||||
pub session: String,
|
||||
pub expires_at: DateTime<Utc>,
|
||||
pub user: ModrinthUser,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ModrinthCredentialsResult {
|
||||
TwoFactorRequired { flow: String },
|
||||
Credentials(ModrinthCredentials),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CredentialsStore(pub Option<ModrinthCredentials>);
|
||||
|
||||
impl CredentialsStore {
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
io_semaphore: &IoSemaphore,
|
||||
) -> crate::Result<Self> {
|
||||
let auth_path = dirs.caches_meta_dir().await.join(AUTH_JSON);
|
||||
let user = read_json(&auth_path, io_semaphore).await.ok();
|
||||
|
||||
if let Some(user) = user {
|
||||
Ok(Self(Some(user)))
|
||||
} else {
|
||||
Ok(Self(None))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save(&self) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let auth_path =
|
||||
state.directories.caches_meta_dir().await.join(AUTH_JSON);
|
||||
|
||||
if let Some(creds) = &self.0 {
|
||||
write(&auth_path, &serde_json::to_vec(creds)?, &state.io_semaphore)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn login(
|
||||
&mut self,
|
||||
credentials: ModrinthCredentials,
|
||||
) -> crate::Result<&Self> {
|
||||
self.0 = Some(credentials);
|
||||
self.save().await?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn update_creds() {
|
||||
let res = async {
|
||||
let state = State::get().await?;
|
||||
let mut creds_write = state.credentials.write().await;
|
||||
|
||||
refresh_credentials(&mut creds_write, &state.fetch_semaphore)
|
||||
.await?;
|
||||
|
||||
Ok::<(), crate::Error>(())
|
||||
}
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
tracing::warn!("Unable to update credentials: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn logout(&mut self) -> crate::Result<&Self> {
|
||||
self.0 = None;
|
||||
self.save().await?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModrinthAuthFlow {
|
||||
socket: async_tungstenite::WebSocketStream<
|
||||
async_tungstenite::tokio::ConnectStream,
|
||||
>,
|
||||
}
|
||||
|
||||
impl ModrinthAuthFlow {
|
||||
pub async fn new(provider: &str) -> crate::Result<Self> {
|
||||
let (socket, _) = async_tungstenite::tokio::connect_async(format!(
|
||||
"wss://api.modrinth.com/v2/auth/ws?provider={provider}"
|
||||
))
|
||||
.await?;
|
||||
Ok(Self { socket })
|
||||
}
|
||||
|
||||
pub async fn prepare_login_url(&mut self) -> crate::Result<String> {
|
||||
let code_resp = self
|
||||
.socket
|
||||
.try_next()
|
||||
.await?
|
||||
.ok_or(
|
||||
crate::ErrorKind::WSClosedError(String::from(
|
||||
"login socket URL",
|
||||
))
|
||||
.as_error(),
|
||||
)?
|
||||
.into_data();
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Url {
|
||||
url: String,
|
||||
}
|
||||
|
||||
let response = serde_json::from_slice::<Url>(&code_resp)?;
|
||||
|
||||
Ok(response.url)
|
||||
}
|
||||
|
||||
pub async fn extract_credentials(
|
||||
&mut self,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModrinthCredentialsResult> {
|
||||
// Minecraft bearer token
|
||||
let token_resp = self
|
||||
.socket
|
||||
.try_next()
|
||||
.await?
|
||||
.ok_or(
|
||||
crate::ErrorKind::WSClosedError(String::from(
|
||||
"login socket URL",
|
||||
))
|
||||
.as_error(),
|
||||
)?
|
||||
.into_data();
|
||||
|
||||
let response =
|
||||
serde_json::from_slice::<HashMap<String, Value>>(&token_resp)?;
|
||||
|
||||
get_result_from_res("code", response, semaphore).await
|
||||
}
|
||||
|
||||
pub async fn close(&mut self) -> crate::Result<()> {
|
||||
self.socket.close(None).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_result_from_res(
|
||||
code_key: &str,
|
||||
response: HashMap<String, Value>,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModrinthCredentialsResult> {
|
||||
if let Some(flow) = response.get("flow").and_then(|x| x.as_str()) {
|
||||
Ok(ModrinthCredentialsResult::TwoFactorRequired {
|
||||
flow: flow.to_string(),
|
||||
})
|
||||
} else if let Some(code) = response.get(code_key).and_then(|x| x.as_str()) {
|
||||
let info = fetch_info(code, semaphore).await?;
|
||||
|
||||
Ok(ModrinthCredentialsResult::Credentials(
|
||||
ModrinthCredentials {
|
||||
session: code.to_string(),
|
||||
expires_at: Utc::now() + Duration::weeks(2),
|
||||
user: info,
|
||||
},
|
||||
))
|
||||
} else if let Some(error) =
|
||||
response.get("description").and_then(|x| x.as_str())
|
||||
{
|
||||
Err(crate::ErrorKind::OtherError(format!(
|
||||
"Failed to login with error {error}"
|
||||
))
|
||||
.as_error())
|
||||
} else {
|
||||
Err(crate::ErrorKind::OtherError(String::from(
|
||||
"Flow/code/error not found in response!",
|
||||
))
|
||||
.as_error())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Session {
|
||||
session: String,
|
||||
}
|
||||
|
||||
pub async fn login_password(
|
||||
username: &str,
|
||||
password: &str,
|
||||
challenge: &str,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModrinthCredentialsResult> {
|
||||
let resp = fetch_advanced(
|
||||
Method::POST,
|
||||
&format!("https://{MODRINTH_API_URL}auth/login"),
|
||||
None,
|
||||
Some(serde_json::json!({
|
||||
"username": username,
|
||||
"password": password,
|
||||
"challenge": challenge,
|
||||
})),
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
&CredentialsStore(None),
|
||||
)
|
||||
.await?;
|
||||
let value = serde_json::from_slice::<HashMap<String, Value>>(&resp)?;
|
||||
|
||||
get_result_from_res("session", value, semaphore).await
|
||||
}
|
||||
|
||||
async fn get_creds_from_res(
|
||||
response: HashMap<String, Value>,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModrinthCredentials> {
|
||||
if let Some(code) = response.get("session").and_then(|x| x.as_str()) {
|
||||
let info = fetch_info(code, semaphore).await?;
|
||||
|
||||
Ok(ModrinthCredentials {
|
||||
session: code.to_string(),
|
||||
expires_at: Utc::now() + Duration::weeks(2),
|
||||
user: info,
|
||||
})
|
||||
} else if let Some(error) =
|
||||
response.get("description").and_then(|x| x.as_str())
|
||||
{
|
||||
Err(crate::ErrorKind::OtherError(format!(
|
||||
"Failed to login with error {error}"
|
||||
))
|
||||
.as_error())
|
||||
} else {
|
||||
Err(crate::ErrorKind::OtherError(String::from(
|
||||
"Flow/code/error not found in response!",
|
||||
))
|
||||
.as_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn login_2fa(
|
||||
code: &str,
|
||||
flow: &str,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModrinthCredentials> {
|
||||
let resp = fetch_advanced(
|
||||
Method::POST,
|
||||
&format!("{MODRINTH_API_URL}auth/login/2fa"),
|
||||
None,
|
||||
Some(serde_json::json!({
|
||||
"code": code,
|
||||
"flow": flow,
|
||||
})),
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
&CredentialsStore(None),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let response = serde_json::from_slice::<HashMap<String, Value>>(&resp)?;
|
||||
|
||||
get_creds_from_res(response, semaphore).await
|
||||
}
|
||||
|
||||
pub async fn create_account(
|
||||
username: &str,
|
||||
email: &str,
|
||||
password: &str,
|
||||
challenge: &str,
|
||||
sign_up_newsletter: bool,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModrinthCredentials> {
|
||||
let resp = fetch_advanced(
|
||||
Method::POST,
|
||||
&format!("{MODRINTH_API_URL}auth/create"),
|
||||
None,
|
||||
Some(serde_json::json!({
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": password,
|
||||
"challenge": challenge,
|
||||
"sign_up_newsletter": sign_up_newsletter,
|
||||
})),
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
&CredentialsStore(None),
|
||||
)
|
||||
.await?;
|
||||
let response = serde_json::from_slice::<HashMap<String, Value>>(&resp)?;
|
||||
|
||||
get_creds_from_res(response, semaphore).await
|
||||
}
|
||||
|
||||
pub async fn login_minecraft(
|
||||
flow: &str,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModrinthCredentialsResult> {
|
||||
let resp = fetch_advanced(
|
||||
Method::POST,
|
||||
&format!("{MODRINTH_API_URL}auth/login/minecraft"),
|
||||
None,
|
||||
Some(serde_json::json!({
|
||||
"flow": flow,
|
||||
})),
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
&CredentialsStore(None),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let response = serde_json::from_slice::<HashMap<String, Value>>(&resp)?;
|
||||
|
||||
get_result_from_res("session", response, semaphore).await
|
||||
}
|
||||
|
||||
pub async fn refresh_credentials(
|
||||
credentials_store: &mut CredentialsStore,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<()> {
|
||||
if let Some(ref mut credentials) = credentials_store.0 {
|
||||
let token = &credentials.session;
|
||||
let resp = fetch_advanced(
|
||||
Method::POST,
|
||||
&format!("{MODRINTH_API_URL}session/refresh"),
|
||||
None,
|
||||
None,
|
||||
Some(("Authorization", token)),
|
||||
None,
|
||||
semaphore,
|
||||
&CredentialsStore(None),
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|resp| serde_json::from_slice::<Session>(&resp).ok());
|
||||
|
||||
if let Some(value) = resp {
|
||||
credentials.user = fetch_info(&value.session, semaphore).await?;
|
||||
credentials.session = value.session;
|
||||
credentials.expires_at = Utc::now() + Duration::weeks(2);
|
||||
} else if credentials.expires_at < Utc::now() {
|
||||
credentials_store.0 = None;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_info(
|
||||
token: &str,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModrinthUser> {
|
||||
let result = fetch_advanced(
|
||||
Method::GET,
|
||||
&format!("{MODRINTH_API_URL}user"),
|
||||
None,
|
||||
None,
|
||||
Some(("Authorization", token)),
|
||||
None,
|
||||
semaphore,
|
||||
&CredentialsStore(None),
|
||||
)
|
||||
.await?;
|
||||
let value = serde_json::from_slice(&result)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
@@ -342,14 +342,17 @@ impl Profile {
|
||||
let paths = profile.get_profile_full_project_paths().await?;
|
||||
|
||||
let caches_dir = state.directories.caches_dir();
|
||||
let creds = state.credentials.read().await;
|
||||
let projects = crate::state::infer_data_from_files(
|
||||
profile.clone(),
|
||||
paths,
|
||||
caches_dir,
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
&creds,
|
||||
)
|
||||
.await?;
|
||||
drop(creds);
|
||||
|
||||
let mut new_profiles = state.profiles.write().await;
|
||||
if let Some(profile) = new_profiles.0.get_mut(&profile_path_id) {
|
||||
@@ -462,14 +465,17 @@ impl Profile {
|
||||
version_id: String,
|
||||
) -> crate::Result<(ProjectPathId, ModrinthVersion)> {
|
||||
let state = State::get().await?;
|
||||
let creds = state.credentials.read().await;
|
||||
let version = fetch_json::<ModrinthVersion>(
|
||||
Method::GET,
|
||||
&format!("{MODRINTH_API_URL}version/{version_id}"),
|
||||
None,
|
||||
None,
|
||||
&state.fetch_semaphore,
|
||||
&creds,
|
||||
)
|
||||
.await?;
|
||||
drop(creds);
|
||||
let file = if let Some(file) = version.files.iter().find(|x| x.primary)
|
||||
{
|
||||
file
|
||||
@@ -482,12 +488,15 @@ impl Profile {
|
||||
.into());
|
||||
};
|
||||
|
||||
let creds = state.credentials.read().await;
|
||||
let bytes = fetch(
|
||||
&file.url,
|
||||
file.hashes.get("sha1").map(|x| &**x),
|
||||
&state.fetch_semaphore,
|
||||
&creds,
|
||||
)
|
||||
.await?;
|
||||
drop(creds);
|
||||
let path = self
|
||||
.add_project_bytes(
|
||||
&file.filename,
|
||||
@@ -736,14 +745,17 @@ impl Profiles {
|
||||
future::try_join_all(files.into_iter().map(
|
||||
|(profile, files)| async {
|
||||
let profile_name = profile.profile_id();
|
||||
let creds = state.credentials.read().await;
|
||||
let inferred = super::projects::infer_data_from_files(
|
||||
profile,
|
||||
files,
|
||||
caches_dir.clone(),
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
&creds,
|
||||
)
|
||||
.await?;
|
||||
drop(creds);
|
||||
|
||||
let mut new_profiles = state.profiles.write().await;
|
||||
if let Some(profile) = new_profiles.0.get_mut(&profile_name)
|
||||
@@ -803,6 +815,7 @@ impl Profiles {
|
||||
let linked_project = linked_project;
|
||||
let state = state.clone();
|
||||
async move {
|
||||
let creds = state.credentials.read().await;
|
||||
let versions: Vec<ModrinthVersion> = fetch_json(
|
||||
Method::GET,
|
||||
&format!(
|
||||
@@ -813,8 +826,10 @@ impl Profiles {
|
||||
None,
|
||||
None,
|
||||
&state.fetch_semaphore,
|
||||
&creds,
|
||||
)
|
||||
.await?;
|
||||
drop(creds);
|
||||
|
||||
// Versions are pre-sorted in labrinth (by versions.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));)
|
||||
// so we can just take the first one
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Project management + inference
|
||||
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
use crate::state::Profile;
|
||||
use crate::state::{CredentialsStore, ModrinthUser, Profile};
|
||||
use crate::util::fetch::{
|
||||
fetch_json, write_cached_icon, FetchSemaphore, IoSemaphore,
|
||||
};
|
||||
@@ -168,18 +168,6 @@ pub struct ModrinthTeamMember {
|
||||
pub ordering: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ModrinthUser {
|
||||
pub id: String,
|
||||
pub github_id: Option<u64>,
|
||||
pub username: String,
|
||||
pub name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub created: DateTime<Utc>,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DependencyType {
|
||||
@@ -289,6 +277,7 @@ pub async fn infer_data_from_files(
|
||||
cache_dir: PathBuf,
|
||||
io_semaphore: &IoSemaphore,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
credentials: &CredentialsStore,
|
||||
) -> crate::Result<HashMap<ProjectPathId, Project>> {
|
||||
let mut file_path_hashes = HashMap::new();
|
||||
|
||||
@@ -327,6 +316,7 @@ pub async fn infer_data_from_files(
|
||||
"algorithm": "sha512",
|
||||
})),
|
||||
fetch_semaphore,
|
||||
credentials,
|
||||
),
|
||||
fetch_json::<HashMap<String, ModrinthVersion>>(
|
||||
Method::POST,
|
||||
@@ -339,6 +329,7 @@ pub async fn infer_data_from_files(
|
||||
"game_versions": [profile.metadata.game_version]
|
||||
})),
|
||||
fetch_semaphore,
|
||||
credentials,
|
||||
)
|
||||
)?;
|
||||
|
||||
@@ -357,6 +348,7 @@ pub async fn infer_data_from_files(
|
||||
None,
|
||||
None,
|
||||
fetch_semaphore,
|
||||
credentials,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -374,6 +366,7 @@ pub async fn infer_data_from_files(
|
||||
None,
|
||||
None,
|
||||
fetch_semaphore,
|
||||
credentials,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
|
||||
@@ -41,7 +41,7 @@ pub struct Settings {
|
||||
#[serde(default)]
|
||||
pub advanced_rendering: bool,
|
||||
#[serde(default)]
|
||||
pub onboarded_new: bool,
|
||||
pub fully_onboarded: bool,
|
||||
#[serde(default = "DirectoryInfo::get_initial_settings_dir")]
|
||||
pub loaded_config_dir: Option<PathBuf>,
|
||||
}
|
||||
@@ -82,7 +82,7 @@ impl Settings {
|
||||
developer_mode: false,
|
||||
opt_out_analytics: false,
|
||||
advanced_rendering: true,
|
||||
onboarded_new: false,
|
||||
fully_onboarded: false,
|
||||
|
||||
// By default, the config directory is the same as the settings directory
|
||||
loaded_config_dir: DirectoryInfo::get_initial_settings_dir(),
|
||||
|
||||
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
use crate::data::DirectoryInfo;
|
||||
use crate::state::CredentialsStore;
|
||||
use crate::util::fetch::{
|
||||
fetch_json, read_json, write, FetchSemaphore, IoSemaphore,
|
||||
};
|
||||
@@ -27,6 +28,7 @@ impl Tags {
|
||||
fetch_online: bool,
|
||||
io_semaphore: &IoSemaphore,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
credentials: &CredentialsStore,
|
||||
) -> crate::Result<Self> {
|
||||
let mut tags = None;
|
||||
let tags_path = dirs.caches_meta_dir().await.join("tags.json");
|
||||
@@ -35,7 +37,7 @@ impl Tags {
|
||||
{
|
||||
tags = Some(tags_json);
|
||||
} else if fetch_online {
|
||||
match Self::fetch(fetch_semaphore).await {
|
||||
match Self::fetch(fetch_semaphore, credentials).await {
|
||||
Ok(tags_fetch) => tags = Some(tags_fetch),
|
||||
Err(err) => {
|
||||
tracing::warn!("Unable to fetch launcher tags: {err}")
|
||||
@@ -58,7 +60,11 @@ impl Tags {
|
||||
pub async fn update() {
|
||||
let res = async {
|
||||
let state = crate::State::get().await?;
|
||||
let tags_fetch = Tags::fetch(&state.fetch_semaphore).await?;
|
||||
|
||||
let creds = state.credentials.read().await;
|
||||
let tags_fetch =
|
||||
Tags::fetch(&state.fetch_semaphore, &creds).await?;
|
||||
drop(creds);
|
||||
|
||||
let tags_path =
|
||||
state.directories.caches_meta_dir().await.join("tags.json");
|
||||
@@ -123,7 +129,10 @@ impl Tags {
|
||||
}
|
||||
|
||||
// Fetches the tags from the Modrinth API and stores them in the database
|
||||
pub async fn fetch(semaphore: &FetchSemaphore) -> crate::Result<Self> {
|
||||
pub async fn fetch(
|
||||
semaphore: &FetchSemaphore,
|
||||
credentials: &CredentialsStore,
|
||||
) -> crate::Result<Self> {
|
||||
let categories = format!("{MODRINTH_API_URL}tag/category");
|
||||
let loaders = format!("{MODRINTH_API_URL}tag/loader");
|
||||
let game_versions = format!("{MODRINTH_API_URL}tag/game_version");
|
||||
@@ -137,6 +146,7 @@ impl Tags {
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
credentials,
|
||||
);
|
||||
let loaders_fut = fetch_json::<Vec<Loader>>(
|
||||
Method::GET,
|
||||
@@ -144,6 +154,7 @@ impl Tags {
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
credentials,
|
||||
);
|
||||
let game_versions_fut = fetch_json::<Vec<GameVersion>>(
|
||||
Method::GET,
|
||||
@@ -151,6 +162,7 @@ impl Tags {
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
credentials,
|
||||
);
|
||||
let donation_platforms_fut = fetch_json::<Vec<DonationPlatform>>(
|
||||
Method::GET,
|
||||
@@ -158,6 +170,7 @@ impl Tags {
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
credentials,
|
||||
);
|
||||
let report_types_fut = fetch_json::<Vec<String>>(
|
||||
Method::GET,
|
||||
@@ -165,6 +178,7 @@ impl Tags {
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
credentials,
|
||||
);
|
||||
|
||||
let (
|
||||
|
||||
Reference in New Issue
Block a user