Track external mods properly
This commit is contained in:
parent
18c37f8661
commit
09749ea78b
9 changed files with 209 additions and 31 deletions
|
|
@ -6,6 +6,10 @@ pub async fn initialize_oneclient() {
|
|||
tracing::error!("failed to initialize clusters: {err}");
|
||||
}
|
||||
|
||||
if let Err(err) = onelauncher_core::api::cluster::sync_clusters().await {
|
||||
tracing::error!("failed to sync clusters: {err}");
|
||||
}
|
||||
|
||||
tokio::spawn(async {
|
||||
bundles::BundlesManager::get().await;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ export type ProcessPayloadKind = { type: "Starting"; command: string } | { type:
|
|||
|
||||
export type ProfileUpdate = { res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null }
|
||||
|
||||
export type Provider = "Modrinth" | "CurseForge" | "SkyClient"
|
||||
export type Provider = "Modrinth" | "CurseForge" | "SkyClient" | "Local"
|
||||
|
||||
export type Resolution = { width: number; height: number }
|
||||
|
||||
|
|
@ -303,19 +303,8 @@ export type VersionType =
|
|||
*/
|
||||
"old_beta"
|
||||
|
||||
const ARGS_MAP = { 'oneclient':'{"installUpdate":[],"getClustersGroupedByMajor":[],"checkForUpdate":[],"getBundlesFor":["cluster_id"],"getVersions":[]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'core':'{"getLogByName":["id","name"],"getClusterById":["id"],"getLogs":["id"],"setDiscordRPCMessage":["message"],"getClusters":[],"getWorlds":["id"],"createSettingsProfile":["name"],"getUsers":[],"isClusterRunning":["cluster_id"],"fetchMinecraftProfile":["uuid"],"getPackage":["provider","slug"],"getScreenshots":["id"],"getRunningProcessesByClusterId":["cluster_id"],"getUsersFromAuthor":["provider","author"],"getLinkedPackages":["cluster_id"],"openMsaLogin":[],"getDefaultUser":["fallback"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"writeSettings":["setting"],"getUser":["uuid"],"readSettings":[],"fetchLoggedInProfile":["access_token"],"getLoadersForVersion":["mc_version"],"open":["input"],"killProcess":["pid"],"installModpack":["modpack","cluster_id"],"setClusterStage":["id","stage"],"getRunningProcesses":[],"getProfileOrDefault":["name"],"updateClusterProfile":["name","profile"],"removeCluster":["id"],"getGameVersions":[],"getPackageBody":["provider","body"],"getMultiplePackages":["provider","slugs"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"changeSkin":["access_token","skin_url","skin_variant"],"changeCape":["access_token","cape_uuid"],"removeUser":["uuid"],"setDefaultUser":["uuid"],"launchCluster":["id","uuid","search_for_java"],"updateClusterById":["id","request"],"createCluster":["options"],"getGlobalProfile":[],"searchPackages":["provider","query"],"removePackage":["cluster_id","package_hash"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"removeCape":["access_token"],"convertUsernameUUID":["username_uuid"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'debug':'{"getArch":[],"getBuildTimestamp":[],"getPlatform":[],"getType":[],"getLocale":[],"getOsVersion":[],"openDevTools":[],"getGitCommitHash":[],"isInDev":[],"getPackageVersion":[],"getFamily":[]}' }
|
||||
export type Router = { 'debug': { openDevTools: () => Promise<void>,
|
||||
isInDev: () => Promise<boolean>,
|
||||
getArch: () => Promise<string>,
|
||||
getFamily: () => Promise<string>,
|
||||
getLocale: () => Promise<string | null>,
|
||||
getType: () => Promise<string>,
|
||||
getPlatform: () => Promise<string>,
|
||||
getOsVersion: () => Promise<string>,
|
||||
getGitCommitHash: () => Promise<string>,
|
||||
getBuildTimestamp: () => Promise<string>,
|
||||
getPackageVersion: () => Promise<string> },
|
||||
'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
const ARGS_MAP = { 'core':'{"searchPackages":["provider","query"],"getPackage":["provider","slug"],"getRunningProcessesByClusterId":["cluster_id"],"getWorlds":["id"],"getGameVersions":[],"getLogs":["id"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"installModpack":["modpack","cluster_id"],"getLinkedPackages":["cluster_id"],"createCluster":["options"],"setDefaultUser":["uuid"],"changeSkin":["access_token","skin_url","skin_variant"],"convertUsernameUUID":["username_uuid"],"readSettings":[],"getLogByName":["id","name"],"updateClusterProfile":["name","profile"],"getLoadersForVersion":["mc_version"],"getDefaultUser":["fallback"],"fetchMinecraftProfile":["uuid"],"getScreenshots":["id"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getProfileOrDefault":["name"],"launchCluster":["id","uuid","search_for_java"],"isClusterRunning":["cluster_id"],"removePackage":["cluster_id","package_hash"],"getClusters":[],"getUsers":[],"setClusterStage":["id","stage"],"getClusterById":["id"],"createSettingsProfile":["name"],"getUser":["uuid"],"changeCape":["access_token","cape_uuid"],"fetchLoggedInProfile":["access_token"],"removeCape":["access_token"],"getUsersFromAuthor":["provider","author"],"removeCluster":["id"],"setDiscordRPCMessage":["message"],"getMultiplePackages":["provider","slugs"],"removeUser":["uuid"],"writeSettings":["setting"],"updateClusterById":["id","request"],"getPackageBody":["provider","body"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"open":["input"],"getRunningProcesses":[],"openMsaLogin":[],"killProcess":["pid"],"getGlobalProfile":[]}', 'oneclient':'{"getBundlesFor":["cluster_id"],"installUpdate":[],"getVersions":[],"getClustersGroupedByMajor":[],"checkForUpdate":[]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'debug':'{"openDevTools":[],"getGitCommitHash":[],"getPackageVersion":[],"getType":[],"getPlatform":[],"getLocale":[],"isInDev":[],"getArch":[],"getOsVersion":[],"getBuildTimestamp":[],"getFamily":[]}' }
|
||||
export type Router = { 'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
message: (event: MessagePayload) => Promise<void>,
|
||||
process: (event: ProcessPayload) => Promise<void> },
|
||||
'folders': { fromCluster: (folderName: string) => Promise<string>,
|
||||
|
|
@ -369,6 +358,17 @@ removeCape: (accessToken: string) => Promise<MojangFullPlayerProfile>,
|
|||
convertUsernameUUID: (usernameUuid: string) => Promise<MowojangProfile>,
|
||||
setDiscordRPCMessage: (message: string) => Promise<null>,
|
||||
open: (input: string) => Promise<null> },
|
||||
'debug': { openDevTools: () => Promise<void>,
|
||||
isInDev: () => Promise<boolean>,
|
||||
getArch: () => Promise<string>,
|
||||
getFamily: () => Promise<string>,
|
||||
getLocale: () => Promise<string | null>,
|
||||
getType: () => Promise<string>,
|
||||
getPlatform: () => Promise<string>,
|
||||
getOsVersion: () => Promise<string>,
|
||||
getGitCommitHash: () => Promise<string>,
|
||||
getBuildTimestamp: () => Promise<string>,
|
||||
getPackageVersion: () => Promise<string> },
|
||||
'oneclient': { getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
|
||||
getBundlesFor: (clusterId: number) => Promise<ModpackArchive[]>,
|
||||
getVersions: () => Promise<OnlineClusterManifest>,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use onelauncher_entity::clusters;
|
||||
use chrono::Utc;
|
||||
use futures::future::join_all;
|
||||
use onelauncher_entity::package::{PackageType, Provider};
|
||||
use onelauncher_entity::{clusters, packages};
|
||||
use sea_orm::{ActiveValue, Iterable};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::api::packages::provider::ProviderExt;
|
||||
use crate::error::{DaoError, LauncherResult};
|
||||
use crate::send_error;
|
||||
use crate::store::Dirs;
|
||||
use crate::utils::crypto::HashAlgorithm;
|
||||
use crate::utils::io;
|
||||
|
||||
use super::dao::{self, ClusterId};
|
||||
|
|
@ -88,7 +94,151 @@ pub async fn sync_cluster(cluster: &clusters::Model) -> LauncherResult<SyncActio
|
|||
return Ok(SyncAction::MissingFs);
|
||||
}
|
||||
|
||||
// TODO: sync packages
|
||||
let mut missing_hashes = HashMap::new();
|
||||
let mut found_hashes = HashSet::new();
|
||||
|
||||
for package_type in PackageType::iter() {
|
||||
let dir = path.join(package_type.folder_name());
|
||||
if !dir.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut stream = io::read_dir(&dir).await?;
|
||||
while let Ok(Some(entry)) = stream.next_entry().await {
|
||||
let file_path = entry.path();
|
||||
if !file_path.is_file() {
|
||||
continue;
|
||||
}
|
||||
// Skip hidden files
|
||||
if file_path
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().starts_with('.'))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let hash = HashAlgorithm::Sha1.hash_file(&file_path).await?;
|
||||
|
||||
if let Some(package) =
|
||||
crate::api::packages::dao::get_package_by_hash(hash.clone()).await?
|
||||
{
|
||||
if !crate::api::packages::dao::is_package_linked_to_cluster(&package, cluster)
|
||||
.await?
|
||||
{
|
||||
crate::api::packages::dao::link_package_to_cluster(&package, cluster).await?;
|
||||
}
|
||||
found_hashes.insert(hash);
|
||||
} else {
|
||||
missing_hashes.insert(hash, (file_path, package_type.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !missing_hashes.is_empty() {
|
||||
let hashes: Vec<String> = missing_hashes.keys().cloned().collect();
|
||||
|
||||
let futures = Provider::get_providers().iter().map(|&provider| {
|
||||
let hashes = hashes.clone();
|
||||
async move {
|
||||
match provider.get_versions_by_hashes(&hashes).await {
|
||||
Ok(versions) => Some((provider, versions)),
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
"failed to get versions from provider {}: {}",
|
||||
provider,
|
||||
err
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let results = join_all(futures).await;
|
||||
|
||||
let mut found_versions = HashMap::new();
|
||||
for result in results.into_iter().flatten() {
|
||||
let (provider, versions) = result;
|
||||
for (hash, version) in versions {
|
||||
found_versions.entry(hash).or_insert((version, provider));
|
||||
}
|
||||
}
|
||||
|
||||
for (hash, (file_path, package_type)) in &missing_hashes {
|
||||
if let Some((version, provider)) = found_versions.get(hash) {
|
||||
// Find the file in version.files
|
||||
let file = version
|
||||
.files
|
||||
.iter()
|
||||
.find(|f| f.sha1 == *hash)
|
||||
.ok_or_else(|| anyhow::anyhow!("File hash mismatch"))?;
|
||||
|
||||
let model = packages::ActiveModel {
|
||||
hash: ActiveValue::Set(hash.clone()),
|
||||
file_name: ActiveValue::Set(file.file_name.clone()),
|
||||
version_id: ActiveValue::Set(version.version_id.clone()),
|
||||
published_at: ActiveValue::Set(version.published),
|
||||
display_name: ActiveValue::Set(version.display_name.clone()),
|
||||
display_version: ActiveValue::Set(version.display_version.clone()),
|
||||
package_type: ActiveValue::Set(package_type.clone()),
|
||||
provider: ActiveValue::Set(*provider),
|
||||
package_id: ActiveValue::Set(version.project_id.clone()),
|
||||
mc_versions: ActiveValue::Set(version.mc_versions.clone().into()),
|
||||
mc_loader: ActiveValue::Set(version.loaders.clone().into()),
|
||||
icon: ActiveValue::Set(None), // TODO: Fetch icon
|
||||
};
|
||||
|
||||
let package = match crate::api::packages::dao::insert_package(model).await {
|
||||
Ok(package) => package,
|
||||
Err(err) => {
|
||||
tracing::error!("failed to insert package {}: {}", hash, err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(err) =
|
||||
crate::api::packages::dao::link_package_to_cluster(&package, cluster).await
|
||||
{
|
||||
tracing::error!("failed to link package {} to cluster: {}", hash, err);
|
||||
}
|
||||
} else {
|
||||
// Save as external package
|
||||
let file_name = file_path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let model = packages::ActiveModel {
|
||||
hash: ActiveValue::Set(hash.clone()),
|
||||
file_name: ActiveValue::Set(file_name.clone()),
|
||||
version_id: ActiveValue::Set(hash.clone()),
|
||||
published_at: ActiveValue::Set(Utc::now()),
|
||||
display_name: ActiveValue::Set(file_name.clone()),
|
||||
display_version: ActiveValue::Set("Unknown".to_string()),
|
||||
package_type: ActiveValue::Set(package_type.clone()),
|
||||
provider: ActiveValue::Set(Provider::Local),
|
||||
package_id: ActiveValue::Set(hash.clone()),
|
||||
mc_versions: ActiveValue::Set(vec![].into()),
|
||||
mc_loader: ActiveValue::Set(vec![].into()),
|
||||
icon: ActiveValue::Set(None),
|
||||
};
|
||||
|
||||
let package = match crate::api::packages::dao::insert_package(model).await {
|
||||
Ok(package) => package,
|
||||
Err(err) => {
|
||||
tracing::error!("failed to insert local package {}: {}", hash, err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(err) =
|
||||
crate::api::packages::dao::link_package_to_cluster(&package, cluster).await
|
||||
{
|
||||
tracing::error!("failed to link local package {} to cluster: {}", hash, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SyncAction::NoAction)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ pub async fn link_package_to_cluster(
|
|||
.into_active_model();
|
||||
|
||||
cluster_packages::Entity::insert(cluster_package)
|
||||
.on_conflict_do_nothing()
|
||||
.exec(db)
|
||||
.await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -144,16 +144,25 @@ pub async fn download_external_package(
|
|||
return Ok(Some(model));
|
||||
}
|
||||
|
||||
let dir = Dirs::get_clusters_dir()
|
||||
.await?
|
||||
.join(cluster.folder_name.clone())
|
||||
.join(package.package_type.folder_name())
|
||||
.join(package.name.clone());
|
||||
let model = packages::Model {
|
||||
hash: package.sha1.clone(),
|
||||
display_name: package.name.clone(),
|
||||
display_version: "Unknown".to_string(),
|
||||
file_name: package.name.clone(),
|
||||
version_id: package.sha1.clone(),
|
||||
published_at: chrono::Utc::now(),
|
||||
provider: Provider::Local,
|
||||
icon: None,
|
||||
package_id: package.sha1.clone(),
|
||||
mc_loader: vec![].into(),
|
||||
mc_versions: vec![].into(),
|
||||
package_type: package.package_type.clone(),
|
||||
};
|
||||
|
||||
http::download_advanced(
|
||||
Method::GET,
|
||||
&package.url,
|
||||
dir,
|
||||
model.path().await?,
|
||||
None,
|
||||
None,
|
||||
Some((HashAlgorithm::Sha1, &package.sha1)),
|
||||
|
|
@ -161,7 +170,9 @@ pub async fn download_external_package(
|
|||
)
|
||||
.await?;
|
||||
|
||||
Ok(None)
|
||||
let inserted_model = dao::insert_package(model.into()).await?;
|
||||
|
||||
Ok(Some(inserted_model))
|
||||
}
|
||||
|
||||
/// Links a package to a cluster on the file system and in database.
|
||||
|
|
@ -186,7 +197,7 @@ pub async fn link_package(
|
|||
}
|
||||
|
||||
tracing::trace!("checking compatibility of package with cluster");
|
||||
if !skip_compatibility.unwrap_or(false) {
|
||||
if !skip_compatibility.unwrap_or(false) && package.provider != Provider::Local {
|
||||
if !package
|
||||
.mc_loader
|
||||
.iter()
|
||||
|
|
@ -239,6 +250,9 @@ pub async fn link_many_packages_to_cluster(
|
|||
packages
|
||||
.iter()
|
||||
.filter(|p| {
|
||||
if p.provider == Provider::Local {
|
||||
return true;
|
||||
}
|
||||
p.mc_loader
|
||||
.iter()
|
||||
.any(|v| cluster.mc_loader.compatible_with(v))
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ pub(super) async fn download_and_link_packages(
|
|||
}
|
||||
}
|
||||
ModpackFileKind::External(package) => {
|
||||
if let Err(e) = api::packages::download_external_package(
|
||||
match api::packages::download_external_package(
|
||||
package,
|
||||
cluster,
|
||||
None,
|
||||
|
|
@ -236,7 +236,9 @@ pub(super) async fn download_and_link_packages(
|
|||
)
|
||||
.await
|
||||
{
|
||||
errors.push(e);
|
||||
Ok(Some(model)) => packages_to_link.push(model),
|
||||
Ok(None) => {}
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ macro_rules! delegate {
|
|||
Provider::Modrinth => ModrinthProviderImpl.$method($($arg),*).await,
|
||||
Provider::CurseForge => CurseForgeProviderImpl.$method($($arg),*).await,
|
||||
// Provider::SkyClient => SkyClientProviderImpl.$method($($arg),*).await,
|
||||
_ => todo!("skyclient not implement")
|
||||
_ => Err(anyhow::anyhow!("Provider {} not supported", $self).into()),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -748,7 +748,7 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
package: ExternalPackage,
|
||||
cluster_id: ClusterId,
|
||||
force: Option<bool>,
|
||||
skip_compatibility: Option<bool>,
|
||||
skip_compatibility: Option<bool>, // default: true
|
||||
) -> LauncherResult<Option<packages::Model>> {
|
||||
let cluster = api::cluster::dao::get_cluster_by_id(cluster_id)
|
||||
.await?
|
||||
|
|
@ -758,11 +758,15 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
&package,
|
||||
&cluster,
|
||||
force,
|
||||
skip_compatibility,
|
||||
skip_compatibility.or(Some(true)),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(model) = &model {
|
||||
api::packages::link_package(model, &cluster, skip_compatibility.or(Some(true))).await?;
|
||||
}
|
||||
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,12 +65,13 @@ impl ToString for PackageType {
|
|||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, EnumIter, DeriveActiveEnum)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, EnumIter, DeriveActiveEnum)]
|
||||
#[sea_orm(rs_type = "u8", db_type = "Integer")]
|
||||
pub enum Provider {
|
||||
Modrinth = 0,
|
||||
CurseForge = 1,
|
||||
SkyClient = 2,
|
||||
Local = 3,
|
||||
}
|
||||
|
||||
impl Display for Provider {
|
||||
|
|
@ -86,6 +87,7 @@ impl Provider {
|
|||
Self::Modrinth => "Modrinth",
|
||||
Self::CurseForge => "CurseForge",
|
||||
Self::SkyClient => "SkyClient",
|
||||
Self::Local => "Local",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,6 +98,7 @@ impl Provider {
|
|||
Self::Modrinth => "https://modrinth.com/",
|
||||
Self::CurseForge => "https://curseforge.com/",
|
||||
Self::SkyClient => "https://skyclient.co/",
|
||||
Self::Local => "",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue