From 02f1aaa429d1b8fed7bde755b9b7229da1db87e0 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sun, 8 Sep 2024 12:53:18 +0200 Subject: [PATCH] fix: Package removal and syncing feat: Added fetching managed packages on ClusterMods Page --- apps/desktop/src/api/commands/mod.rs | 1 + apps/desktop/src/api/commands/package.rs | 12 +- .../src/ui/pages/cluster/ClusterMods.tsx | 150 ++++++++++++++---- apps/testing/examples/cluster_packages.rs | 21 ++- .../core/src/api/cluster/content/package.rs | 2 +- .../core/src/api/package/content/modrinth.rs | 2 +- packages/core/src/store/package.rs | 54 +++++-- 7 files changed, 181 insertions(+), 61 deletions(-) diff --git a/apps/desktop/src/api/commands/mod.rs b/apps/desktop/src/api/commands/mod.rs index 560ec8d6..3604f618 100644 --- a/apps/desktop/src/api/commands/mod.rs +++ b/apps/desktop/src/api/commands/mod.rs @@ -71,6 +71,7 @@ macro_rules! collect_commands { add_cluster_package, remove_cluster_package, sync_cluster_packages, + sync_cluster_packages_by_type, // Updater check_for_update, install_update diff --git a/apps/desktop/src/api/commands/package.rs b/apps/desktop/src/api/commands/package.rs index 9f55bd9b..9994dce9 100644 --- a/apps/desktop/src/api/commands/package.rs +++ b/apps/desktop/src/api/commands/package.rs @@ -103,9 +103,10 @@ pub async fn download_provider_package( .await? .ok_or("cluster not found")?; - let pkg = provider.get(&package_id).await?; + let mgd_pkg = provider.get(&package_id).await?; - package::download_package(&pkg, &mut cluster, game_version, loader, package_version).await?; + let (pkg_path, pkg) = package::download_package(&mgd_pkg, &mut cluster, game_version, loader, package_version).await?; + package::add_package(&cluster.cluster_path(), pkg_path, pkg, Some(mgd_pkg.package_type)).await?; Ok(()) } @@ -161,3 +162,10 @@ pub async fn sync_cluster_packages(cluster_path: ClusterPath) -> Result<(), Stri package::sync_packages(&cluster_path).await?; Ok(()) } + +#[specta::specta] +#[tauri::command] +pub async fn sync_cluster_packages_by_type(cluster_path: ClusterPath, package_type: PackageType) -> Result<(), String> { + package::sync_packages_by_type(&cluster_path, package_type).await?; + Ok(()) +} diff --git a/apps/frontend/src/ui/pages/cluster/ClusterMods.tsx b/apps/frontend/src/ui/pages/cluster/ClusterMods.tsx index 72d599c5..3043334f 100644 --- a/apps/frontend/src/ui/pages/cluster/ClusterMods.tsx +++ b/apps/frontend/src/ui/pages/cluster/ClusterMods.tsx @@ -1,19 +1,56 @@ -import { Edit02Icon, FilterFunnel01Icon, SearchMdIcon, Trash03Icon } from '@untitled-theme/icons-solid'; -import { For, Match, Switch, createEffect, createSignal } from 'solid-js'; +import { FilterFunnel01Icon, SearchMdIcon, Trash03Icon } from '@untitled-theme/icons-solid'; +import { For, Match, Show, Switch, createEffect, createResource, createSignal } from 'solid-js'; import UFuzzy from '@leeoniya/ufuzzy'; -import type { Package } from '@onelauncher/client/bindings'; +import type { ManagedPackage, Package, Providers } from '@onelauncher/client/bindings'; import Button from '~ui/components/base/Button'; import TextField from '~ui/components/base/TextField'; import ScrollableContainer from '~ui/components/ScrollableContainer'; import Sidebar from '~ui/components/Sidebar'; import useClusterContext from '~ui/hooks/useCluster'; -import useCommand from '~ui/hooks/useCommand'; +import useCommand, { tryResult } from '~ui/hooks/useCommand'; import { bridge } from '~imports'; +import Spinner from '~ui/components/Spinner'; +import useBrowser from '~ui/hooks/useBrowser'; // TODO: Possibly optimise this as it has 2 cloned lists, and another containing only the names function ClusterMods() { const [cluster] = useClusterContext(); - const [mods] = useCommand(cluster, () => bridge.commands.getClusterPackages(cluster()?.path || '', 'mod')); + const [mods, { refetch }] = useCommand(cluster, () => bridge.commands.getClusterPackages(cluster()?.path || '', 'mod')); + const [managedMods] = createResource(mods, async () => { + const list = mods(); + if (!list) + return Promise.resolve(new Map()); + + const providers: Map = new Map(); + + list.forEach((mod) => { + if (mod.meta.type === 'managed') + providers.set(mod.meta.provider, [ + ...(providers.get(mod.meta.provider) || []), + mod.meta.package_id, + ]); + }); + + const formatted: Map = new Map(); + + for (const [provider, packageIds] of providers.entries()) { + const result = await tryResult(() => bridge.commands.getProviderPackages(provider, packageIds)); + formatted.set(provider, result); + } + + return formatted; + }); + + function getManagedData(mod: Package): ManagedPackage | undefined { + const meta = mod.meta; + if (meta.type === 'managed') { + const provider = managedMods()?.get(meta.provider); + if (provider && provider.length > 0) + return provider.find(pkg => pkg.id === meta.package_id); + } + + return undefined; + } // Mods to display, should be a clone of the mods array but sorted const [displayedMods, setDisplayedMods] = createSignal([]); @@ -73,32 +110,39 @@ function ClusterMods() {

Mods

-
-
- - 0}> - - {mod => ( - - )} - - + + + + 0}> + + {(mod) => { + const managed = getManagedData(mod); + return ; + }} + + - -

No mods were found

-
-
+ +

No mods were found

+
+
+ +
); @@ -108,13 +152,14 @@ export default ClusterMods; interface ModEntryProps { pkg: Package; + refetch: () => void; + managed?: ManagedPackage | undefined; }; -function icon() { - return ''; -} - function ModEntry(props: ModEntryProps) { + const [cluster] = useClusterContext(); + const browser = useBrowser(); + const name = () => { if (props.pkg.meta.type === 'unknown') return props.pkg.file_name; @@ -144,10 +189,45 @@ function ModEntry(props: ModEntryProps) { return tag; }; + const icon = () => { + if (props.pkg.meta.type === 'managed') + return props.managed?.icon_url; + + return undefined; + }; + + function onClick(e: MouseEvent) { + if (props.managed) { + e.preventDefault(); + e.stopPropagation(); + + browser.displayPackage(props.managed.id, props.managed.provider); + } + } + + async function deletePackage(e: MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + + const path = cluster()?.path; + if (path) { + await bridge.commands.removeClusterPackage(path, props.pkg.file_name, 'mod'); + await bridge.commands.syncClusterPackages(path); + props.refetch(); + } + } + return ( -
+
- {name()} + } + children={{name()}} + />
@@ -169,12 +249,12 @@ function ModEntry(props: ModEntryProps) {
- + */} -
diff --git a/apps/testing/examples/cluster_packages.rs b/apps/testing/examples/cluster_packages.rs index d8074c8e..5a9f4192 100644 --- a/apps/testing/examples/cluster_packages.rs +++ b/apps/testing/examples/cluster_packages.rs @@ -1,6 +1,6 @@ use onelauncher::data::{Loader, PackageType}; use onelauncher::package::content::Providers; -use onelauncher::store::Cluster; +use onelauncher::store::{Cluster, PackagePath}; use onelauncher::{cluster, Result}; const CLUSTER_NAME: &str = "Test Packages"; @@ -18,7 +18,7 @@ async fn main() -> Result<()> { // Print Packages first print_packages(&cluster).await?; - // Download the first mod, add it to the PackageManager and sync packages + println!("Download the first mod, adding it to the PackageManager and sync packages"); download_mod( &mut cluster, MODRINTH_PACKAGE_ONE, @@ -32,8 +32,8 @@ async fn main() -> Result<()> { // Print Packages again print_packages(&cluster).await?; - // Download the second mod, DON'T add it to the PackageManager and sync packages - download_mod( + println!("Download the second mod, NOT adding it to the PackageManager and sync packages"); + let package_path = download_mod( &mut cluster, MODRINTH_PACKAGE_TWO, MODRINTH_PACKAGE_TWO_VERSION, @@ -46,6 +46,15 @@ async fn main() -> Result<()> { // Print Packages again print_packages(&cluster).await?; + println!("Removing the second mod from the PackageManager and sync packages"); + cluster::content::package::remove_package( + &cluster.cluster_path(), + &package_path, + PackageType::Mod, + ).await?; + cluster::content::package::sync_packages_by_type(&cluster.cluster_path(), PackageType::Mod) + .await?; + Ok(()) } @@ -74,7 +83,7 @@ async fn create_cluster() -> Result { Ok(cluster) } -async fn download_mod(cluster: &mut Cluster, pkg: &str, ver: &str, add: bool) -> Result<()> { +async fn download_mod(cluster: &mut Cluster, pkg: &str, ver: &str, add: bool) -> Result { let package = Providers::Modrinth.get(pkg).await?; let (package_path, package) = cluster::content::package::download_package( @@ -96,7 +105,7 @@ async fn download_mod(cluster: &mut Cluster, pkg: &str, ver: &str, add: bool) -> .await?; } - Ok(()) + Ok(package_path) } async fn print_packages(cluster: &Cluster) -> Result<()> { diff --git a/packages/core/src/api/cluster/content/package.rs b/packages/core/src/api/cluster/content/package.rs index 84a1d70c..a0bc60fd 100644 --- a/packages/core/src/api/cluster/content/package.rs +++ b/packages/core/src/api/cluster/content/package.rs @@ -242,7 +242,7 @@ pub async fn sync_packages_by_type( .get_mut(cluster_path) .ok_or(anyhow::anyhow!("cluster not found in packages map"))?; - manager.sync_packages(&state.directories).await; + manager.sync_packages_by_type(&state.directories, package_type).await?; Ok(()) } diff --git a/packages/core/src/api/package/content/modrinth.rs b/packages/core/src/api/package/content/modrinth.rs index 7ccc5101..6dc2e2f8 100644 --- a/packages/core/src/api/package/content/modrinth.rs +++ b/packages/core/src/api/package/content/modrinth.rs @@ -290,7 +290,7 @@ pub async fn get(id: &str) -> Result { pub async fn get_multiple(ids: &[String]) -> Result> { Ok(serde_json::from_slice( &fetch( - format_url!("/projects?ids=[{}]", ids.join(",")).as_str(), + format_url!("/projects?ids=[{}]", ids.iter().map(|id| format!("\"{}\"", id)).collect::>().join(",")).as_str(), None, &State::get().await?.fetch_semaphore, ) diff --git a/packages/core/src/store/package.rs b/packages/core/src/store/package.rs index e9f088ad..3cada065 100644 --- a/packages/core/src/store/package.rs +++ b/packages/core/src/store/package.rs @@ -7,7 +7,7 @@ use crate::utils::io; use async_zip::tokio::read::fs::ZipFileReader; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use tokio::fs::DirEntry; @@ -354,6 +354,9 @@ impl PackageManager { package_path: &PackagePath, package_type: PackageType, ) -> crate::Result<()> { + let full_path = self.cluster_path.clone().full_path().await?.join(package_type.get_folder()).join(package_path.0.clone()); + io::remove_file(full_path).await?; + let packages = &mut self.get_mut(package_type).packages; packages.remove(package_path); @@ -420,7 +423,7 @@ impl PackageManager { /// returns a list of packages that have a file but are not synced in the manager #[tracing::instrument] - async fn insert_unsynced_packages( + async fn sync_packages_from_local( &self, dirs: &Directories, packages: &mut PackagesMap, @@ -434,26 +437,45 @@ impl PackageManager { ) .await?; + let mut packages_to_keep = HashSet::::new(); + while let Some(file) = files.next_entry().await? { - if file - .file_name() + // Skip .packages.json meta file + if file.file_name() .to_string_lossy() .eq(&package_type.get_meta_file_name()) { continue; } - let path = PackagePath::new(&file.path()); + let package_path = PackagePath::new(&file.path()); - if let std::collections::hash_map::Entry::Vacant(e) = packages.entry(path) { - if package_type.file_matches(&file).await? { - // TODO: Infer - let package = - Package::new(&PackagePath::new(&file.path()), PackageMetadata::Unknown) - .await?; - e.insert(package); - } + // Check if the file is in the packages list already + if let Some(_package) = packages.get(&package_path) { + // Package path is in manager and file system + + // match package.meta { + // PackageMetadata::Unknown => { + // // TODO: Infer + // } + // _ => {} + // } + } else { + // Package path is not in the packages list but exists on file system, lets add it to the manager + + // TODO: Infer package + let meta = PackageMetadata::Unknown; + let package = Package::new(&package_path, meta).await?; + + packages.insert(package_path.clone(), package); } + + // Add the package to the set to not remove it from the manager + packages_to_keep.insert(package_path); + } + + if packages.len() != packages_to_keep.len() { + packages.retain(|pkg_path, _| packages_to_keep.contains(pkg_path)) } Ok(()) @@ -461,7 +483,7 @@ impl PackageManager { // sync packages in a cluster #[tracing::instrument] - async fn sync_packages_by_type( + pub async fn sync_packages_by_type( &mut self, dirs: &Directories, package_type: PackageType, @@ -471,8 +493,8 @@ impl PackageManager { let mut new_packages = self.get_from_file(dirs, package_type).await?.packages; packages.extend(new_packages.drain()); - // Insert unsynced packages (files that exist but are not in the manager) - self.insert_unsynced_packages(dirs, &mut packages, package_type) + // Sync the packages from FS + self.sync_packages_from_local(dirs, &mut packages, package_type) .await?; // Finally store the new list in memory and on disk