You've already forked AstralRinth
forked from didirus/AstralRinth
Tag fetching and caching (#59)
* basic framework. still has errors * added functionality for main endpoints + some structuring * formatting * unused code * mimicked CLI function with wait_for process * added basic auth bindings * made PR changes, added playground * cargo fmt * removed missed println * misc tests fixes * cargo fmt * added windows support * cargo fmt * all OS use dunce * restructured profile slightly; fixed mac bug * profile changes, new main.rs * fixed requested pr + canonicaliation bug * fixed regressed bug in ui * fixed regressed bugs * fixed git error * typo * ran prettier * clippy * playground clippy * ported profile loading fix * profile change for real, url println and clippy * PR changes * auth bindings + semisynch flow * fixed dropping task error * prettier, eslint, clippy * removed debugging modifications * removed unused function that eslinter missed :( * initial errored push * working draft * added tag system! * fixed merge issue --------- Co-authored-by: Wyatt <wyatt@modrinth.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
pub mod auth;
|
||||
pub mod profile;
|
||||
pub mod profile_create;
|
||||
pub mod tags;
|
||||
pub mod settings;
|
||||
|
||||
pub mod data {
|
||||
|
||||
71
theseus/src/api/tags.rs
Normal file
71
theseus/src/api/tags.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
//! Theseus tag management interface
|
||||
pub use crate::{
|
||||
state::{
|
||||
Category, DonationPlatform, GameVersion, License, Loader, TagBundle,
|
||||
},
|
||||
State,
|
||||
};
|
||||
|
||||
// Get bundled set of tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_tag_bundle() -> crate::Result<TagBundle> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_tag_bundle()
|
||||
}
|
||||
|
||||
/// Get category tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_category_tags() -> crate::Result<Vec<Category>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_categories()
|
||||
}
|
||||
|
||||
/// Get report type tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_report_type_tags() -> crate::Result<Vec<String>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_report_types()
|
||||
}
|
||||
|
||||
/// Get loader tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_loader_tags() -> crate::Result<Vec<Loader>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_loaders()
|
||||
}
|
||||
|
||||
/// Get game version tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_game_version_tags() -> crate::Result<Vec<GameVersion>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_game_versions()
|
||||
}
|
||||
|
||||
/// Get license tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_license_tags() -> crate::Result<Vec<License>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_licenses()
|
||||
}
|
||||
|
||||
/// Get donation platform tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_donation_platform_tags() -> crate::Result<Vec<DonationPlatform>>
|
||||
{
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_donation_platforms()
|
||||
}
|
||||
@@ -28,6 +28,9 @@ pub use self::children::*;
|
||||
mod auth_task;
|
||||
pub use self::auth_task::*;
|
||||
|
||||
mod tags;
|
||||
pub use self::tags::*;
|
||||
|
||||
// Global state
|
||||
static LAUNCHER_STATE: OnceCell<Arc<State>> = OnceCell::const_new();
|
||||
pub struct State {
|
||||
@@ -50,6 +53,8 @@ pub struct State {
|
||||
pub(crate) profiles: RwLock<Profiles>,
|
||||
/// Launcher user account info
|
||||
pub(crate) users: RwLock<Users>,
|
||||
/// Launcher tags
|
||||
pub(crate) tags: RwLock<Tags>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
@@ -87,6 +92,15 @@ impl State {
|
||||
|
||||
let auth_flow = AuthTask::new();
|
||||
|
||||
// On launcher initialization, attempt a tag fetch after tags init
|
||||
let mut tags = Tags::init(&database)?;
|
||||
if let Err(tag_fetch_err) = tags.fetch_update().await {
|
||||
tracing::error!(
|
||||
"Failed to fetch tags on launcher init: {}",
|
||||
tag_fetch_err
|
||||
);
|
||||
};
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
database,
|
||||
directories,
|
||||
@@ -97,6 +111,7 @@ impl State {
|
||||
users: RwLock::new(users),
|
||||
children: RwLock::new(children),
|
||||
auth_flow: RwLock::new(auth_flow),
|
||||
tags: RwLock::new(tags),
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
253
theseus/src/state/tags.rs
Normal file
253
theseus/src/state/tags.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bincode::{Decode, Encode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::{BINCODE_CONFIG, MODRINTH_API_URL, REQWEST_CLIENT};
|
||||
|
||||
const CATEGORIES_DB_TREE: &[u8] = b"categories";
|
||||
const LOADERS_DB_TREE: &[u8] = b"loaders";
|
||||
const GAME_VERSIONS_DB_TREE: &[u8] = b"game_versions";
|
||||
const LICENSES_DB_TREE: &[u8] = b"licenses";
|
||||
const DONATION_PLATFORMS_DB_TREE: &[u8] = b"donation_platforms";
|
||||
const REPORT_TYPES_DB_TREE: &[u8] = b"report_types";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Tags(pub(crate) TagsInner);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TagsInner {
|
||||
pub categories: sled::Tree,
|
||||
pub loaders: sled::Tree,
|
||||
pub game_versions: sled::Tree,
|
||||
pub licenses: sled::Tree,
|
||||
pub donation_platforms: sled::Tree,
|
||||
pub report_types: sled::Tree,
|
||||
}
|
||||
|
||||
impl Tags {
|
||||
#[tracing::instrument(skip(db))]
|
||||
pub fn init(db: &sled::Db) -> crate::Result<Self> {
|
||||
Ok(Tags(TagsInner {
|
||||
categories: db.open_tree(CATEGORIES_DB_TREE)?,
|
||||
loaders: db.open_tree(LOADERS_DB_TREE)?,
|
||||
game_versions: db.open_tree(GAME_VERSIONS_DB_TREE)?,
|
||||
licenses: db.open_tree(LICENSES_DB_TREE)?,
|
||||
donation_platforms: db.open_tree(DONATION_PLATFORMS_DB_TREE)?,
|
||||
report_types: db.open_tree(REPORT_TYPES_DB_TREE)?,
|
||||
}))
|
||||
}
|
||||
|
||||
// Checks the database for categories tag, returns a Vec::new() if it doesnt exist, otherwise returns the categories
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_categories(&self) -> crate::Result<Vec<Category>> {
|
||||
self.0.categories.get("categories")?.map_or(
|
||||
Ok(Vec::new()),
|
||||
|categories| {
|
||||
bincode::decode_from_slice(&categories, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Checks the database for loaders tag, returns a Vec::new() if it doesnt exist, otherwise returns the loaders
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_loaders(&self) -> crate::Result<Vec<Loader>> {
|
||||
self.0
|
||||
.loaders
|
||||
.get("loaders")?
|
||||
.map_or(Ok(Vec::new()), |loaders| {
|
||||
bincode::decode_from_slice(&loaders, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
})
|
||||
}
|
||||
|
||||
// Checks the database for game_versions tag, returns a Vec::new() if it doesnt exist, otherwise returns the game_versions
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_game_versions(&self) -> crate::Result<Vec<GameVersion>> {
|
||||
self.0.game_versions.get("game_versions")?.map_or(
|
||||
Ok(Vec::new()),
|
||||
|game_versions| {
|
||||
bincode::decode_from_slice(&game_versions, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Checks the database for licenses tag, returns a Vec::new() if it doesnt exist, otherwise returns the licenses
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_licenses(&self) -> crate::Result<Vec<License>> {
|
||||
self.0
|
||||
.licenses
|
||||
.get("licenses")?
|
||||
.map_or(Ok(Vec::new()), |licenses| {
|
||||
bincode::decode_from_slice(&licenses, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
})
|
||||
}
|
||||
|
||||
// Checks the database for donation_platforms tag, returns a Vec::new() if it doesnt exist, otherwise returns the donation_platforms
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_donation_platforms(
|
||||
&self,
|
||||
) -> crate::Result<Vec<DonationPlatform>> {
|
||||
self.0.donation_platforms.get("donation_platforms")?.map_or(
|
||||
Ok(Vec::new()),
|
||||
|donation_platforms| {
|
||||
bincode::decode_from_slice(&donation_platforms, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Checks the database for report_types tag, returns a Vec::new() if it doesnt exist, otherwise returns the report_types
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_report_types(&self) -> crate::Result<Vec<String>> {
|
||||
self.0.report_types.get("report_types")?.map_or(
|
||||
Ok(Vec::new()),
|
||||
|report_types| {
|
||||
bincode::decode_from_slice(&report_types, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Gets all tags together as a serializable bundle
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_tag_bundle(&self) -> crate::Result<TagBundle> {
|
||||
Ok(TagBundle {
|
||||
categories: self.get_categories()?,
|
||||
loaders: self.get_loaders()?,
|
||||
game_versions: self.get_game_versions()?,
|
||||
licenses: self.get_licenses()?,
|
||||
donation_platforms: self.get_donation_platforms()?,
|
||||
report_types: self.get_report_types()?,
|
||||
})
|
||||
}
|
||||
|
||||
// Fetches the tags from the Modrinth API and stores them in the database
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn fetch_update(&mut self) -> crate::Result<()> {
|
||||
let categories = self.fetch_tag::<Category>("category");
|
||||
let loaders = self.fetch_tag::<Loader>("loader");
|
||||
let game_versions = self.fetch_tag::<GameVersion>("game_version");
|
||||
let licenses = self.fetch_tag::<License>("license");
|
||||
let donation_platforms =
|
||||
self.fetch_tag::<DonationPlatform>("donation_platform");
|
||||
let report_types = self.fetch_tag::<String>("report_type");
|
||||
|
||||
let (
|
||||
categories,
|
||||
loaders,
|
||||
game_versions,
|
||||
licenses,
|
||||
donation_platforms,
|
||||
report_types,
|
||||
) = futures::join!(
|
||||
categories,
|
||||
loaders,
|
||||
game_versions,
|
||||
licenses,
|
||||
donation_platforms,
|
||||
report_types
|
||||
);
|
||||
|
||||
// Store the tags in the database
|
||||
self.0.categories.insert(
|
||||
"categories",
|
||||
bincode::encode_to_vec(categories?, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.loaders.insert(
|
||||
"loaders",
|
||||
bincode::encode_to_vec(loaders?, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.game_versions.insert(
|
||||
"game_versions",
|
||||
bincode::encode_to_vec(game_versions?, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.licenses.insert(
|
||||
"licenses",
|
||||
bincode::encode_to_vec(licenses?, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.donation_platforms.insert(
|
||||
"donation_platforms",
|
||||
bincode::encode_to_vec(donation_platforms?, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.report_types.insert(
|
||||
"report_types",
|
||||
bincode::encode_to_vec(report_types?, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn fetch_tag<T>(
|
||||
&self,
|
||||
tag_type: &str,
|
||||
) -> Result<Vec<T>, reqwest::Error>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let url = &format!("{MODRINTH_API_URL}tag/{}", tag_type);
|
||||
let content = REQWEST_CLIENT
|
||||
.get(url)
|
||||
.send()
|
||||
.await?
|
||||
.json::<Vec<T>>()
|
||||
.await?;
|
||||
Ok(content)
|
||||
}
|
||||
}
|
||||
|
||||
// Serializeable struct for all tags to be fetched together by the frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TagBundle {
|
||||
pub categories: Vec<Category>,
|
||||
pub loaders: Vec<Loader>,
|
||||
pub game_versions: Vec<GameVersion>,
|
||||
pub licenses: Vec<License>,
|
||||
pub donation_platforms: Vec<DonationPlatform>,
|
||||
pub report_types: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct Category {
|
||||
pub name: String,
|
||||
pub project_type: String,
|
||||
pub header: String,
|
||||
pub icon: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct Loader {
|
||||
pub name: String,
|
||||
pub icon: PathBuf,
|
||||
pub supported_project_types: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct GameVersion {
|
||||
pub version: String,
|
||||
pub version_type: String,
|
||||
pub date: String,
|
||||
pub major: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct License {
|
||||
pub short: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct DonationPlatform {
|
||||
pub short: String,
|
||||
pub name: String,
|
||||
}
|
||||
@@ -3,9 +3,9 @@ use serde::{Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod auth;
|
||||
|
||||
pub mod profile;
|
||||
pub mod profile_create;
|
||||
pub mod tags;
|
||||
pub mod settings;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, TheseusGuiError>;
|
||||
|
||||
47
theseus_gui/src-tauri/src/api/tags.rs
Normal file
47
theseus_gui/src-tauri/src/api/tags.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use crate::api::Result;
|
||||
use theseus::tags::{
|
||||
Category, DonationPlatform, GameVersion, License, Loader, TagBundle,
|
||||
};
|
||||
|
||||
/// Gets cached category tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_category_tags() -> Result<Vec<Category>> {
|
||||
Ok(theseus::tags::get_category_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached report type tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_report_type_tags() -> Result<Vec<String>> {
|
||||
Ok(theseus::tags::get_report_type_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached loader tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_loader_tags() -> Result<Vec<Loader>> {
|
||||
Ok(theseus::tags::get_loader_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached game version tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_game_version_tags() -> Result<Vec<GameVersion>> {
|
||||
Ok(theseus::tags::get_game_version_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached license tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_license_tags() -> Result<Vec<License>> {
|
||||
Ok(theseus::tags::get_license_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached donation platform tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_donation_platform_tags() -> Result<Vec<DonationPlatform>>
|
||||
{
|
||||
Ok(theseus::tags::get_donation_platform_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached tag bundle from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_tag_bundle() -> Result<TagBundle> {
|
||||
Ok(theseus::tags::get_tag_bundle().await?)
|
||||
}
|
||||
@@ -38,6 +38,13 @@ fn main() {
|
||||
api::auth::auth_has_user,
|
||||
api::auth::auth_users,
|
||||
api::auth::auth_get_user,
|
||||
api::tags::tags_get_category_tags,
|
||||
api::tags::tags_get_donation_platform_tags,
|
||||
api::tags::tags_get_game_version_tags,
|
||||
api::tags::tags_get_loader_tags,
|
||||
api::tags::tags_get_license_tags,
|
||||
api::tags::tags_get_report_type_tags,
|
||||
api::tags::tags_get_tag_bundle,
|
||||
api::settings::settings_get,
|
||||
api::settings::settings_set,
|
||||
])
|
||||
|
||||
41
theseus_gui/src/helpers/tags.js
Normal file
41
theseus_gui/src/helpers/tags.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* All theseus API calls return serialized values (both return values and errors);
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
// Gets tag bundle of all tags
|
||||
export async function get_tag_bundle() {
|
||||
return await invoke('tags_get_tag_bundle')
|
||||
}
|
||||
|
||||
// Gets cached category tags
|
||||
export async function get_categories() {
|
||||
return await invoke('tags_get_categories')
|
||||
}
|
||||
|
||||
// Gets cached loaders tags
|
||||
export async function get_loaders() {
|
||||
return await invoke('tags_get_loaders')
|
||||
}
|
||||
|
||||
// Gets cached game_versions tags
|
||||
export async function get_game_versions() {
|
||||
return await invoke('tags_get_game_versions')
|
||||
}
|
||||
|
||||
// Gets cached licenses tags
|
||||
export async function get_licenses() {
|
||||
return await invoke('tags_get_licenses')
|
||||
}
|
||||
|
||||
// Gets cached donation_platforms tags
|
||||
export async function get_donation_platforms() {
|
||||
return await invoke('tags_get_donation_platforms')
|
||||
}
|
||||
|
||||
// Gets cached licenses tags
|
||||
export async function get_report_types() {
|
||||
return await invoke('tags_get_report_types')
|
||||
}
|
||||
@@ -117,7 +117,6 @@ async fn main() -> theseus::Result<()> {
|
||||
profile::run(&canonicalize(&profile_path)?, credentials).await
|
||||
}
|
||||
}?;
|
||||
|
||||
// Spawn a thread and hold the lock to the process until it ends
|
||||
println!("Started Minecraft. Waiting for process to end...");
|
||||
let mut proc: RwLockWriteGuard<Child> = proc_lock.write().await;
|
||||
|
||||
Reference in New Issue
Block a user