OneLauncher-Cracked/packages/core/src/store/package.rs
2024-12-08 15:27:19 +01:00

946 lines
25 KiB
Rust

//! Handlers for Mod metadata that can be displayed in a GUI mod list or exported as a mod pack
use crate::package::content::Providers;
use crate::store::Loader;
use crate::utils::crypto;
use crate::utils::http::{write_icon, IoSemaphore};
use crate::State;
use async_zip::tokio::read::fs::ZipFileReader;
use chrono::{DateTime, Utc};
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};
/// Represents types of packages handled by the launcher.
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum PackageType {
/// Represents a mod jarfile
Mod,
/// Represents a datapack file
DataPack,
/// Represents a resourcepack file
ResourcePack,
/// Represents a shaderpack file
#[serde(alias = "shader")]
ShaderPack,
/// Represents a modpack file
ModPack,
}
impl PackageType {
/// attempt to get a [`PackageType`] from a loader string.
#[must_use]
pub fn from_loader(loaders: &[String]) -> Option<Self> {
if loaders
.iter()
.any(|x| ["fabric", "forge", "quilt"].contains(&&**x))
{
Some(Self::Mod)
} else if loaders.iter().any(|x| x == "datapack") {
Some(Self::DataPack)
} else if loaders.iter().any(|x| ["iris", "optifine"].contains(&&**x)) {
Some(Self::ShaderPack)
} else if loaders
.iter()
.any(|x| ["vanilla", "canvas", "minecraft"].contains(&&**x))
{
Some(Self::ResourcePack)
} else {
None
}
}
#[must_use]
pub fn from_parent(path: &Path) -> Option<Self> {
let path = path.parent()?.file_name()?;
match path.to_str()? {
"mods" => Some(Self::Mod),
"datapacks" => Some(Self::DataPack),
"resourcepacks" => Some(Self::ResourcePack),
"shaderpacks" => Some(Self::ShaderPack),
"modpack" => Some(Self::ModPack),
_ => None,
}
}
#[must_use]
pub const fn get_name(&self) -> &'static str {
match self {
Self::Mod => "mod",
Self::DataPack => "datapack",
Self::ResourcePack => "resourcepack",
Self::ShaderPack => "shader",
Self::ModPack => "modpack",
}
}
#[must_use]
pub const fn get_folder(&self) -> &'static str {
match self {
Self::Mod => "mods",
Self::DataPack => "datapacks",
Self::ResourcePack => "resourcepacks",
Self::ShaderPack => "shaderpacks",
Self::ModPack => "modpack",
}
}
#[must_use]
pub fn get_meta(&self) -> PathBuf {
PathBuf::from(format!(
"{}/{}",
self.get_folder(),
self.get_meta_file_name()
))
}
#[must_use]
pub fn get_meta_file_name(&self) -> String {
String::from(".packages.json")
}
pub async fn file_matches(&self, entry: &DirEntry) -> crate::Result<bool> {
if !(entry.path().try_exists()?) {
return Ok(false);
}
Ok(match self {
Self::Mod => {
entry.file_type().await?.is_file()
&& entry.path().extension() == Some("jar".as_ref())
}
_ => false,
})
}
pub fn iterator() -> impl Iterator<Item = Self> {
[
Self::Mod,
Self::DataPack,
Self::ResourcePack,
Self::ShaderPack,
Self::ModPack,
]
.iter()
.copied()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub struct SearchResult {
pub slug: String,
pub title: String,
pub description: String,
#[serde(default)]
pub categories: Vec<String>,
pub client_side: PackageSide,
pub server_side: PackageSide,
pub project_type: PackageType,
pub downloads: u64,
#[serde(default)]
pub icon_url: String,
pub project_id: String,
pub author: String,
pub versions: Vec<String>,
pub follows: u32,
pub date_created: Option<DateTime<Utc>>,
pub date_modified: Option<DateTime<Utc>>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub struct ProviderSearchResults {
pub provider: Providers,
pub results: Vec<SearchResult>,
pub total: u32,
}
/// A struct that represents a Package.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub struct Package {
pub sha1: String,
pub meta: PackageMetadata,
pub file_name: String,
pub disabled: bool,
}
impl Package {
pub fn new(path: &PackagePath, meta: PackageMetadata) -> crate::Result<Self> {
let file_name = path
.0
.file_name()
.ok_or_else(|| crate::ErrorKind::AnyhowError(anyhow::anyhow!("no file name")))?
.to_string_lossy()
.to_string();
let sha1 = crypto::sha1_file(&path.0)?;
Ok(Self {
sha1,
meta,
file_name,
disabled: false,
})
}
}
pub type PackagesMap = HashMap<PackagePath, Package>;
pub struct Packages {
managers: HashMap<ClusterPath, PackageManager>,
}
impl Packages {
pub fn initialize(clusters: &Clusters) -> Self {
let mut this = Self { managers: HashMap::new() };
// TODO: This should probably not clone and store the cluster path in like 2 areas
for cluster_path in clusters.0.keys() {
this.add_cluster(cluster_path.clone());
}
this
}
pub fn add_cluster(&mut self, cluster_path: ClusterPath) {
let mgr = PackageManager::new(cluster_path.clone());
self.managers.insert(cluster_path, mgr);
}
#[must_use]
pub fn get(&self, cluster_path: &ClusterPath) -> Option<&PackageManager> {
self.managers.get(cluster_path)
}
pub fn get_mut(&mut self, cluster_path: &ClusterPath) -> Option<&mut PackageManager> {
self.managers.get_mut(cluster_path)
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PackagesMeta {
pub packages: PackagesMap,
pub package_type: PackageType,
}
impl PackagesMeta {
#[must_use]
pub fn new(package_type: PackageType) -> Self {
Self {
packages: PackagesMap::new(),
package_type,
}
}
}
#[derive(Debug)]
pub struct PackageManager {
pub cluster_path: ClusterPath,
synced: bool,
modpacks: PackagesMeta,
mods: PackagesMeta,
datapacks: PackagesMeta,
resourcepacks: PackagesMeta,
shaderpacks: PackagesMeta,
}
impl PackageManager {
#[must_use]
pub fn new(cluster_path: ClusterPath) -> Self {
Self {
cluster_path,
modpacks: PackagesMeta::new(PackageType::ModPack),
synced: false,
mods: PackagesMeta::new(PackageType::Mod),
datapacks: PackagesMeta::new(PackageType::DataPack),
resourcepacks: PackagesMeta::new(PackageType::ResourcePack),
shaderpacks: PackagesMeta::new(PackageType::ShaderPack),
}
}
pub async fn get_meta_file(
&self,
dirs: &Directories,
package_type: PackageType,
) -> crate::Result<PathBuf> {
Ok(self
.cluster_path
.full_path_dirs(dirs)
.await?
.join(package_type.get_meta()))
}
#[async_recursion::async_recursion]
async fn get_and_sync(&mut self, dirs: Option<&Directories>) -> &mut Self {
if !self.synced {
self.synced = true; // early set to prevent infinite loop
let state = State::get().await.expect("couldn't get state when syncing packages");
let dirs = match dirs {
Some(dirs) => dirs,
None => &state.directories,
};
self.sync_packages(dirs).await;
}
self
}
/// Get the `PackagesMeta` for a specific package type. Syncs.
#[must_use]
pub async fn get(&mut self, package_type: PackageType) -> &PackagesMeta {
self.get_and_sync(None).await;
match package_type {
PackageType::Mod => &self.mods,
PackageType::DataPack => &self.datapacks,
PackageType::ResourcePack => &self.resourcepacks,
PackageType::ShaderPack => &self.shaderpacks,
PackageType::ModPack => &self.modpacks,
}
}
/// Get the `PackagesMeta` for a specific package type. Does not sync.
fn get_mut(&mut self, package_type: PackageType) -> &mut PackagesMeta {
match package_type {
PackageType::Mod => &mut self.mods,
PackageType::ModPack => &mut self.modpacks,
PackageType::DataPack => &mut self.datapacks,
PackageType::ResourcePack => &mut self.resourcepacks,
PackageType::ShaderPack => &mut self.shaderpacks,
}
}
/// Get the PackagesMeta for a specific package type. Does not sync.
#[tracing::instrument]
async fn get_from_file(
&self,
dirs: &Directories,
package_type: PackageType,
) -> crate::Result<PackagesMeta> {
let packages_meta = &self.get_meta_file(dirs, package_type).await?;
let meta: PackagesMeta = if packages_meta.exists() && packages_meta.is_file() {
if let Ok(meta) = serde_json::from_slice(&io::read(packages_meta).await?) {
meta
} else {
io::copy(packages_meta, packages_meta.with_extension("bak")).await?;
io::write(packages_meta, "{}").await?;
PackagesMeta::new(package_type)
}
} else {
io::write(packages_meta, "{}").await?;
PackagesMeta::new(package_type)
};
Ok(meta)
}
// add a package to the manager
#[tracing::instrument(skip(self, package))]
pub async fn add_package(
&mut self,
package_path: PackagePath,
package: Package,
package_type: Option<PackageType>,
) -> crate::Result<()> {
let package_type = package
.meta
.get_package_type()
.unwrap_or(package_type.ok_or_else(|| anyhow::anyhow!("no package type"))?);
let packages = &mut self.get_mut(package_type).packages;
packages.insert(package_path, package);
Ok(())
}
// remove a package to the manager
#[tracing::instrument(skip(self))]
pub async fn remove_package(
&mut self,
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);
Ok(())
}
/// sync all packages
#[tracing::instrument(skip(self, dirs))]
pub async fn sync_packages(&mut self, dirs: &Directories) {
tracing::info!("syncing packages for cluster {}", self.cluster_path);
let package_types = vec![
PackageType::Mod,
PackageType::DataPack,
PackageType::ResourcePack,
PackageType::ShaderPack,
];
for package_type in package_types {
if let Err(err) = &self.sync_packages_by_type(dirs, package_type, None).await {
tracing::error!("failed to sync package {}: {}", package_type.get_name(), err);
}
}
}
/// sync packages from the metafile
#[tracing::instrument(skip(self, dirs))]
async fn sync_from_file_by_type(
&mut self,
dirs: &Directories,
package_type: PackageType,
) -> crate::Result<()> {
let packages_meta = self.get_from_file(dirs, package_type).await?;
self.get_mut(package_type).packages = packages_meta.packages;
Ok(())
}
/// sync packages from the manager to the metafile
#[tracing::instrument(skip(self, dirs))]
async fn sync_to_file_by_type(
&mut self,
dirs: &Directories,
package_type: PackageType,
) -> crate::Result<()> {
let packages_meta = self.get(package_type).await;
let packages_meta = serde_json::to_string(&packages_meta)?;
io::write(self.get_meta_file(dirs, package_type).await?, packages_meta).await?;
Ok(())
}
/// returns a list of packages that have a file but are not synced in the manager
#[tracing::instrument(skip(self, dirs, packages))]
async fn sync_packages_from_local(
&self,
dirs: &Directories,
packages: &mut PackagesMap,
package_type: PackageType,
) -> crate::Result<()> {
let mut files = io::read_dir(
self.cluster_path
.full_path_dirs(dirs)
.await?
.join(package_type.get_folder()),
)
.await?;
let mut packages_to_keep = HashSet::<PackagePath>::new();
let mut packages_to_infer = HashSet::<PathBuf>::new();
while let Some(file) = files.next_entry().await? {
// Skip .packages.json meta file
if file.file_name().to_string_lossy().eq(&package_type.get_meta_file_name()) || file.file_type().await?.is_dir()
{
continue;
}
let package_path = PackagePath::new(&file.path());
// Check if the file is in the packages list already
if let None = packages.get(&package_path) {
// Package path is not in the packages list but exists on file system, lets try to infer it later on
packages_to_infer.insert(package_path.0.clone());
}
// Add the package to the set to not remove it from the manager
packages_to_keep.insert(package_path);
}
// Infer any packages
if packages_to_infer.len() > 0 {
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);
let package_path = PackagePath::new(&path);
let meta = PackageMetadata::Unknown;
let package = Package::new(&package_path, meta)?;
packages.insert(package_path, package);
}
}
}
}
}
if packages.len() != packages_to_keep.len() {
packages.retain(|pkg_path, _| packages_to_keep.contains(pkg_path));
}
Ok(())
}
// sync packages in a cluster
#[tracing::instrument(skip(self, dirs))]
pub async fn sync_packages_by_type(
&mut self,
dirs: &Directories,
package_type: PackageType,
clear: Option<bool>,
) -> crate::Result<()> {
if clear.unwrap_or(false) {
self.get_mut(package_type).packages.clear();
}
// Clone the current packages and merge them with the local package list
let mut packages = self.get(package_type).await.packages.clone();
let mut new_packages = self.get_from_file(dirs, package_type).await?.packages;
packages.extend(new_packages.drain());
// 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
let stored_packages = &self.get_mut(package_type).packages;
if stored_packages.len() != packages.len() {
self.get_mut(package_type).packages = packages;
self.sync_to_file_by_type(dirs, package_type).await?;
}
Ok(())
}
}
// #[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,
package_type: PackageType,
packages: &mut PackagesMap,
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) {
hash_map.insert(hash, path.clone());
}
});
let hash_len = hash_map.len();
match provider.get_versions_by_hashes(hash_map.iter().map(|hash| hash.0.clone()).collect()).await {
Ok(versions) => {
let found_all = (hash_len - versions.len()) <= 0;
let havent_found = if !found_all {
Some(hash_map.iter().filter_map(|(hash, path)| {
if versions.contains_key(hash) {
None
} else {
Some(path.clone())
}
}).collect::<HashSet<PathBuf>>())
} else {
None
};
let package_ids = versions.iter().map(|(hash, version)| (version.package_id.clone(), hash.clone())).collect::<HashMap<String, String>>();
match provider.get_multiple(package_ids.clone().into_keys().collect::<Vec<String>>().as_slice()).await {
Ok(managed_packages) => {
for managed in managed_packages {
let hash = match package_ids.get(&managed.id) {
Some(hash) => hash,
None => continue,
};
let version = match versions.get(hash) {
Some(version) => version,
None => continue,
};
if let Some(package_path) = hash_map.get(hash) {
let meta = PackageMetadata::Managed {
package_id: managed.id,
provider: provider.clone(),
package_type,
title: managed.title,
version_id: version.id.clone(),
version_formatted: version.version_display.clone(),
mc_versions: Some(version.game_versions.clone()),
icon_url: managed.icon_url
};
let package_path = &PackagePath::new(package_path);
match Package::new(package_path, meta) {
Ok(package) => {
packages.insert(package_path.clone(), package);
},
Err(err) => {
tracing::error!("failed to create package from {}: {}", provider, err);
},
};
}
}
},
Err(err) => {
tracing::error!("failed to get versions from {}: {}", provider, err);
}
}
return havent_found;
},
Err(err) => {
tracing::error!("failed to get versions from {}: {}", provider, err);
}
};
Some(to_infer)
}
/// Metadata that represents a [`Package`].
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum PackageMetadata {
Managed {
package_id: String,
provider: Providers,
package_type: PackageType,
title: String,
version_id: String,
version_formatted: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
mc_versions: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
icon_url: Option<String>,
},
Unknown,
}
impl PackageMetadata {
#[must_use]
pub fn from_managed_package(package: ManagedPackage, version: ManagedVersion) -> Self {
Self::Managed {
package_id: package.id,
version_id: version.id,
version_formatted: version.version_display,
title: package.title,
provider: package.provider,
package_type: package.package_type,
mc_versions: Some(version.game_versions),
icon_url: package.icon_url,
}
}
#[must_use]
pub const fn get_package_type(&self) -> Option<PackageType> {
match self {
Self::Managed { package_type, .. } => Some(*package_type),
_ => None,
}
}
}
/// Universal metadata for any managed user from a Mod distribution platform.
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ManagedUser {
pub id: String,
pub username: String,
#[serde(default)]
pub url: Option<String>,
#[serde(default)]
pub avatar_url: Option<String>,
#[serde(default)]
pub bio: Option<String>,
#[serde(default = "default_is_organization_user")]
pub is_organization_user: bool,
#[serde(default)]
pub role: Option<String>,
}
const fn default_is_organization_user() -> bool {
false
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Author {
Team {
team: String,
organization: Option<String>,
},
Users(Vec<ManagedUser>),
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum PackageBody {
Url(String),
Markdown(String),
}
/// Universal metadata for any managed package from a Mod distribution platform.
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ManagedPackage {
// Core Metadata
pub provider: Providers,
pub id: String,
pub package_type: PackageType,
pub title: String,
pub description: String,
pub body: PackageBody,
pub main: String,
pub versions: Vec<String>,
pub game_versions: Vec<String>,
pub loaders: Vec<Loader>,
pub icon_url: Option<String>,
pub created: Option<DateTime<Utc>>,
pub updated: Option<DateTime<Utc>>,
pub client: PackageSide,
pub server: PackageSide,
pub downloads: u64,
pub followers: u32,
pub categories: Vec<String>,
pub optional_categories: Option<Vec<String>>,
pub license: Option<License>,
pub author: Author,
pub is_archived: bool,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct License {
#[serde(default)]
pub id: String,
#[serde(default)]
pub name: String,
#[serde(default)]
pub url: Option<String>,
}
/// Universal managed package version of a package.
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ManagedVersion {
pub id: String,
pub package_id: String,
pub author: String,
pub name: String,
pub featured: bool,
pub version_display: String,
pub changelog: String,
pub changelog_url: Option<String>,
pub published: Option<DateTime<Utc>>,
pub downloads: u32,
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>,
}
impl ManagedVersion {
#[must_use]
pub fn get_primary_file(&self) -> Option<&ManagedVersionFile> {
self.files.iter().find(|f| f.primary)
}
}
/// 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)]
pub struct ManagedVersionFile {
pub url: String,
pub file_name: String,
pub primary: bool,
pub size: u64,
pub file_type: Option<PackageFile>,
pub hashes: HashMap<String, String>,
}
/// Universal interface for managed package dependencies.
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ManagedDependency {
pub version_id: Option<String>,
pub package_id: Option<String>,
pub file_name: Option<String>,
pub dependency_type: PackageDependency,
}
/// The type of a [`ManagedDependency`].
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum PackageDependency {
Required,
Optional,
Incompatible,
Embedded,
}
/// The Client/Server side type of a [`Package`].
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub enum PackageSide {
Required,
Optional,
Unsupported,
Unknown,
}
/// The file type of a [`Package`].
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PackageFile {
RequiredPack,
OptionalPack,
Unknown,
}
#[tracing::instrument(skip(io_semaphore))]
#[onelauncher_macros::memory]
pub async fn read_icon(
icon_path: Option<String>,
cache_dir: &Path,
path: &PathBuf,
io_semaphore: &IoSemaphore,
) -> crate::Result<Option<PathBuf>> {
if let Some(icon_path) = icon_path {
let zip_reader = ZipFileReader::new(path).await;
if let Ok(zip_reader) = zip_reader {
let zip_idx_res = zip_reader
.file()
.entries()
.iter()
.position(|f| f.filename().as_str().unwrap_or_default() == icon_path);
if let Some(zip_idx) = zip_idx_res {
let mut bytes = Vec::new();
if zip_reader
.reader_with_entry(zip_idx)
.await?
.read_to_end_checked(&mut bytes)
.await
.is_ok()
{
let bytes = bytes::Bytes::from(bytes);
let path = write_icon(&icon_path, cache_dir, bytes, io_semaphore).await?;
return Ok(Some(path));
}
}
}
}
Ok(None)
}