feat: Implement more functionality for Curseforge
This commit is contained in:
parent
e458108923
commit
518c7ec8b9
16 changed files with 579 additions and 288 deletions
|
|
@ -4,7 +4,10 @@ use onelauncher::cluster::content::package;
|
|||
use onelauncher::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType};
|
||||
use onelauncher::package::content::Providers;
|
||||
use onelauncher::package::import::{default_launcher_path, ImportType};
|
||||
use onelauncher::store::{Author, ClusterPath, Package, PackageBody, PackagePath, ProviderSearchResults};
|
||||
use onelauncher::store::{
|
||||
Author, ClusterPath, Package, PackageBody, PackagePath, ProviderSearchResults,
|
||||
};
|
||||
use onelauncher::utils::pagination::Pagination;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[specta::specta]
|
||||
|
|
@ -49,9 +52,11 @@ pub async fn get_all_provider_package_versions(
|
|||
project_id: String,
|
||||
game_versions: Option<Vec<String>>,
|
||||
loaders: Option<Vec<Loader>>,
|
||||
) -> Result<Vec<ManagedVersion>, String> {
|
||||
page: Option<u32>,
|
||||
page_size: Option<u16>,
|
||||
) -> Result<(Vec<ManagedVersion>, Pagination), String> {
|
||||
Ok(provider
|
||||
.get_all_versions(&project_id, game_versions, loaders, None, None) // TODO
|
||||
.get_all_versions(&project_id, game_versions, loaders, page, page_size)
|
||||
.await?)
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +66,7 @@ pub async fn get_provider_package_versions(
|
|||
provider: Providers,
|
||||
versions: Vec<String>,
|
||||
) -> Result<Vec<ManagedVersion>, String> {
|
||||
Ok(provider.get_versions("", versions).await?) // TODO
|
||||
Ok(provider.get_versions(versions).await?)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
|
|
@ -70,7 +75,7 @@ pub async fn get_provider_package_version(
|
|||
provider: Providers,
|
||||
version: String,
|
||||
) -> Result<ManagedVersion, String> {
|
||||
Ok(provider.get_version("", &version).await?) // TODO
|
||||
Ok(provider.get_version("", &version).await?) // TODO
|
||||
}
|
||||
|
||||
#[derive(specta::Type, serde::Deserialize, serde::Serialize)]
|
||||
|
|
@ -116,10 +121,7 @@ pub async fn get_provider_authors(
|
|||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn get_package_body(
|
||||
provider: Providers,
|
||||
body: PackageBody
|
||||
) -> Result<String, String> {
|
||||
pub async fn get_package_body(provider: Providers, body: PackageBody) -> Result<String, String> {
|
||||
Ok(provider.get_package_body(&body).await?)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { Ref } from '@solid-primitives/refs';
|
||||
import type { OnUpdatedEventListenerArgs, OverlayScrollbars } from 'overlayscrollbars';
|
||||
import { ChevronDownDoubleIcon } from '@untitled-theme/icons-solid';
|
||||
import { OverlayScrollbarsComponent, type OverlayScrollbarsComponentRef } from 'overlayscrollbars-solid';
|
||||
import { createEffect, createMemo, createSignal, Index, Show, untrack } from 'solid-js';
|
||||
import type { Ref } from '@solid-primitives/refs';
|
||||
import type { OnUpdatedEventListenerArgs, OverlayScrollbars } from 'overlayscrollbars';
|
||||
import Button from '../base/Button';
|
||||
import styles from './FormattedLog.module.scss';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { ManagedPackage, Providers } from '@onelauncher/client/bindings';
|
||||
import ModrinthImage from '~assets/logos/modrinth.svg?component-solid';
|
||||
import { type Component, type JSX, Match, Show, splitProps, Switch } from 'solid-js';
|
||||
import type { ManagedPackage, Providers } from '@onelauncher/client/bindings';
|
||||
|
||||
export function getProviderLogoElement(provider: ManagedPackage | Providers): string | Component {
|
||||
const providerName = (typeof provider === 'string' ? provider : provider.provider)?.toLowerCase() as Lowercase<Providers>;
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ function usePagination(initialOptions: PaginationOptions) {
|
|||
totalPages,
|
||||
offset,
|
||||
reset,
|
||||
options,
|
||||
Navigation,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ import Tooltip from '~ui/components/base/Tooltip';
|
|||
import Markdown from '~ui/components/content/Markdown';
|
||||
import Spinner from '~ui/components/Spinner';
|
||||
import useBrowser from '~ui/hooks/useBrowser';
|
||||
import useCommand from '~ui/hooks/useCommand';
|
||||
import useCommand, { tryResult } from '~ui/hooks/useCommand';
|
||||
import usePagination from '~ui/hooks/usePagination';
|
||||
import usePromptOpener from '~ui/hooks/usePromptOpener';
|
||||
import { abbreviateNumber, formatAsRelative } from '~utils';
|
||||
import { type Accessor, createContext, createEffect, createSignal, For, Match, on, type ParentProps, type Resource, Show, Switch, useContext } from 'solid-js';
|
||||
import { type Accessor, createContext, createEffect, createResource, createSignal, For, Match, on, type ParentProps, type Resource, Show, Switch, useContext } from 'solid-js';
|
||||
import { getLicenseUrl, getPackageUrl, upperFirst } from '../../../utils';
|
||||
|
||||
interface BrowserModParams extends Params {
|
||||
|
|
@ -148,7 +148,7 @@ function BrowserSidebar(props: { package: ManagedPackage }) {
|
|||
</div>
|
||||
<div class="flex flex-1 flex-col gap-2 p-3">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h4 class="text-fg-primary font-medium">{props.package.title}</h4>
|
||||
<h4 class="text-fg-primary font-medium line-height-snug">{props.package.title}</h4>
|
||||
<p class="text-xs text-fg-secondary">
|
||||
<span class="text-fg-primary capitalize">{props.package.package_type}</span>
|
||||
{' '}
|
||||
|
|
@ -383,7 +383,7 @@ function BrowserPackageVersions() {
|
|||
const context = useContext(BrowserPackageContext);
|
||||
const MAX_ITEMS_PER_PAGE = 20;
|
||||
|
||||
const { page, Navigation } = usePagination({
|
||||
const { page, Navigation, reset, options } = usePagination({
|
||||
itemsCount: () => context?.pkg().versions.length || 0,
|
||||
itemsPerPage: () => MAX_ITEMS_PER_PAGE,
|
||||
});
|
||||
|
|
@ -399,13 +399,25 @@ function BrowserPackageVersions() {
|
|||
return newestToOldest.slice(start, end);
|
||||
};
|
||||
|
||||
const [versions, { refetch }] = useCommand(context, async () => {
|
||||
const [versions, { refetch }] = createResource(context, async () => {
|
||||
if (context === null)
|
||||
return { error: 'No package context', status: 'error' };
|
||||
throw new Error('Context is null');
|
||||
|
||||
if (context.pkg().versions.length === 0) {
|
||||
const [data, pagination] = (await tryResult(() => bridge.commands.getAllProviderPackageVersions(context.pkg().provider, context.pkg().id, null, null, page() - 1, MAX_ITEMS_PER_PAGE))) || [];
|
||||
|
||||
if (options().itemsCount() !== pagination.totalCount)
|
||||
reset({
|
||||
itemsCount: () => pagination.totalCount || 0,
|
||||
itemsPerPage: () => MAX_ITEMS_PER_PAGE,
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const list = getVersionsForPage(page());
|
||||
|
||||
return await bridge.commands.getProviderPackageVersions(context.pkg().provider, list);
|
||||
return await tryResult(() => bridge.commands.getProviderPackageVersions(context.pkg().provider, list));
|
||||
});
|
||||
|
||||
let container!: HTMLDivElement;
|
||||
|
|
@ -484,8 +496,8 @@ function VersionRow(props: ManagedVersion) {
|
|||
|
||||
<td>
|
||||
<div class="flex flex-col gap-2">
|
||||
<h3 class="text-lg">{props.version_id}</h3>
|
||||
<p class="text-wrap text-sm">{props.name}</p>
|
||||
<h3 class="text-lg">{props.version_display}</h3>
|
||||
<p class="text-wrap text-sm">{props.name === props.version_display ? props.files[0]?.file_name : props.name}</p>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@ export default defineConfig(async ({ mode }) => {
|
|||
emptyOutDir: false,
|
||||
},
|
||||
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
clearScreen: false,
|
||||
server: {
|
||||
port: 8001,
|
||||
|
|
|
|||
|
|
@ -31,4 +31,4 @@ async fn search(provider: Providers) -> Result<()> {
|
|||
println!("{:#?}", results);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let paths = vec![
|
||||
get_root_workspace().join(".env"),
|
||||
PathBuf::from(".env"),
|
||||
];
|
||||
let paths = vec![get_root_workspace().join(".env"), PathBuf::from(".env")];
|
||||
|
||||
for path in paths {
|
||||
if path.exists() {
|
||||
|
|
@ -33,9 +30,11 @@ fn get_root_workspace() -> PathBuf {
|
|||
}
|
||||
|
||||
fn load_from_path(path: PathBuf) {
|
||||
let vars = dotenvy::EnvLoader::with_path(path).load().expect("failed to find .env file");
|
||||
let vars = dotenvy::EnvLoader::with_path(path)
|
||||
.load()
|
||||
.expect("failed to find .env file");
|
||||
|
||||
for (key, value) in vars {
|
||||
println!("cargo:rustc-env={key}={value}");
|
||||
}
|
||||
}
|
||||
for (key, value) in vars {
|
||||
println!("cargo:rustc-env={key}={value}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub async fn find_managed_version(
|
|||
)
|
||||
.await?;
|
||||
|
||||
Ok(versions
|
||||
Ok(versions.0
|
||||
.iter()
|
||||
.find(|v| {
|
||||
let check_game_version = game_version
|
||||
|
|
@ -46,7 +46,7 @@ pub async fn find_managed_version(
|
|||
|
||||
let check_package_version = package_version
|
||||
.as_ref()
|
||||
.map_or(true, |pv| pv == &v.version_id);
|
||||
.map_or(true, |pv| pv == &v.version_display);
|
||||
|
||||
check_game_version && check_loader && check_package_version
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use url::Url;
|
||||
|
||||
use crate::{data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType}, store::{ProviderSearchResults, SearchResult}, utils::http, Result, State};
|
||||
use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType};
|
||||
use crate::store::{ManagedVersionFile, ManagedVersionReleaseType, ProviderSearchResults, SearchResult};
|
||||
use crate::utils::{http, pagination::Pagination};
|
||||
use crate::{Result, State};
|
||||
|
||||
/// Mapping of Curseforge "classes" to their respective category ids
|
||||
#[derive(Debug, Default, Serialize_repr, Deserialize_repr)]
|
||||
|
|
@ -57,6 +60,18 @@ pub enum CurseforgeLoader {
|
|||
NeoForge = 6,
|
||||
}
|
||||
|
||||
impl From<String> for CurseforgeLoader {
|
||||
fn from(loader: String) -> Self {
|
||||
match loader.to_lowercase().as_str() {
|
||||
"forge" => CurseforgeLoader::Forge,
|
||||
"fabric" => CurseforgeLoader::Fabric,
|
||||
"quilt" => CurseforgeLoader::Quilt,
|
||||
"neoforge" => CurseforgeLoader::NeoForge,
|
||||
_ => CurseforgeLoader::Any,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Loader> for CurseforgeLoader {
|
||||
fn from(loader: Loader) -> Self {
|
||||
match loader {
|
||||
|
|
@ -69,48 +84,50 @@ impl From<Loader> for CurseforgeLoader {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[allow(dead_code)]
|
||||
pub struct Pagination {
|
||||
pub index: u32,
|
||||
pub page_size: u32,
|
||||
pub result_count: u32,
|
||||
pub total_count: u32,
|
||||
impl Into<Loader> for CurseforgeLoader {
|
||||
fn into(self) -> Loader {
|
||||
match self {
|
||||
CurseforgeLoader::Forge => Loader::Forge,
|
||||
CurseforgeLoader::Fabric => Loader::Fabric,
|
||||
CurseforgeLoader::Quilt => Loader::Quilt,
|
||||
CurseforgeLoader::NeoForge => Loader::NeoForge,
|
||||
_ => Loader::Vanilla,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CurseforgePackage {
|
||||
pub id: u32,
|
||||
pub game_id: u32,
|
||||
pub id: u32,
|
||||
pub game_id: u32,
|
||||
#[serde(alias = "classId")]
|
||||
pub package_type: CFPackageType,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub links: Links,
|
||||
pub summary: String,
|
||||
// pub status: u32,
|
||||
pub download_count: u64,
|
||||
pub is_featured: bool,
|
||||
// pub primary_category_id: u32,
|
||||
pub categories: Vec<Category>,
|
||||
pub authors: Vec<Author>,
|
||||
pub logo: Logo,
|
||||
// pub screenshots: Vec<Screenshot>,
|
||||
// pub main_file_id: u32,
|
||||
// pub latest_files: Vec<LatestFile>,
|
||||
// pub latest_files_indexes: Vec<LatestFilesIndex>,
|
||||
// pub latest_early_access_files_indexes: Vec<LatestEarlyAccessFilesIndex>,
|
||||
pub date_created: DateTime<Utc>,
|
||||
pub date_modified: DateTime<Utc>,
|
||||
pub date_released: DateTime<Utc>,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub links: Links,
|
||||
pub summary: String,
|
||||
// pub status: u32,
|
||||
pub download_count: u64,
|
||||
pub is_featured: bool,
|
||||
// pub primary_category_id: u32,
|
||||
pub categories: Vec<Category>,
|
||||
pub authors: Vec<Author>,
|
||||
pub logo: Logo,
|
||||
// pub screenshots: Vec<Screenshot>,
|
||||
// pub main_file_id: u32,
|
||||
// pub latest_files: Vec<LatestFile>,
|
||||
// pub latest_files_indexes: Vec<LatestFilesIndex>,
|
||||
// pub latest_early_access_files_indexes: Vec<LatestEarlyAccessFilesIndex>,
|
||||
pub date_created: DateTime<Utc>,
|
||||
pub date_modified: DateTime<Utc>,
|
||||
pub date_released: DateTime<Utc>,
|
||||
#[serde(default)]
|
||||
pub allow_mod_distribution: bool,
|
||||
// pub game_popularity_rank: u32,
|
||||
// pub is_available: bool,
|
||||
pub thumbs_up_count: u32,
|
||||
pub rating: Option<f32>,
|
||||
pub allow_mod_distribution: bool,
|
||||
// pub game_popularity_rank: u32,
|
||||
// pub is_available: bool,
|
||||
pub thumbs_up_count: u32,
|
||||
pub rating: Option<f32>,
|
||||
}
|
||||
|
||||
impl From<CurseforgePackage> for ManagedPackage {
|
||||
|
|
@ -137,15 +154,19 @@ impl From<CurseforgePackage> for ManagedPackage {
|
|||
optional_categories: None,
|
||||
license: None,
|
||||
author: crate::store::Author::Users(
|
||||
package.authors.into_iter().map(|a| ManagedUser {
|
||||
id: a.id.to_string(),
|
||||
username: a.name,
|
||||
url: Some(a.url),
|
||||
is_organization_user: false,
|
||||
avatar_url: None,
|
||||
bio: None,
|
||||
role: None,
|
||||
}).collect()
|
||||
package
|
||||
.authors
|
||||
.into_iter()
|
||||
.map(|a| ManagedUser {
|
||||
id: a.id.to_string(),
|
||||
username: a.name,
|
||||
url: Some(a.url),
|
||||
is_organization_user: false,
|
||||
avatar_url: None,
|
||||
bio: None,
|
||||
role: None,
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
is_archived: false,
|
||||
}
|
||||
|
|
@ -158,7 +179,11 @@ impl Into<SearchResult> for CurseforgePackage {
|
|||
title: self.name,
|
||||
slug: self.slug,
|
||||
project_id: self.id.to_string(),
|
||||
author: self.authors.first().and_then(|a| Some(a.name.clone())).unwrap_or(String::from("Unknown")),
|
||||
author: self
|
||||
.authors
|
||||
.first()
|
||||
.and_then(|a| Some(a.name.clone()))
|
||||
.unwrap_or(String::from("Unknown")),
|
||||
description: self.summary,
|
||||
client_side: crate::store::PackageSide::Unknown,
|
||||
server_side: crate::store::PackageSide::Unknown,
|
||||
|
|
@ -178,134 +203,223 @@ impl Into<SearchResult> for CurseforgePackage {
|
|||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Links {
|
||||
pub website_url: Option<String>,
|
||||
pub wiki_url: Option<String>,
|
||||
pub issues_url: Option<String>,
|
||||
pub source_url: Option<String>,
|
||||
pub website_url: Option<String>,
|
||||
pub wiki_url: Option<String>,
|
||||
pub issues_url: Option<String>,
|
||||
pub source_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Category {
|
||||
pub id: u32,
|
||||
pub game_id: u32,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub url: String,
|
||||
pub icon_url: String,
|
||||
pub date_modified: String,
|
||||
// pub parent_category_id: u32,
|
||||
// pub display_index: u32,
|
||||
pub id: u32,
|
||||
pub game_id: u32,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub url: String,
|
||||
pub icon_url: String,
|
||||
pub date_modified: String,
|
||||
// pub parent_category_id: u32,
|
||||
// pub display_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Author {
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Logo {
|
||||
pub id: u32,
|
||||
pub mod_id: u32,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub thumbnail_url: String,
|
||||
pub url: String,
|
||||
pub id: u32,
|
||||
pub mod_id: u32,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub thumbnail_url: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Screenshot {
|
||||
pub id: u32,
|
||||
pub mod_id: u32,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub thumbnail_url: String,
|
||||
pub url: String,
|
||||
pub id: u32,
|
||||
pub mod_id: u32,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub thumbnail_url: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[derive(Default, Serialize_repr, Deserialize_repr, Clone, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum ModFileReleaseType {
|
||||
#[default]
|
||||
Release = 1,
|
||||
Beta = 2,
|
||||
Alpha = 3,
|
||||
}
|
||||
|
||||
impl Into<ManagedVersionReleaseType> for ModFileReleaseType {
|
||||
fn into(self) -> ManagedVersionReleaseType {
|
||||
match self {
|
||||
ModFileReleaseType::Release => ManagedVersionReleaseType::Release,
|
||||
ModFileReleaseType::Beta => ManagedVersionReleaseType::Beta,
|
||||
ModFileReleaseType::Alpha => ManagedVersionReleaseType::Alpha,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModFile {
|
||||
pub id: u32,
|
||||
pub game_id: u32,
|
||||
pub mod_id: u32,
|
||||
pub is_available: bool,
|
||||
pub display_name: String,
|
||||
pub file_name: String,
|
||||
// pub release_type: u32,
|
||||
// pub file_status: u32,
|
||||
// pub hashes: Vec<Hash>,
|
||||
// pub file_date: String,
|
||||
// pub file_length: u32,
|
||||
pub download_count: u32,
|
||||
// pub file_size_on_disk: u32,
|
||||
pub download_url: Option<String>,
|
||||
pub game_versions: Vec<String>,
|
||||
pub sortable_game_versions: Vec<SortableGameVersion>,
|
||||
pub dependencies: Vec<Dependency>,
|
||||
// pub expose_as_alternative: bool,
|
||||
// pub parent_project_file_id: u32,
|
||||
// pub alternate_file_id: u32,
|
||||
// pub is_server_pack: bool,
|
||||
// pub server_pack_file_id: u32,
|
||||
// pub is_early_access_content: bool,
|
||||
// pub early_access_end_date: String,
|
||||
// pub file_fingerprint: u32,
|
||||
// pub modules: Vec<Module>,
|
||||
pub id: u32,
|
||||
pub game_id: u32,
|
||||
pub mod_id: u32,
|
||||
pub is_available: bool,
|
||||
pub display_name: String,
|
||||
pub file_name: String,
|
||||
pub release_type: ModFileReleaseType,
|
||||
// pub file_status: u32,
|
||||
pub hashes: Vec<Hash>,
|
||||
pub file_date: DateTime<Utc>,
|
||||
pub file_length: u64,
|
||||
pub download_count: u32,
|
||||
pub file_size_on_disk: Option<u64>,
|
||||
pub download_url: Option<String>,
|
||||
pub game_versions: Vec<String>,
|
||||
pub sortable_game_versions: Vec<SortableGameVersion>,
|
||||
pub dependencies: Vec<Dependency>,
|
||||
// pub expose_as_alternative: bool,
|
||||
// pub parent_project_file_id: u32,
|
||||
// pub alternate_file_id: u32,
|
||||
// pub is_server_pack: bool,
|
||||
// pub server_pack_file_id: u32,
|
||||
// pub is_early_access_content: bool,
|
||||
// pub early_access_end_date: String,
|
||||
// pub file_fingerprint: u32,
|
||||
// pub modules: Vec<Module>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
impl Into<ManagedVersion> for ModFile {
|
||||
fn into(self) -> ManagedVersion {
|
||||
let mut hashes = HashMap::new();
|
||||
|
||||
for hash in self.hashes {
|
||||
hashes.insert(hash.algo.name(), hash.value);
|
||||
}
|
||||
|
||||
let mut game_versions = Vec::new();
|
||||
let mut loader = CurseforgeLoader::Any;
|
||||
|
||||
for version in self.sortable_game_versions {
|
||||
if version.game_version.is_empty() {
|
||||
// This is most likely a loader
|
||||
loader = CurseforgeLoader::from(version.game_version_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
game_versions.push(version.game_version);
|
||||
}
|
||||
|
||||
let mut files = Vec::new();
|
||||
if let Some(download_url) = self.download_url {
|
||||
files.push(ManagedVersionFile {
|
||||
url: download_url,
|
||||
file_name: self.file_name,
|
||||
primary: true,
|
||||
size: self.file_size_on_disk.unwrap_or(self.file_length),
|
||||
file_type: Some(crate::store::PackageFile::RequiredPack),
|
||||
hashes,
|
||||
});
|
||||
}
|
||||
|
||||
ManagedVersion {
|
||||
package_id: self.mod_id.to_string(),
|
||||
id: self.id.to_string(),
|
||||
name: self.display_name.clone(),
|
||||
author: String::new(),
|
||||
loaders: vec![Into::<Loader>::into(loader)],
|
||||
changelog: String::new(),
|
||||
changelog_url: None,
|
||||
deps: vec![],
|
||||
downloads: self.download_count,
|
||||
featured: false,
|
||||
is_available: self.is_available && files.len() > 0,
|
||||
files,
|
||||
game_versions: game_versions,
|
||||
published: self.file_date,
|
||||
version_display: self.display_name,
|
||||
version_type: self.release_type.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)]
|
||||
#[repr(u8)]
|
||||
pub enum HashAlgorithm {
|
||||
Sha1 = 1,
|
||||
MD5 = 2,
|
||||
}
|
||||
|
||||
impl HashAlgorithm {
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
Self::Sha1 => "sha1",
|
||||
Self::MD5 => "md5",
|
||||
}.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Hash {
|
||||
pub value: String,
|
||||
pub algo: u32,
|
||||
pub value: String,
|
||||
pub algo: HashAlgorithm,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SortableGameVersion {
|
||||
pub game_version_name: String,
|
||||
pub game_version_padded: String,
|
||||
pub game_version: String,
|
||||
pub game_version_release_date: String,
|
||||
pub game_version_type_id: u32,
|
||||
pub game_version_name: String,
|
||||
pub game_version_padded: String,
|
||||
pub game_version: String,
|
||||
pub game_version_release_date: String,
|
||||
pub game_version_type_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Dependency {
|
||||
pub mod_id: u32,
|
||||
pub relation_type: u32,
|
||||
pub mod_id: u32,
|
||||
pub relation_type: u32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Module {
|
||||
pub name: String,
|
||||
pub fingerprint: u32,
|
||||
pub name: String,
|
||||
pub fingerprint: u32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModFilesIndex {
|
||||
pub game_version: String,
|
||||
pub file_id: u32,
|
||||
pub filename: String,
|
||||
pub release_type: u32,
|
||||
pub game_version_type_id: u32,
|
||||
pub game_version: String,
|
||||
pub file_id: u32,
|
||||
pub filename: String,
|
||||
pub release_type: u32,
|
||||
pub game_version_type_id: u32,
|
||||
#[serde(default)]
|
||||
pub mod_loader: CurseforgeLoader,
|
||||
pub mod_loader: CurseforgeLoader,
|
||||
}
|
||||
|
||||
// https://docs.curseforge.com/rest-api/?shell#tocS_ModsSearchSortField
|
||||
#[allow(dead_code)]
|
||||
enum SearchSort {
|
||||
Featured = 1,
|
||||
Featured = 1,
|
||||
Popularity = 2,
|
||||
LastUpdated = 3,
|
||||
Name = 4,
|
||||
|
|
@ -319,28 +433,38 @@ struct CFData<T> {
|
|||
data: T,
|
||||
}
|
||||
|
||||
pub async fn fetch_url_builder<F: FnOnce(&mut Url)>(url: &str, url_builder: Option<F>) -> Result<Bytes> {
|
||||
// TODO: Get the API key from settings, fallback to constant, and error if missing
|
||||
let key = crate::constants::CURSEFORGE_API_KEY.ok_or(anyhow::anyhow!("missing curseforge api key"))?;
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
struct CFPaginatedData<T> {
|
||||
pagination: Pagination,
|
||||
data: T,
|
||||
}
|
||||
|
||||
let mut headers = HashMap::<&str, &str>::new();
|
||||
pub async fn fetch_advanced<F: FnOnce(&mut Url)>(
|
||||
method: Method,
|
||||
url: &str,
|
||||
url_builder: Option<F>,
|
||||
headers: Option<HashMap<&str, &str>>,
|
||||
json_body: Option<serde_json::Value>,
|
||||
) -> Result<Bytes> {
|
||||
// TODO: Get the API key from settings, fallback to constant, and error if missing
|
||||
let key = crate::constants::CURSEFORGE_API_KEY
|
||||
.ok_or(anyhow::anyhow!("missing curseforge api key"))?;
|
||||
|
||||
let mut headers = headers.unwrap_or(HashMap::<&str, &str>::new());
|
||||
headers.insert("x-api-key", key);
|
||||
|
||||
let url = &mut Url::parse(format!(
|
||||
"{}{}",
|
||||
crate::constants::CURSEFORGE_API_URL,
|
||||
url,
|
||||
).as_str())?;
|
||||
let url =
|
||||
&mut Url::parse(format!("{}{}", crate::constants::CURSEFORGE_API_URL, url).as_str())?;
|
||||
|
||||
if let Some(url_builder) = url_builder {
|
||||
url_builder(url);
|
||||
}
|
||||
|
||||
http::fetch_advanced(
|
||||
Method::GET,
|
||||
method,
|
||||
url.as_str(),
|
||||
None,
|
||||
None,
|
||||
json_body,
|
||||
Some(headers),
|
||||
None,
|
||||
&State::get().await?.fetch_semaphore,
|
||||
|
|
@ -348,6 +472,13 @@ pub async fn fetch_url_builder<F: FnOnce(&mut Url)>(url: &str, url_builder: Opti
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn fetch_url_builder<F: FnOnce(&mut Url)>(
|
||||
url: &str,
|
||||
url_builder: Option<F>,
|
||||
) -> Result<Bytes> {
|
||||
fetch_advanced(Method::GET, url, url_builder, None, None).await
|
||||
}
|
||||
|
||||
async fn fetch(url: &str) -> Result<Bytes> {
|
||||
fetch_url_builder(url, None::<fn(&mut Url)>).await
|
||||
}
|
||||
|
|
@ -362,11 +493,7 @@ pub async fn search(
|
|||
loaders: Option<Vec<Loader>>,
|
||||
package_types: Option<Vec<PackageType>>,
|
||||
) -> Result<ProviderSearchResults> {
|
||||
#[derive(Deserialize)]
|
||||
struct CFSearchResults {
|
||||
pagination: Pagination,
|
||||
data: Vec<CurseforgePackage>
|
||||
}
|
||||
type CFSearchResults = CFPaginatedData<Vec<CurseforgePackage>>;
|
||||
|
||||
let results: CFSearchResults = serde_json::from_slice(
|
||||
&fetch_url_builder(
|
||||
|
|
@ -374,10 +501,16 @@ pub async fn search(
|
|||
Some(Box::new(move |url: &mut Url| {
|
||||
let params = &mut url.query_pairs_mut();
|
||||
|
||||
params.append_pair("gameId", crate::constants::CURSEFORGE_GAME_ID.to_string().as_str());
|
||||
params.append_pair(
|
||||
"gameId",
|
||||
crate::constants::CURSEFORGE_GAME_ID.to_string().as_str(),
|
||||
);
|
||||
params.append_pair("pageSize", limit.unwrap_or(10).to_string().as_str());
|
||||
params.append_pair("index", offset.unwrap_or(0).to_string().as_str());
|
||||
params.append_pair("sortField", (SearchSort::Popularity as u8).to_string().as_str());
|
||||
params.append_pair(
|
||||
"sortField",
|
||||
(SearchSort::Popularity as u8).to_string().as_str(),
|
||||
);
|
||||
params.append_pair("sortOrder", "desc");
|
||||
|
||||
if let Some(query) = query {
|
||||
|
|
@ -386,11 +519,33 @@ pub async fn search(
|
|||
|
||||
if let Some(game_versions) = game_versions {
|
||||
// Need to create a list e.g. ["1.16.5", "1.17.1"]
|
||||
params.append_pair("gameVersions", format!("[{}]", game_versions.into_iter().map(|v| format!("\"{}\"", v)).collect::<Vec<String>>().join(",")).as_str());
|
||||
params.append_pair(
|
||||
"gameVersions",
|
||||
format!(
|
||||
"[{}]",
|
||||
game_versions
|
||||
.into_iter()
|
||||
.map(|v| format!("\"{}\"", v))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(loaders) = loaders {
|
||||
params.append_pair("modLoaderTypes", format!("[{}]", loaders.into_iter().map(|l| (CurseforgeLoader::from(l) as u32).to_string()).collect::<Vec<String>>().join(",")).as_str());
|
||||
params.append_pair(
|
||||
"modLoaderTypes",
|
||||
format!(
|
||||
"[{}]",
|
||||
loaders
|
||||
.into_iter()
|
||||
.map(|l| (CurseforgeLoader::from(l) as u32).to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(package_types) = package_types {
|
||||
|
|
@ -399,42 +554,105 @@ pub async fn search(
|
|||
}
|
||||
|
||||
if package_types.len() > 1 {
|
||||
tracing::warn!("curseforge provider does not support multiple package types");
|
||||
tracing::warn!(
|
||||
"curseforge provider does not support multiple package types"
|
||||
);
|
||||
}
|
||||
|
||||
let package_type = package_types.first().unwrap();
|
||||
|
||||
params.append_pair("classId", (CFPackageType::from(*package_type) as u32).to_string().as_str());
|
||||
params.append_pair(
|
||||
"classId",
|
||||
(CFPackageType::from(*package_type) as u32)
|
||||
.to_string()
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
}))
|
||||
).await?)?;
|
||||
})),
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
Ok(ProviderSearchResults {
|
||||
total: results.pagination.total_count,
|
||||
results: results.data.into_iter().map(Into::into).collect::<Vec<SearchResult>>(),
|
||||
results: results
|
||||
.data
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<SearchResult>>(),
|
||||
provider: crate::package::content::Providers::Curseforge,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get(id: u32) -> Result<CurseforgePackage> {
|
||||
Ok(serde_json::from_slice::<CFData<_>>(
|
||||
&fetch(format!("/v1/mods/{}", id).as_str())
|
||||
.await?,
|
||||
)?.data)
|
||||
Ok(
|
||||
serde_json::from_slice::<CFData<_>>(&fetch(format!("/v1/mods/{}", id).as_str()).await?)?
|
||||
.data,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get_package_body(url: String) -> Result<String> {
|
||||
Ok(serde_json::from_slice::<CFData<_>>(
|
||||
&fetch(&url).await?,
|
||||
)?.data)
|
||||
Ok(serde_json::from_slice::<CFData<_>>(&fetch(&url).await?)?.data)
|
||||
}
|
||||
|
||||
pub async fn get_all_versions(
|
||||
project_id: &str,
|
||||
game_versions: Option<Vec<String>>,
|
||||
loaders: Option<Vec<Loader>>,
|
||||
page: Option<u32>,
|
||||
page_size: Option<u16>,
|
||||
) -> Result<(Vec<ModFile>, Pagination)> {
|
||||
let data: CFPaginatedData<Vec<ModFile>> = serde_json::from_slice::<CFPaginatedData<_>>(
|
||||
&fetch_url_builder(
|
||||
format!("/v1/mods/{project_id}/files").as_str(),
|
||||
Some(Box::new(move |url: &mut Url| {
|
||||
if let Some(game_versions) = game_versions {
|
||||
if let Some(version) = game_versions.first() {
|
||||
url.query_pairs_mut().append_pair("gameVersion", version);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(loader) = loaders {
|
||||
if let Some(loader) = loader.first() {
|
||||
url.query_pairs_mut().append_pair(
|
||||
"modLoaderType",
|
||||
(CurseforgeLoader::from(*loader) as u8).to_string().as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let page = page.unwrap_or(0);
|
||||
url.query_pairs_mut().append_pair("index", page.to_string().as_str());
|
||||
|
||||
let page_size = page_size.unwrap_or(10);
|
||||
url.query_pairs_mut().append_pair("pageSize", page_size.to_string().as_str());
|
||||
})),
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
Ok((data.data, data.pagination))
|
||||
}
|
||||
|
||||
pub async fn get_versions(
|
||||
versions: Vec<String>,
|
||||
) -> Result<Vec<ManagedVersion>> {
|
||||
let mut map = serde_json::Map::new();
|
||||
map.insert("fileIds".to_string(), versions.iter().map(|v| v.parse::<u32>().unwrap()).collect::<Vec<u32>>().into());
|
||||
|
||||
let json_body = serde_json::Value::Object(map);
|
||||
|
||||
Ok(serde_json::from_slice::<CFData<_>>(
|
||||
&fetch_advanced(
|
||||
Method::POST,
|
||||
format!("/v1/mods/files").as_str(),
|
||||
None::<fn(&mut Url)>,
|
||||
None,
|
||||
Some(json_body)
|
||||
)
|
||||
.await?,
|
||||
)?
|
||||
.data)
|
||||
}
|
||||
|
||||
// pub async fn get_all_versions(
|
||||
// project_id: &str,
|
||||
// game_versions: Option<Vec<String>>,
|
||||
// loaders: Option<Vec<Loader>>,
|
||||
// page: Option<u32>,
|
||||
// page_size: Option<u16>,
|
||||
// ) -> Result<Vec<ManagedVersion>> {
|
||||
|
||||
// }
|
||||
|
|
@ -8,10 +8,11 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType};
|
||||
use crate::package::content::modrinth::FacetBuilder;
|
||||
use crate::store::{Author, PackageBody, ProviderSearchResults};
|
||||
use crate::utils::pagination::Pagination;
|
||||
use crate::Result;
|
||||
|
||||
mod modrinth;
|
||||
mod curseforge;
|
||||
mod modrinth;
|
||||
|
||||
/// Providers for content packages
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
|
|
@ -59,35 +60,49 @@ impl Providers {
|
|||
open_source: Option<bool>,
|
||||
) -> Result<ProviderSearchResults> {
|
||||
match self {
|
||||
Self::Modrinth => modrinth::search(
|
||||
query,
|
||||
limit,
|
||||
offset,
|
||||
Some(|mut builder| build_facets(
|
||||
&mut builder,
|
||||
categories,
|
||||
Self::Modrinth => {
|
||||
modrinth::search(
|
||||
query,
|
||||
limit,
|
||||
offset,
|
||||
Some(|mut builder| {
|
||||
build_facets(
|
||||
&mut builder,
|
||||
categories,
|
||||
game_versions,
|
||||
loaders,
|
||||
package_types,
|
||||
open_source,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
Self::Curseforge => {
|
||||
curseforge::search(
|
||||
query,
|
||||
limit,
|
||||
offset,
|
||||
game_versions,
|
||||
categories,
|
||||
loaders,
|
||||
package_types,
|
||||
open_source
|
||||
)),
|
||||
).await,
|
||||
Self::Curseforge => curseforge::search(
|
||||
query,
|
||||
limit,
|
||||
offset,
|
||||
game_versions,
|
||||
categories,
|
||||
loaders,
|
||||
package_types
|
||||
).await
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(&self, slug_or_id: &str) -> Result<ManagedPackage> {
|
||||
Ok(match self {
|
||||
Self::Modrinth => modrinth::get(slug_or_id).await?.into(),
|
||||
Self::Curseforge => curseforge::get(slug_or_id.parse::<u32>().map_err(|err| anyhow::anyhow!(err))?).await?.into(),
|
||||
Self::Curseforge => curseforge::get(
|
||||
slug_or_id
|
||||
.parse::<u32>()
|
||||
.map_err(|err| anyhow::anyhow!(err))?,
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -109,35 +124,33 @@ impl Providers {
|
|||
loaders: Option<Vec<Loader>>,
|
||||
page: Option<u32>,
|
||||
page_size: Option<u16>,
|
||||
) -> Result<Vec<ManagedVersion>> {
|
||||
Ok(
|
||||
match self {
|
||||
Self::Modrinth => modrinth::get_all_versions(project_id, game_versions, loaders),
|
||||
// Self::Curseforge => curseforge::get_all_versions(
|
||||
// project_id,
|
||||
// game_versions.map(|list| list.first()),
|
||||
// loaders.map(|list| list.first()),
|
||||
// page,
|
||||
// page_size
|
||||
// ),
|
||||
Self::Curseforge => todo!(),
|
||||
) -> Result<(Vec<ManagedVersion>, Pagination)> {
|
||||
Ok(match self {
|
||||
Self::Modrinth => {
|
||||
let data = modrinth::get_all_versions(project_id, game_versions, loaders, page, page_size).await?;
|
||||
(data.0.into_iter().map(Into::into).collect(), data.1)
|
||||
}
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
)
|
||||
|
||||
Self::Curseforge => {
|
||||
let data = curseforge::get_all_versions(project_id, game_versions, loaders, page, page_size).await?;
|
||||
(data.0.into_iter().map(Into::into).collect(), data.1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_versions(&self, pkg_id: &str, versions: Vec<String>) -> Result<Vec<ManagedVersion>> {
|
||||
pub async fn get_versions(&self, versions: Vec<String>) -> Result<Vec<ManagedVersion>> {
|
||||
Ok(match self {
|
||||
Self::Modrinth => modrinth::get_versions(versions),
|
||||
Self::Curseforge => todo!(),
|
||||
}
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect())
|
||||
Self::Modrinth => modrinth::get_versions(versions)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
Self::Curseforge => curseforge::get_versions(versions)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_version(&self, pkg_id: &str, version_id: &str) -> Result<ManagedVersion> {
|
||||
|
|
@ -151,25 +164,21 @@ impl Providers {
|
|||
|
||||
pub async fn get_authors(&self, author: &Author) -> Result<Vec<ManagedUser>> {
|
||||
Ok(match author {
|
||||
Author::Team { .. } => {
|
||||
match self {
|
||||
Self::Modrinth => modrinth::get_authors(author).await?,
|
||||
_ => return Err(anyhow::anyhow!("{} does not support teams", self).into()),
|
||||
}
|
||||
Author::Team { .. } => match self {
|
||||
Self::Modrinth => modrinth::get_authors(author).await?,
|
||||
_ => return Err(anyhow::anyhow!("{} does not support teams", self).into()),
|
||||
},
|
||||
Author::Users(users) => users.to_owned()
|
||||
Author::Users(users) => users.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_package_body(&self, body: &PackageBody) -> Result<String> {
|
||||
Ok(match body {
|
||||
PackageBody::Url(url) => {
|
||||
match self {
|
||||
Self::Curseforge => curseforge::get_package_body(url.to_owned()).await?,
|
||||
_ => return Err(anyhow::anyhow!("{} does not support direct URLs", self).into()),
|
||||
}
|
||||
PackageBody::Url(url) => match self {
|
||||
Self::Curseforge => curseforge::get_package_body(url.to_owned()).await?,
|
||||
_ => return Err(anyhow::anyhow!("{} does not support direct URLs", self).into()),
|
||||
},
|
||||
PackageBody::Markdown(markdown) => markdown.to_owned()
|
||||
PackageBody::Markdown(markdown) => markdown.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ use std::fmt::{Display, Formatter};
|
|||
|
||||
use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType};
|
||||
use crate::store::{
|
||||
Author, License, ManagedVersionFile, PackageFile, PackageSide, ProviderSearchResults,
|
||||
SearchResult,
|
||||
Author, License, ManagedVersionFile, ManagedVersionReleaseType, PackageFile, PackageSide, ProviderSearchResults, SearchResult
|
||||
};
|
||||
use crate::utils::http::fetch;
|
||||
use crate::utils::pagination::Pagination;
|
||||
use crate::{Result, State};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
|
@ -145,7 +145,7 @@ pub struct File {
|
|||
pub url: String,
|
||||
pub filename: String,
|
||||
pub primary: bool,
|
||||
pub size: u32,
|
||||
pub size: u64,
|
||||
pub file_type: Option<PackageFile>,
|
||||
}
|
||||
|
||||
|
|
@ -171,15 +171,16 @@ impl From<ModrinthVersion> for ManagedVersion {
|
|||
name: value.name,
|
||||
|
||||
featured: value.featured,
|
||||
version_id: value.version_number,
|
||||
version_display: value.version_number,
|
||||
changelog: value.changelog,
|
||||
changelog_url: value.changelog_url,
|
||||
|
||||
published: value.date_published,
|
||||
downloads: value.downloads,
|
||||
version_type: value.version_type,
|
||||
version_type: ManagedVersionReleaseType::from(value.version_type),
|
||||
|
||||
files: value.files.into_iter().map(Into::into).collect(),
|
||||
is_available: true,
|
||||
deps: vec![], // TODO [`ManagedDependency`]?
|
||||
game_versions: value.game_versions,
|
||||
loaders: value
|
||||
|
|
@ -439,7 +440,9 @@ pub async fn get_all_versions(
|
|||
project_id: &str,
|
||||
game_versions: Option<Vec<String>>,
|
||||
loaders: Option<Vec<Loader>>,
|
||||
) -> Result<Vec<ModrinthVersion>> {
|
||||
page: Option<u32>,
|
||||
page_size: Option<u16>,
|
||||
) -> Result<(Vec<ModrinthVersion>, Pagination)> {
|
||||
let mut url = url::Url::parse(format_url!("/project/{}/version", project_id).as_str())?;
|
||||
if let Some(game_versions) = game_versions {
|
||||
url.query_pairs_mut().append_pair(
|
||||
|
|
@ -467,14 +470,26 @@ pub async fn get_all_versions(
|
|||
);
|
||||
}
|
||||
|
||||
Ok(serde_json::from_slice(
|
||||
let data = serde_json::from_slice::<Vec<ModrinthVersion>>(
|
||||
&fetch(
|
||||
format_url!("/project/{}/version", project_id).as_str(),
|
||||
None,
|
||||
&State::get().await?.fetch_semaphore,
|
||||
)
|
||||
.await?,
|
||||
)?)
|
||||
)?;
|
||||
|
||||
let data_len = data.len() as u32;
|
||||
|
||||
Ok((
|
||||
data,
|
||||
Pagination {
|
||||
index: page.unwrap_or(1),
|
||||
page_size: page_size.unwrap_or(10),
|
||||
result_count: data_len,
|
||||
total_count: data_len,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_versions(versions: Vec<String>) -> Result<Vec<ModrinthVersion>> {
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ use tokio::fs::DirEntry;
|
|||
|
||||
use super::{ClusterPath, Clusters, Directories, PackagePath};
|
||||
|
||||
// TODO: Curseforge, Modrinth, SkyClient integration
|
||||
|
||||
/// Represents types of packages handled by the launcher.
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
|
|
@ -554,7 +552,7 @@ impl PackageMetadata {
|
|||
Self::Managed {
|
||||
package_id: package.id,
|
||||
version_id: version.id,
|
||||
version_formatted: version.version_id,
|
||||
version_formatted: version.version_display,
|
||||
title: package.title,
|
||||
provider: package.provider,
|
||||
package_type: package.package_type,
|
||||
|
|
@ -662,15 +660,16 @@ pub struct ManagedVersion {
|
|||
pub name: String,
|
||||
|
||||
pub featured: bool,
|
||||
pub version_id: String,
|
||||
pub version_display: String,
|
||||
pub changelog: String,
|
||||
pub changelog_url: Option<String>,
|
||||
|
||||
pub published: DateTime<Utc>,
|
||||
pub downloads: u32,
|
||||
pub version_type: String,
|
||||
pub version_type: ManagedVersionReleaseType,
|
||||
|
||||
pub files: Vec<ManagedVersionFile>,
|
||||
pub is_available: bool,
|
||||
pub deps: Vec<ManagedDependency>,
|
||||
pub game_versions: Vec<String>,
|
||||
pub loaders: Vec<Loader>,
|
||||
|
|
@ -683,6 +682,39 @@ impl ManagedVersion {
|
|||
}
|
||||
}
|
||||
|
||||
/// Universal version release type
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum ManagedVersionReleaseType {
|
||||
#[default]
|
||||
Release,
|
||||
Alpha,
|
||||
Beta,
|
||||
Snapshot,
|
||||
}
|
||||
|
||||
impl From<String> for ManagedVersionReleaseType {
|
||||
fn from(s: String) -> Self {
|
||||
match s.to_lowercase().as_str() {
|
||||
"alpha" => Self::Alpha,
|
||||
"beta" => Self::Beta,
|
||||
"snapshot" => Self::Snapshot,
|
||||
_ => Self::Release,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for ManagedVersionReleaseType {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::Release => "Release",
|
||||
Self::Alpha => "Alpha",
|
||||
Self::Beta => "Beta",
|
||||
Self::Snapshot => "Snapshot",
|
||||
}.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Universal interface for managed package files.
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
|
||||
|
|
@ -691,7 +723,7 @@ pub struct ManagedVersionFile {
|
|||
pub file_name: String,
|
||||
pub primary: bool,
|
||||
|
||||
pub size: u32,
|
||||
pub size: u64,
|
||||
pub file_type: Option<PackageFile>,
|
||||
pub hashes: HashMap<String, String>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,13 +297,7 @@ impl Processor {
|
|||
if let Some(process) = self.get(key) {
|
||||
let process = process.clone();
|
||||
let process = process.write().await;
|
||||
if process
|
||||
.current_child
|
||||
.write()
|
||||
.await
|
||||
.try_wait()?
|
||||
.is_none()
|
||||
{
|
||||
if process.current_child.write().await.try_wait()?.is_none() {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
|
|
@ -334,13 +328,7 @@ impl Processor {
|
|||
if let Some(process) = self.get(key) {
|
||||
let process = process.clone();
|
||||
let process = process.write().await;
|
||||
if process
|
||||
.current_child
|
||||
.write()
|
||||
.await
|
||||
.try_wait()?
|
||||
.is_none()
|
||||
{
|
||||
if process.current_child.write().await.try_wait()?.is_none() {
|
||||
clusters.push(process.cluster_path.clone());
|
||||
}
|
||||
}
|
||||
|
|
@ -355,13 +343,7 @@ impl Processor {
|
|||
if let Some(process) = self.get(key) {
|
||||
let process = process.clone();
|
||||
let process = process.write().await;
|
||||
if process
|
||||
.current_child
|
||||
.write()
|
||||
.await
|
||||
.try_wait()?
|
||||
.is_none()
|
||||
{
|
||||
if process.current_child.write().await.try_wait()?.is_none() {
|
||||
if let Some(cluster) = cluster::get(&process.cluster_path.clone()).await? {
|
||||
clusters.push(cluster);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
pub mod http;
|
||||
pub mod java;
|
||||
pub mod watcher;
|
||||
pub mod pagination;
|
||||
pub mod watcher;
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
pub mod window;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,18 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[allow(dead_code)]
|
||||
pub struct Pagination {
|
||||
pub index: u32,
|
||||
pub page_size: u16,
|
||||
pub result_count: u32,
|
||||
pub total_count: u32,
|
||||
}
|
||||
|
||||
pub fn get_page_offset(page: Option<u32>, page_size: Option<u16>) -> (u32, u16) {
|
||||
let page = page.unwrap_or(1);
|
||||
let page = page.unwrap_or(0);
|
||||
let page_size = page_size.unwrap_or(10);
|
||||
let offset = (page - 1) * page_size as u32;
|
||||
(offset, page_size)
|
||||
|
|
@ -7,4 +20,4 @@ pub fn get_page_offset(page: Option<u32>, page_size: Option<u16>) -> (u32, u16)
|
|||
|
||||
pub fn get_page_count(total: u32, page_size: u16) -> u32 {
|
||||
(total as f64 / page_size as f64).ceil() as u32
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue