feat: SkyClient Part 2
This commit is contained in:
parent
f49dc71e9d
commit
e973812aaf
10 changed files with 225 additions and 54 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -3212,6 +3212,12 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
|
|
@ -3819,6 +3825,7 @@ dependencies = [
|
|||
"interpulse",
|
||||
"iota_stronghold",
|
||||
"lazy_static",
|
||||
"md5",
|
||||
"murmur2",
|
||||
"notify",
|
||||
"notify-debouncer-mini",
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ flate2 = { version = "1.0" }
|
|||
sha1_smol = { version = "1.0", features = [ "std" ] }
|
||||
sha2 = { version = "0.10" }
|
||||
murmur2 = { version = "0.1.0" }
|
||||
md5 = { version = "0.7.0" }
|
||||
bytes = { version = "1.7" }
|
||||
|
||||
# # ============= UTILITY ============= # #
|
||||
|
|
|
|||
2
packages/client/src/bindings.ts
generated
2
packages/client/src/bindings.ts
generated
|
|
@ -865,7 +865,7 @@ export interface ManagedUser { id: string; username: string; url?: string | null
|
|||
/**
|
||||
* Universal managed package version of a package.
|
||||
*/
|
||||
export interface ManagedVersion { id: string; package_id: string; author: string; name: string; featured: boolean; version_display: string; changelog: string; changelog_url: string | null; published: string; downloads: number; version_type: ManagedVersionReleaseType; files: ManagedVersionFile[]; is_available: boolean; deps: ManagedDependency[]; game_versions: string[]; loaders: Loader[] };
|
||||
export interface ManagedVersion { id: string; package_id: string; author: string; name: string; featured: boolean; version_display: string; changelog: string; changelog_url: string | null; published: string | null; downloads: number; version_type: ManagedVersionReleaseType; files: ManagedVersionFile[]; is_available: boolean; deps: ManagedDependency[]; game_versions: string[]; loaders: Loader[] };
|
||||
/**
|
||||
* Universal interface for managed package files.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ dirs = { workspace = true }
|
|||
sha1_smol = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
murmur2 = { workspace = true }
|
||||
md5 = { workspace = true }
|
||||
async_zip = { workspace = true }
|
||||
discord-rich-presence = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ impl Into<ManagedVersion> for ModFile {
|
|||
is_available: self.is_available && files.len() > 0,
|
||||
files,
|
||||
game_versions: game_versions,
|
||||
published: self.file_date,
|
||||
published: Some(self.file_date),
|
||||
version_display: self.display_name,
|
||||
version_type: self.release_type.into(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
//! Utilities for searching and downloading content packages to `OneLauncher`.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use modrinth::{Facet, FacetOperation};
|
||||
use reqwest::Method;
|
||||
|
|
@ -11,6 +12,7 @@ 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::crypto;
|
||||
use crate::utils::http::fetch_json;
|
||||
use crate::utils::pagination::Pagination;
|
||||
use crate::{Result, State};
|
||||
|
|
@ -178,7 +180,10 @@ impl Providers {
|
|||
(data.0.into_iter().map(Into::into).collect(), data.1)
|
||||
}
|
||||
|
||||
Self::SkyClient => todo!(),
|
||||
Self::SkyClient => {
|
||||
let data = skyclient::get_all_versions(project_id, game_versions, loaders, page, page_size).await?;
|
||||
(data.0.into_iter().map(Into::into).collect(), data.1)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +199,7 @@ impl Providers {
|
|||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
Self::SkyClient => todo!(),
|
||||
Self::SkyClient => skyclient::get_versions(versions).await?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -230,9 +235,20 @@ impl Providers {
|
|||
.into_iter()
|
||||
.map(|(hash, version)| (hash, version.into()))
|
||||
.collect(),
|
||||
Self::SkyClient => todo!(),
|
||||
Self::SkyClient => skyclient::get_versions_by_hashes(hashes).await?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hash_file(&self, path: &PathBuf) -> Result<String> {
|
||||
match self {
|
||||
Providers::Modrinth => crypto::sha1_file(path),
|
||||
Providers::SkyClient => crypto::md5_file(path),
|
||||
Providers::Curseforge => {
|
||||
let hash = crypto::murmur2_file(path)?;
|
||||
Ok(hash.to_string())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ impl From<ModrinthVersion> for ManagedVersion {
|
|||
changelog: value.changelog,
|
||||
changelog_url: value.changelog_url,
|
||||
|
||||
published: value.date_published,
|
||||
published: Some(value.date_published),
|
||||
downloads: value.downloads,
|
||||
version_type: ManagedVersionReleaseType::from(value.version_type),
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use tokio::sync::{OnceCell, RwLock};
|
||||
|
||||
use crate::{data::{Loader, ManagedPackage, ManagedUser, ManagedVersion}, store::{ProviderSearchResults, SearchResult}, utils::{http, pagination::Pagination}, Result, State};
|
||||
use crate::{data::{Loader, ManagedPackage, ManagedUser, ManagedVersion}, store::{ManagedVersionFile, ProviderSearchResults, SearchResult}, utils::{http, pagination::Pagination}, Result, State};
|
||||
|
||||
async fn fetch<T: DeserializeOwned>(url: &str) -> Result<T> {
|
||||
let state = State::get().await?;
|
||||
|
|
@ -143,6 +143,37 @@ pub struct SkyClientModVersion {
|
|||
pub mod_id: Option<String>,
|
||||
}
|
||||
|
||||
fn get_managed_version(m: &SkyClientMod, v: &SkyClientModVersion) -> ManagedVersion {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(String::from("md5"), v.hash.clone());
|
||||
|
||||
ManagedVersion {
|
||||
id: v.version.clone(),
|
||||
package_id: m.id.clone(),
|
||||
author: m.creator.clone(),
|
||||
changelog_url: None,
|
||||
changelog: String::new(),
|
||||
deps: vec![],
|
||||
downloads: 0,
|
||||
featured: false,
|
||||
files: vec![ManagedVersionFile {
|
||||
file_name: v.file.clone(),
|
||||
url: v.url.clone(),
|
||||
file_type: None,
|
||||
hashes: map,
|
||||
primary: true,
|
||||
size: 0,
|
||||
}],
|
||||
game_versions: v.game_versions.clone(),
|
||||
is_available: true,
|
||||
loaders: v.loaders.iter().map(|l| Loader::from_string(l.to_owned())).collect(),
|
||||
name: format!("{} v{}", m.display, v.version),
|
||||
published: None,
|
||||
version_display: v.version.clone(),
|
||||
version_type: crate::store::ManagedVersionReleaseType::Release,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(id: &str) -> Result<SkyClientMod> {
|
||||
let store = SkyClientStore::get().await?;
|
||||
|
||||
|
|
@ -181,45 +212,97 @@ pub async fn get_multiple(slug_or_ids: &[String]) -> Result<Vec<SkyClientMod>> {
|
|||
Ok(results)
|
||||
}
|
||||
|
||||
// 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>, Pagination)> {
|
||||
// let store = SkyClientStore::get().await?;
|
||||
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>, Pagination)> {
|
||||
let store = SkyClientStore::get().await?;
|
||||
|
||||
// let mods = match &store.mods {
|
||||
// Some(mods) => mods,
|
||||
// None => return Ok((vec![], Pagination::default()))
|
||||
// };
|
||||
let mods = match &store.mods {
|
||||
Some(mods) => mods,
|
||||
None => return Ok((vec![], Pagination::default()))
|
||||
};
|
||||
|
||||
let mut pagination = Pagination::default();
|
||||
let mut versions = vec![];
|
||||
|
||||
// let mut versions = vec![];
|
||||
for m in mods {
|
||||
if m.id == project_id {
|
||||
for v in &m.versions {
|
||||
let mut can_add = true;
|
||||
|
||||
// for m in mods {
|
||||
// if m.id == project_id {
|
||||
// for v in m.versions {
|
||||
// let mut can_add = true;
|
||||
if let Some(game_versions) = &game_versions {
|
||||
if !game_versions.is_empty() {
|
||||
can_add = v.game_versions.iter().any(|gv| game_versions.contains(gv))
|
||||
}
|
||||
}
|
||||
|
||||
// if let Some(game_versions) = &game_versions {
|
||||
// can_add = v.game_versions.iter().any(|gv| game_versions.contains(gv))
|
||||
// }
|
||||
if can_add {
|
||||
if let Some(loaders) = &loaders {
|
||||
if !loaders.is_empty() {
|
||||
can_add = v.loaders.iter().any(|l| loaders.contains(&Loader::from_string(l.to_owned())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if can_add {
|
||||
// if let Some(loaders) = &loaders {
|
||||
// can_add = v.loaders.iter().any(|l| loaders.contains(&Loader::from_string(l)))
|
||||
// }
|
||||
// }
|
||||
if can_add {
|
||||
if page_size.map(|ps| versions.len() < ps as usize).unwrap_or(true) && page.map(|p| p <= pagination.index).unwrap_or(true) {
|
||||
versions.push(get_managed_version(m, v));
|
||||
}
|
||||
|
||||
// if can_add {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
pagination.total_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((versions, pagination))
|
||||
}
|
||||
|
||||
pub async fn get_versions(versions: Vec<String>) -> Result<Vec<ManagedVersion>> {
|
||||
let store = SkyClientStore::get().await?;
|
||||
|
||||
let mods = match &store.mods {
|
||||
Some(mods) => mods,
|
||||
None => return Ok(vec![])
|
||||
};
|
||||
|
||||
let mut results = vec![];
|
||||
|
||||
for m in mods {
|
||||
for v in &m.versions {
|
||||
if versions.contains(&v.version) {
|
||||
results.push(get_managed_version(m, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub async fn get_versions_by_hashes(hashes: Vec<String>) -> Result<HashMap<String, ManagedVersion>> {
|
||||
let store = SkyClientStore::get().await?;
|
||||
|
||||
let mods = match &store.mods {
|
||||
Some(mods) => mods,
|
||||
None => return Ok(HashMap::new())
|
||||
};
|
||||
|
||||
let mut results = HashMap::new();
|
||||
|
||||
for m in mods {
|
||||
for v in &m.versions {
|
||||
if hashes.contains(&v.hash) {
|
||||
results.insert(v.hash.clone(), get_managed_version(m, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub async fn search(
|
||||
query: Option<String>,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use onelauncher_utils::io;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::vec;
|
||||
use tokio::fs::DirEntry;
|
||||
|
||||
use super::{ClusterPath, Clusters, Directories, PackagePath};
|
||||
|
|
@ -471,21 +472,21 @@ impl PackageManager {
|
|||
|
||||
// Infer any packages
|
||||
if packages_to_infer.len() > 0 {
|
||||
let not_found = infer_provider(Providers::Modrinth, package_type, packages, packages_to_infer, crypto::sha1_file).await;
|
||||
if let Some(not_found) = infer_provider(Providers::Modrinth, package_type, packages, packages_to_infer, crypto::sha1_file).await {
|
||||
if let Some(not_found) = infer_provider(Providers::SkyClient, package_type, packages, not_found, crypto::md5_file).await {
|
||||
if let Some(not_found) = infer_provider(Providers::Curseforge, package_type, packages, not_found, |path| {
|
||||
let hash = crypto::murmur2_file(path)?;
|
||||
Ok(hash.to_string())
|
||||
}).await {
|
||||
for path in not_found {
|
||||
tracing::warn!("failed to infer package: {:?}", path);
|
||||
|
||||
if let Some(not_found) = not_found {
|
||||
if let Some(not_found) = infer_provider(Providers::Curseforge, package_type, packages, not_found, |path| {
|
||||
let hash = crypto::murmur2_file(path)?;
|
||||
Ok(hash.to_string())
|
||||
}).await {
|
||||
for path in not_found {
|
||||
tracing::warn!("failed to infer package: {:?}", path);
|
||||
let package_path = PackagePath::new(&path);
|
||||
let meta = PackageMetadata::Unknown;
|
||||
let package = Package::new(&package_path, meta)?;
|
||||
|
||||
let package_path = PackagePath::new(&path);
|
||||
let meta = PackageMetadata::Unknown;
|
||||
let package = Package::new(&package_path, meta)?;
|
||||
|
||||
packages.insert(package_path, package);
|
||||
packages.insert(package_path, package);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -529,6 +530,49 @@ impl PackageManager {
|
|||
}
|
||||
}
|
||||
|
||||
// #[tracing::instrument(skip(packages))]
|
||||
// async fn infer_packages(
|
||||
// packages: &mut PackagesMap,
|
||||
// package_type: PackageType,
|
||||
// to_infer: HashSet<PathBuf>,
|
||||
// ) {
|
||||
// let infer_order = vec![
|
||||
// Providers::Modrinth,
|
||||
// Providers::SkyClient,
|
||||
// Providers::Curseforge,
|
||||
// ];
|
||||
|
||||
|
||||
// // if let Some(not_found) = infer_provider(Providers::Modrinth, package_type, packages, packages_to_infer, crypto::sha1_file).await {
|
||||
// // if let Some(not_found) = infer_provider(Providers::SkyClient, package_type, packages, not_found, crypto::md5_file).await {
|
||||
// // if let Some(not_found) = infer_provider(Providers::Curseforge, package_type, packages, not_found, |path| {
|
||||
// // let hash = crypto::murmur2_file(path)?;
|
||||
// // Ok(hash.to_string())
|
||||
// // }).await {
|
||||
// // }
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// let mut to_infer = to_infer;
|
||||
// for provider in infer_order {
|
||||
// if let Some(not_found) = infer_provider(provider, package_type, packages, to_infer, move|path| {
|
||||
// Ok(provider.hash_file(path))
|
||||
// }).await {
|
||||
// to_infer = not_found;
|
||||
// }
|
||||
// }
|
||||
|
||||
// for path in to_infer {
|
||||
// tracing::warn!("failed to infer package: {:?}", path);
|
||||
|
||||
// let package_path = PackagePath::new(&path);
|
||||
// let meta = PackageMetadata::Unknown;
|
||||
// if let Ok(package) = Package::new(&package_path, meta) {
|
||||
// packages.insert(package_path, package);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[tracing::instrument(skip(packages, to_infer))]
|
||||
async fn infer_provider(
|
||||
provider: Providers,
|
||||
|
|
@ -537,6 +581,8 @@ async fn infer_provider(
|
|||
to_infer: HashSet<PathBuf>,
|
||||
hash_fn: fn(&PathBuf) -> crate::Result<String>,
|
||||
) -> Option<HashSet<PathBuf>> {
|
||||
// TODO: Add ingress
|
||||
|
||||
let mut hash_map = HashMap::<String, PathBuf>::new();
|
||||
to_infer.iter().for_each(|path| {
|
||||
if let Ok(hash) = hash_fn(path) {
|
||||
|
|
@ -756,7 +802,7 @@ pub struct ManagedVersion {
|
|||
pub changelog: String,
|
||||
pub changelog_url: Option<String>,
|
||||
|
||||
pub published: DateTime<Utc>,
|
||||
pub published: Option<DateTime<Utc>>,
|
||||
pub downloads: u32,
|
||||
pub version_type: ManagedVersionReleaseType,
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,23 @@ pub fn sha1_files(files: Vec<&PathBuf>) -> Vec<Option<String>> {
|
|||
hashes
|
||||
}
|
||||
|
||||
pub fn mda5(bytes: &[u8]) -> String {
|
||||
let digest = md5::compute(bytes);
|
||||
format!("{:x}", digest)
|
||||
}
|
||||
|
||||
pub fn md5_file(file: &PathBuf) -> Result<String> {
|
||||
Ok(mda5(&std::fs::read(file)?))
|
||||
}
|
||||
|
||||
pub fn md5_files(files: Vec<&PathBuf>) -> Vec<Option<String>> {
|
||||
let mut hashes = Vec::new();
|
||||
for file in files {
|
||||
hashes.push(md5_file(file).ok());
|
||||
}
|
||||
hashes
|
||||
}
|
||||
|
||||
const CURSEFORGE_FINGERPRINT_SEED: u32 = 1;
|
||||
pub fn murmur2(bytes: &[u8]) -> u32 {
|
||||
let normalized = normalize_byte_array(bytes);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue