🚧 wip: is somebody gonna match my tweaks

Signed-off-by: Pauline <git@ethanlibs.co>
This commit is contained in:
Pauline 2024-09-12 18:32:02 +02:00
parent bfaa6ceb3a
commit 5f64f4fed1
No known key found for this signature in database
GPG key ID: 722BB181973296AE
69 changed files with 1000 additions and 662 deletions

62
Cargo.lock generated
View file

@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "addr2line"
version = "0.22.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
dependencies = [
"gimli",
]
@ -458,17 +458,17 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.73"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide 0.7.4",
"miniz_oxide 0.8.0",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
]
[[package]]
@ -1870,9 +1870,9 @@ dependencies = [
[[package]]
name = "error-code"
version = "3.2.0"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
[[package]]
name = "event-listener"
@ -2404,9 +2404,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.29.0"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
[[package]]
name = "gio"
@ -2904,9 +2904,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba"
dependencies = [
"bytes",
"futures-channel",
@ -4350,7 +4350,6 @@ name = "onelauncher"
version = "1.0.0-alpha.1"
dependencies = [
"anyhow",
"async-compression",
"async-recursion",
"async-tungstenite",
"async_zip",
@ -4360,19 +4359,18 @@ dependencies = [
"chrono",
"dirs 5.0.1",
"discord-rich-presence",
"dunce",
"flate2",
"futures",
"indicatif",
"interpulse",
"iota_stronghold",
"lazy_static",
"nom",
"notify",
"notify-debouncer-mini",
"onelauncher_gamemode",
"onelauncher_macros",
"onelauncher_prisma",
"onelauncher_utils",
"p256",
"paste",
"rand 0.8.5",
@ -4384,7 +4382,6 @@ dependencies = [
"sha1_smol",
"sha2 0.10.8",
"specta 2.0.0-rc.20",
"sys-info",
"sysinfo",
"tauri",
"tauri-specta",
@ -4483,6 +4480,31 @@ dependencies = [
"webbrowser",
]
[[package]]
name = "onelauncher_utils"
version = "1.0.0-alpha.1"
dependencies = [
"async-channel",
"async-compression",
"chrono",
"dunce",
"futures",
"interpulse",
"nom",
"onelauncher_prisma",
"pin-project-lite",
"prisma-client-rust",
"regex",
"serde",
"sys-info",
"tempfile",
"thiserror",
"tokio",
"tracing",
"uuid 1.10.0",
"zip",
]
[[package]]
name = "opaque-debug"
version = "0.3.1"
@ -6002,9 +6024,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.12"
version = "0.23.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
dependencies = [
"once_cell",
"ring",
@ -6041,9 +6063,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
[[package]]
name = "rustls-webpki"
version = "0.102.7"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",

View file

@ -7,7 +7,8 @@ members = [
"packages/macros", # onelauncher_macros -- utility procedural macros for onelauncher
"packages/gamemode", # onelauncher_gamemode -- rust wrapper for feral gamemode
"packages/prisma-cli", # onelauncher_prisma_cli -- rust wrapper for prisma cli
"packages/prisma" # onelauncher_prisma -- rust client for prisma
"packages/prisma", # onelauncher_prisma -- rust client for prisma
"packages/utils" # onelauncher_utils -- standard utilities for use within all crates
]
[workspace.package] # sets default [package] settings for workspace members
@ -31,6 +32,7 @@ onelauncher_test = { path = "./apps/testing" }
onelauncher_gamemode = { path = "./packages/gamemode" }
onelauncher_prisma = { path = "./packages/prisma" }
onelauncher_prisma_cli = { path = "./packages/prisma-cli" }
onelauncher_utils = { path = "./packages/utils" }
# # ============= TAURI ============= # #
# tauri uses latest crates.io rc channel
@ -158,6 +160,8 @@ sha2 = { version = "0.10" }
bytes = { version = "1.7" }
# # ============= UTILITY ============= # #
async-channel = { version = "2.3" }
pin-project-lite = { version = "0.2" }
once_cell = { version = "1.19" }
quote = { version = "1.0" }
syn = { version = "2.0", features = [ "full" ] }

View file

@ -1,11 +1,11 @@
import { ChevronDownIcon, Download01Icon, HeartIcon } from '@untitled-theme/icons-solid';
import { For, Match, Show, Switch, createSignal } from 'solid-js';
import useBrowser from '~ui/hooks/useBrowser';
import useSettings from '~ui/hooks/useSettings';
import { abbreviateNumber } from '~utils';
import { createSignal, For, Match, Show, Switch } from 'solid-js';
import type { Providers, SearchResult } from '@onelauncher/client/bindings';
import type { JSX } from 'solid-js';
import Button from '../base/Button';
import { abbreviateNumber } from '~utils';
import useSettings from '~ui/hooks/useSettings';
import useBrowser from '~ui/hooks/useBrowser';
interface SearchResultsContainerProps {
provider: Providers;

View file

@ -6,19 +6,19 @@ import {
RefreshCw01Icon,
SearchMdIcon,
} from '@untitled-theme/icons-solid';
import { For, Show, onMount } from 'solid-js';
import type { Cluster } from '@onelauncher/client/bindings';
import BannerBackground from '../../assets/images/header.png';
import Button from '../components/base/Button';
import TextField from '../components/base/TextField';
import ClusterRoot from './cluster/ClusterRoot';
import { bridge } from '~imports';
import ClusterCover from '~ui/components/game/ClusterCover';
import { useClusterCreator } from '~ui/components/overlay/cluster/ClusterCreationModal';
import { useLaunchCluster, useRecentCluster } from '~ui/hooks/useCluster';
import useCommand from '~ui/hooks/useCommand';
import { formatAsDuration, upperFirst } from '~utils';
import { For, onMount, Show } from 'solid-js';
import type { Cluster } from '@onelauncher/client/bindings';
import BannerBackground from '../../assets/images/header.png';
import Button from '../components/base/Button';
import TextField from '../components/base/TextField';
import ClusterRoot from './cluster/ClusterRoot';
type GroupedClusters = Record<string, Cluster[]>;

View file

@ -1,13 +1,13 @@
import { useBeforeLeave } from '@solidjs/router';
import { type Accessor, Show } from 'solid-js';
import type { Cluster } from '@onelauncher/client/bindings';
import { GameSettings, JvmSettings, LauncherSettings, ProcessSettings, createSetting } from '../settings/game/SettingsMinecraft';
import { bridge } from '~imports';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import Sidebar from '~ui/components/Sidebar';
import useClusterContext from '~ui/hooks/useCluster';
import { tryResult } from '~ui/hooks/useCommand';
import useSettings from '~ui/hooks/useSettings';
import { type Accessor, Show } from 'solid-js';
import type { Cluster } from '@onelauncher/client/bindings';
import { createSetting, GameSettings, JvmSettings, LauncherSettings, ProcessSettings } from '../settings/game/SettingsMinecraft';
function ClusterSettings() {
const [cluster] = useClusterContext();

View file

@ -1,11 +1,11 @@
import { InfoCircleIcon, Trash01Icon, UserPlus02Icon } from '@untitled-theme/icons-solid';
import { For, Match, Show, Switch, createSignal, mergeProps } from 'solid-js';
import Button from '~ui/components/base/Button';
import Tooltip from '~ui/components/base/Tooltip';
import PlayerHead from '~ui/components/game/PlayerHead';
import useAccountController from '~ui/components/overlay/account/AddAccountModal';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import Sidebar from '~ui/components/Sidebar';
import { createSignal, For, Match, mergeProps, Show, Switch } from 'solid-js';
function SettingsAccounts() {
const controller = useAccountController();

View file

@ -1,15 +1,15 @@
import { ActivityIcon, CpuChip01Icon, Database01Icon, EyeIcon, FilePlus02Icon, FileX02Icon, LayoutTopIcon, Maximize01Icon, ParagraphWrapIcon, VariableIcon, XIcon } from '@untitled-theme/icons-solid';
import { type Accessor, For, type Setter, Show, createSignal, onMount, splitProps, untrack } from 'solid-js';
import type { JavaVersion, JavaVersions, Memory, Resolution } from '@onelauncher/client/bindings';
import Button from '~ui/components/base/Button';
import TextField from '~ui/components/base/TextField';
import Toggle from '~ui/components/base/Toggle';
import Modal, { createModal } from '~ui/components/overlay/Modal';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import BaseSettingsRow, { type SettingsRowProps } from '~ui/components/SettingsRow';
import Sidebar from '~ui/components/Sidebar';
import useSettings from '~ui/hooks/useSettings';
import { asEnvVariables } from '~utils';
import Modal, { createModal } from '~ui/components/overlay/Modal';
import Button from '~ui/components/base/Button';
import { type Accessor, createSignal, For, onMount, type Setter, Show, splitProps, untrack } from 'solid-js';
import type { JavaVersion, JavaVersions, Memory, Resolution } from '@onelauncher/client/bindings';
function SettingsMinecraft() {
return (
@ -300,14 +300,14 @@ export function JvmSettings(props: {
}) {
const modal = createModal(controller => (
<Modal.Simple
title="Java Versions"
buttons={[
<Button
onClick={controller.hide}
buttonStyle="secondary"
children="Close"
onClick={controller.hide}
/>,
]}
title="Java Versions"
{...controller}
>
<div class="grid grid-cols-[80px_1fr] items-center gap-y-2">
@ -318,10 +318,10 @@ export function JvmSettings(props: {
<>
<span class="capitalize">{prettified}</span>
<TextField
value={meta.path}
onValidSubmit={(value) => {
props.javaVersions?.set({ ...props.javaVersions.get(), [version]: { ...meta, path: value } });
}}
value={meta.path}
/>
</>
);
@ -343,9 +343,9 @@ export function JvmSettings(props: {
title="Version"
>
<Button
onClick={modal.show}
iconLeft={<Database01Icon />}
children="Configure"
iconLeft={<Database01Icon />}
onClick={modal.show}
/>
</SettingsRow>

View file

@ -1,14 +1,14 @@
import { useBeforeLeave } from '@solidjs/router';
import { PackageIcon, Speedometer04Icon } from '@untitled-theme/icons-solid';
import { For, createSignal } from 'solid-js';
import SettingsRow from '../../../components/SettingsRow';
import Dropdown from '~ui/components/base/Dropdown';
import Toggle from '~ui/components/base/Toggle';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import Sidebar from '~ui/components/Sidebar';
import useSettings from '~ui/hooks/useSettings';
import { BROWSER_VIEWS } from '~utils/browser';
import { upperFirst } from '~utils';
import { BROWSER_VIEWS } from '~utils/browser';
import { createSignal, For } from 'solid-js';
import SettingsRow from '../../../components/SettingsRow';
function SettingsAppearance() {
const { settings, saveOnLeave } = useSettings();

View file

@ -1,17 +1,4 @@
import hljs from 'highlight.js/lib/core';
// import javascript from 'highlight.js/lib/languages/javascript';
// import python from 'highlight.js/lib/languages/python';
// import lua from 'highlight.js/lib/languages/lua';
// import java from 'highlight.js/lib/languages/java';
// import kotlin from 'highlight.js/lib/languages/kotlin';
// import scala from 'highlight.js/lib/languages/scala';
// import groovy from 'highlight.js/lib/languages/groovy';
// import gradle from 'highlight.js/lib/languages/gradle';
// import json from 'highlight.js/lib/languages/json';
// import ini from 'highlight.js/lib/languages/ini';
// import yaml from 'highlight.js/lib/languages/yaml';
// import xml from 'highlight.js/lib/languages/xml';
// import properties from 'highlight.js/lib/languages/properties';
import { configuredXss, md, noop } from './markdown';
function langFactory(lang: string) {

View file

@ -22,6 +22,7 @@ cli = [ "dep:indicatif" ]
# features[default]
onelauncher_macros = { workspace = true }
onelauncher_prisma = { workspace = true }
onelauncher_utils = { workspace = true }
interpulse = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
@ -50,7 +51,6 @@ async_zip = { workspace = true }
discord-rich-presence = { workspace = true }
regex = { workspace = true }
sysinfo = { workspace = true }
sys-info = { workspace = true }
whoami = { workspace = true }
lazy_static = { workspace = true }
async-tungstenite = { workspace = true }
@ -58,15 +58,12 @@ async-recursion = { workspace = true }
tokio-stream = { workspace = true }
bytes = { workspace = true }
tempfile = { workspace = true }
dunce = { workspace = true }
rand = { workspace = true }
p256 = { workspace = true }
byteorder = { workspace = true }
base64 = { workspace = true }
async-compression = { workspace = true }
flate2 = { workspace = true }
toml = { workspace = true }
nom = { workspace = true }
url = { workspace = true }
# features[tauri]

View file

@ -14,7 +14,7 @@ generator client {
model Settings {
/// The unique ID associated with this Settings instance.
id Int @id @unique @default(0)
/// A OneLauncher theme managed by the core GUI.
/// A OneLauncher theme managed by the core GUI. (system, light, dark, oled, mocha)
theme String @default("dark")
/// Whether or not ask for confirmation when closing the core GUI.
hide_close_prompt Boolean @default(true)

View file

@ -1,17 +1,17 @@
//! OneLauncher log management
//! `OneLauncher` log management
use crate::data::{Credentials, Directories, MinecraftCredentials};
use crate::prelude::ClusterPath;
use crate::utils::http;
use crate::utils::io::{self, IOError};
use futures::TryFutureExt;
use onelauncher_utils::io::{self, IOError};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::SystemTime;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
// TODO: put this in the global store
/// Core logging state and reader for OneLauncher.
/// Core logging state and reader for `OneLauncher`.
#[derive(Serialize, Debug)]
pub struct LogManager {
/// Type log type associated with this log file.
@ -128,7 +128,7 @@ pub async fn delete_logs(cluster_path: ClusterPath) -> crate::Result<()> {
let entry = entry.map_err(|e| IOError::with_path(e, &logs_folder))?;
let path = entry.path();
if path.is_dir() {
io::remove_dir_all(&path).await?
io::remove_dir_all(&path).await?;
}
}
@ -170,14 +170,13 @@ pub async fn get_live_log_cursor(
mut cursor: u64,
) -> crate::Result<LogCursor> {
let cluster_path = cluster_path.cluster_path().await?;
let state = crate::State::get().await?;
let logs_folder = Directories::cluster_logs_dir(&cluster_path).await?;
let path = logs_folder.join(log_file);
if !path.exists() {
return Ok(LogCursor {
cursor: 0,
new: false,
output: LogOutput("".to_string()),
output: LogOutput(String::new()),
});
}
@ -188,12 +187,13 @@ pub async fn get_live_log_cursor(
.metadata()
.await
.map_err(|e| IOError::with_path(e, &path))?;
let mut new = false;
if cursor > metadata.len() {
let new = if cursor > metadata.len() {
cursor = 0;
new = true;
}
true
} else {
false
};
let mut buf = Vec::new();
file.seek(std::io::SeekFrom::Start(cursor))
@ -205,7 +205,8 @@ pub async fn get_live_log_cursor(
.await?;
let output = String::from_utf8_lossy(&buf).to_string();
let cursor = cursor + bytes_read as u64;
let creds: Vec<MinecraftCredentials> = state
let creds: Vec<MinecraftCredentials> = crate::State::get()
.await?
.users
.read()
.await
@ -213,11 +214,11 @@ pub async fn get_live_log_cursor(
.clone()
.into_values()
.collect();
let output = LogOutput::censor_secrets(output, &creds, None);
let output = LogOutput::censor_secrets(output, &creds, &None);
Ok(LogCursor {
cursor,
new,
output,
new,
})
}
@ -262,13 +263,13 @@ pub async fn get_output_by_file(
log_type: LogType,
log_file: &str,
) -> crate::Result<LogOutput> {
let state = crate::State::get().await?;
let logs_folder = match log_type {
LogType::Info => Directories::cluster_logs_dir(cluster_path).await?,
LogType::Crash => Directories::crash_reports_dir(cluster_path).await?,
};
let path = logs_folder.join(log_file);
let credentials: Vec<MinecraftCredentials> = state
let credentials: Vec<MinecraftCredentials> = crate::State::get()
.await?
.users
.read()
.await
@ -279,7 +280,7 @@ pub async fn get_output_by_file(
let result = read_log_to_string(&path).await?;
return Ok(LogOutput::censor_secrets(result, &credentials, None));
return Ok(LogOutput::censor_secrets(result, &credentials, &None));
}
/// The log cursor used to parse logs passed into [`LogManager`].
@ -303,14 +304,14 @@ impl LogOutput {
pub fn censor_secrets(
mut output: String,
credentials: &Vec<MinecraftCredentials>,
_credentials_store: Option<Credentials>,
_credentials_store: &Option<Credentials>,
) -> Self {
let username = whoami::username();
let realname = whoami::realname();
output = output.replace(&format!("/{}/", username), "/{ENV_USERNAME}/");
output = output.replace(&format!("\\{}\\", username), "\\{ENV_USERNAME}\\");
output = output.replace(&format!("/{}/", realname), "/{ENV_REALNAME}/");
output = output.replace(&format!("\\{}\\", realname), "\\{ENV_REALNAME}\\");
output = output.replace(&format!("/{username}/"), "/{ENV_USERNAME}/");
output = output.replace(&format!("\\{username}\\"), "\\{ENV_USERNAME}\\");
output = output.replace(&format!("/{realname}/"), "/{ENV_REALNAME}/");
output = output.replace(&format!("\\{realname}\\"), "\\{ENV_REALNAME}\\");
for cred in credentials {
output = output.replace(&cred.access_token, "{MC_ACCESS_TOKEN}");
@ -323,6 +324,15 @@ impl LogOutput {
}
}
#[derive(serde::Deserialize)]
struct McLogsResponse {
pub success: bool,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub error: Option<String>,
}
/// Upload a log to https://mclo.gs/. If successful, it returns the log id
#[tracing::instrument]
pub async fn upload_log(path: &ClusterPath, log: LogOutput) -> crate::Result<String> {
@ -330,16 +340,7 @@ pub async fn upload_log(path: &ClusterPath, log: LogOutput) -> crate::Result<Str
let mut form = HashMap::new();
form.insert("content", log);
#[derive(serde::Deserialize)]
struct ResponseBody {
pub success: bool,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub error: Option<String>,
}
let parsed = serde_json::from_slice::<ResponseBody>(
let parsed = serde_json::from_slice::<McLogsResponse>(
&http::REQWEST_CLIENT
.post(format!("{}/log", crate::constants::MCLOGS_API_URL))
.header("Content-Type", "application/x-www-form-urlencoded")
@ -351,11 +352,10 @@ pub async fn upload_log(path: &ClusterPath, log: LogOutput) -> crate::Result<Str
)?;
if parsed.success {
if let Some(id) = parsed.id {
Ok(id)
} else {
Err(anyhow::anyhow!("failed to get log id from mclo.gs").into())
}
parsed.id.map_or_else(
|| Err(anyhow::anyhow!("failed to get log id from mclo.gs").into()),
Ok,
)
} else {
Err(anyhow::anyhow!(
"failed to upload log to mclo.gs: {}",

View file

@ -1,10 +1,13 @@
#![allow(clippy::significant_drop_tightening)]
use crate::data::{Loader, ManagedPackage, ManagedVersion, PackageType};
use crate::prelude::PackagePath;
use crate::processor::Cluster;
use crate::proxy::send::send_internet;
use crate::store::{ClusterPath, ManagedVersionFile, Package, PackageMetadata};
use crate::utils::{http, io};
use crate::utils::http;
use crate::{Result, State};
use onelauncher_utils::io;
// TODO: Implement proper error handling
/// Find a managed version using filters
@ -23,7 +26,7 @@ pub async fn find_managed_version(
let versions = provider
.get_all_versions(
&package.id,
game_version.as_ref().map(|v| vec![v.to_owned()]).to_owned(),
game_version.as_ref().map(|v| vec![v.to_owned()]).clone(),
loader.map(|l| vec![l]).clone(),
)
.await?;
@ -45,7 +48,7 @@ pub async fn find_managed_version(
check_game_version && check_loader && check_package_version
})
.ok_or(anyhow::anyhow!("no matching version found"))
.ok_or_else(|| anyhow::anyhow!("no matching version found"))
.cloned()?)
}
@ -72,14 +75,14 @@ pub async fn download_package(
.await?;
let loader = loader.unwrap_or(cluster.meta.loader);
let game_version = game_version.unwrap_or(cluster.meta.mc_version.clone());
let game_version = game_version.unwrap_or_else(|| cluster.meta.mc_version.clone());
let managed_version =
find_managed_version(package, Some(game_version), Some(loader), package_version).await?;
let file = managed_version
.get_primary_file()
.ok_or(anyhow::anyhow!("no primary file found"))?;
.ok_or_else(|| anyhow::anyhow!("no primary file found"))?;
tracing::info!(
"downloading file '{}' version '{}'",
file.file_name,
@ -91,7 +94,7 @@ pub async fn download_package(
.hashes
.get("sha512")
.unwrap_or(&"unknown".to_string())
.to_owned(); // TODO: Figure out sha1
.to_owned();
let package = Package {
file_name: file.file_name.clone(),
@ -122,7 +125,7 @@ async fn download_file(
let state = State::get().await?;
let bytes = http::fetch(
&file.url,
file.hashes.get("sha1").map(|s| s.as_str()),
file.hashes.get("sha1").map(String::as_str),
&state.fetch_semaphore,
)
.await?;
@ -135,6 +138,8 @@ async fn download_file(
return Err(err);
};
drop(state);
Ok(path)
}
@ -150,7 +155,7 @@ pub async fn add_package(
let mut manager = state.packages.write().await;
let manager = manager
.get_mut(cluster_path)
.ok_or(anyhow::anyhow!("cluster not found in packages map"))?;
.ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?;
manager
.add_package(package_path, package, package_type)
@ -170,7 +175,7 @@ pub async fn remove_package(
let mut manager = state.packages.write().await;
let manager = manager
.get_mut(cluster_path)
.ok_or(anyhow::anyhow!("cluster not found in packages map"))?;
.ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?;
manager.remove_package(package_path, package_type).await?;
@ -188,14 +193,14 @@ pub async fn get_package(
let manager = state.packages.read().await;
let manager = manager
.get(cluster_path)
.ok_or(anyhow::anyhow!("cluster not found in packages map"))?;
.ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?;
Ok(manager
.get(package_type)
.packages
.get(package_path)
.cloned()
.ok_or(anyhow::anyhow!("package not found"))?)
.ok_or_else(|| anyhow::anyhow!("package not found"))?)
}
/// Get packages from a cluster.
@ -208,7 +213,7 @@ pub async fn get_packages(
let manager = state.packages.read().await;
let manager = manager
.get(cluster_path)
.ok_or(anyhow::anyhow!("cluster not found in packages map"))?;
.ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?;
Ok(manager
.get(package_type)
@ -225,7 +230,7 @@ pub async fn sync_packages(cluster_path: &ClusterPath) -> Result<()> {
let mut manager = state.packages.write().await;
let manager = manager
.get_mut(cluster_path)
.ok_or(anyhow::anyhow!("cluster not found in packages map"))?;
.ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?;
manager.sync_packages(&state.directories).await;
@ -242,7 +247,7 @@ pub async fn sync_packages_by_type(
let mut manager = state.packages.write().await;
let manager = manager
.get_mut(cluster_path)
.ok_or(anyhow::anyhow!("cluster not found in packages map"))?;
.ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?;
manager
.sync_packages_by_type(&state.directories, package_type)

View file

@ -1,5 +1,5 @@
use crate::utils::io;
use crate::Result;
use onelauncher_utils::io;
use crate::store::ClusterPath;

View file

@ -1,5 +1,5 @@
use crate::utils::io;
use crate::Result;
use onelauncher_utils::io;
use crate::store::ClusterPath;

View file

@ -5,9 +5,9 @@ use crate::package::from::CreatePackCluster;
use crate::proxy::send::send_cluster;
use crate::proxy::ClusterPayloadType;
pub use crate::store::{Cluster, ClusterPath, JavaOptions, PackageData, State};
use crate::utils::io::{self, canonicalize};
use crate::{cluster, package};
use interpulse::api::modded::LoaderVersion;
use onelauncher_utils::io::{self, canonicalize};
use std::path::PathBuf;
/// Creates a [`Cluster`] and adds it to the memory [`State`].
@ -60,10 +60,10 @@ pub async fn create_cluster(
"creating cluster at path {}",
&canonicalize(&path)?.display()
);
let loader = if mod_loader != Loader::Vanilla {
get_loader_version(mc_version.clone(), mod_loader, loader_version).await?
} else {
let loader = if mod_loader == Loader::Vanilla {
None
} else {
get_loader_version(mc_version.clone(), mod_loader, loader_version).await?
};
let mut cluster = Cluster::new(uuid, name, mc_version).await?;
@ -101,8 +101,10 @@ pub async fn create_cluster(
.await?;
{
let mut clusters = state.clusters.write().await;
clusters
state
.clusters
.write()
.await
.insert(cluster.clone(), skip_watch.unwrap_or_default())
.await?;
}
@ -129,7 +131,7 @@ pub async fn create_cluster(
match result {
Ok(cluster) => Ok(cluster),
Err(err) => {
let _ = crate::api::cluster::remove(&cluster.cluster_path()).await;
let _ = cluster::remove(&cluster.cluster_path()).await;
Err(err)
}
@ -170,11 +172,10 @@ pub async fn create_cluster_from_duplicate(from: ClusterPath) -> crate::Result<C
)
.await?;
let state = State::get().await?;
let copied = package::import::copy_minecraft(
cluster_path.clone(),
from.full_path().await?,
&state.io_semaphore,
&State::get().await?.io_semaphore,
None,
)
.await?;
@ -221,8 +222,8 @@ pub(crate) async fn get_loader_version(
"stable" => it.stable,
id => {
it.id == *id
|| format!("{}-{}", mc_version, id) == it.id
|| format!("{}-{}-{}", mc_version, id, mc_version) == it.id
|| format!("{mc_version}-{id}") == it.id
|| format!("{mc_version}-{id}-{mc_version}") == it.id
}
};
@ -237,7 +238,7 @@ pub(crate) async fn get_loader_version(
let loader_meta = loader_meta
.to_owned()
.ok_or(anyhow::anyhow!("couldn't get game versions"))?;
.ok_or_else(|| anyhow::anyhow!("couldn't get game versions"))?;
let loaders = &loader_meta
.game_versions
@ -256,10 +257,12 @@ pub(crate) async fn get_loader_version(
.iter()
.find(|&it| filter(it))
.cloned()
.or(if version == "stable" {
loaders.iter().next().cloned()
} else {
None
.or_else(|| {
if version == "stable" {
loaders.iter().next().cloned()
} else {
None
}
})
.ok_or_else(|| CreateClusterError::InvalidLoaderVersion(version, loader.to_string()))?;

View file

@ -1,4 +1,4 @@
//! **OneLauncher Cluster**
//! **`OneLauncher` Cluster**
//!
//! API for creating our managed Minecraft instances, Clusters.
@ -11,7 +11,7 @@ use crate::proxy::ClusterPayloadType;
pub use crate::store::{Cluster, JavaOptions, State};
use crate::store::{MinecraftCredentials, ProcessorChild};
use crate::utils::io::{self, IOError};
use onelauncher_utils::io::{self, IOError};
use std::collections::HashMap;
use std::future::Future;
@ -66,7 +66,7 @@ pub async fn list_grouped() -> crate::Result<HashMap<String, Vec<Cluster>>> {
.meta
.group
.clone()
.unwrap_or(String::from("Ungrouped"));
.unwrap_or_else(|| String::from("Ungrouped"));
if let Some(items) = map.get_mut(&group) {
items.push(cluster);
} else {
@ -141,7 +141,7 @@ pub async fn run_credentials(
if let Some(command) = cmd.next() {
let full_path = path.full_path().await?;
let result = Command::new(command)
.args(cmd.collect::<Vec<&str>>())
.args(cmd)
.current_dir(&full_path)
.spawn()
.map_err(|e| IOError::with_path(e, &full_path))?
@ -359,8 +359,8 @@ pub async fn get_optimal_java_version(path: &ClusterPath) -> crate::Result<Optio
let metadata = state.metadata.read().await;
let minecraft_metadata = metadata
.minecraft
.to_owned()
.ok_or(anyhow::anyhow!("couldn't get minecraft metadata"))?;
.clone()
.ok_or_else(|| anyhow::anyhow!("couldn't get minecraft metadata"))?;
let version = minecraft_metadata
.versions
@ -421,6 +421,7 @@ pub async fn update_playtime(path: &ClusterPath) -> crate::Result<()> {
}
/// Sanitize a user-inputted [`Cluster`] name.
#[must_use]
pub fn sanitize_cluster_name(input: &str) -> String {
input.replace(['/', '\\', '?', '*', ':', '\'', '\"', '|', '<', '>'], "_")
}

View file

@ -2,7 +2,7 @@ use std::path::PathBuf;
use crate::api::prelude::InternetPayload;
use crate::proxy::send::{send_internet, send_message};
use crate::utils::io;
use onelauncher_utils::io;
pub async fn parse_uri(cmd: &str) -> crate::Result<()> {
let cmd = handle_cmd(cmd).await?;

View file

@ -5,9 +5,9 @@ use serde::Deserialize;
use crate::proxy::send::{init_ingress, send_ingress};
use crate::utils::http::{fetch_advanced, fetch_json};
use crate::utils::io::{self, IOError};
use crate::utils::java::{self, get_java_version, JavaVersion};
use crate::State;
use onelauncher_utils::io::{self, IOError};
pub async fn filter_java_version(java_version: Option<u32>) -> crate::Result<Vec<JavaVersion>> {
let java = java::locate_java().await?;
@ -15,11 +15,7 @@ pub async fn filter_java_version(java_version: Option<u32>) -> crate::Result<Vec
java.into_iter()
.filter(|j| {
let jre_version = get_java_version(&j.version);
if let Ok(jre_version) = jre_version {
jre_version.1 == java_version
} else {
false
}
jre_version.map_or(false, |jre_version| jre_version.1 == java_version)
})
.collect()
} else {
@ -27,6 +23,13 @@ pub async fn filter_java_version(java_version: Option<u32>) -> crate::Result<Vec
})
}
#[derive(Deserialize)]
struct JavaPackage {
pub download_url: String,
pub name: PathBuf,
}
// TODO: support more than just zulu ?
#[onelauncher_macros::memory]
pub async fn install_java(java_version: u32) -> crate::Result<PathBuf> {
let state = State::get().await?;
@ -40,14 +43,8 @@ pub async fn install_java(java_version: u32) -> crate::Result<PathBuf> {
)
.await?;
#[derive(Deserialize)]
struct Package {
pub download_url: String,
pub name: PathBuf,
}
send_ingress(&ingress, 0.0, Some("fetching java api")).await?;
let packages = fetch_json::<Vec<Package>>(Method::GET, &format!(
let packages = fetch_json::<Vec<JavaPackage>>(Method::GET, &format!(
"https://api.azul.com/metadata/v1/zulu/packages?arch={}&java_version={}&os={}&archive_type=zip&javafx_bundled=false&java_package_type=jre&page_size=1",
std::env::consts::ARCH, java_version, std::env::consts::OS,
), None, None, &state.fetch_semaphore).await?;
@ -93,11 +90,11 @@ pub async fn install_java(java_version: u32) -> crate::Result<PathBuf> {
#[cfg(target_os = "macos")]
{
base_path = base_path
.join(format!("zulu-{}.jre", java_version))
.join(format!("zulu-{java_version}.jre"))
.join("Contents")
.join("Home")
.join("bin")
.join("java")
.join("java");
}
#[cfg(not(target_os = "macos"))]
@ -122,9 +119,8 @@ pub async fn check_java(path: PathBuf) -> crate::Result<Option<JavaVersion>> {
}
pub async fn test_java(path: PathBuf, major: u32) -> crate::Result<bool> {
let jvm = match java::check_java_instance(&path).await {
Some(jvm) => jvm,
None => return Ok(false),
let Some(jvm) = java::check_java_instance(&path).await else {
return Ok(false);
};
let (maj, _) = get_java_version(&jvm.version)?;

View file

@ -5,6 +5,7 @@ pub use interpulse::api::minecraft::VersionManifest;
pub use interpulse::api::modded::Manifest;
// TODO: better error handling (prisma?)
// TODO: fix this lmao
/// Get a [`VersionManifest`] for all available Minecraft versions.
#[tracing::instrument]
@ -16,7 +17,7 @@ pub async fn get_minecraft_versions() -> crate::Result<VersionManifest> {
.await
.minecraft
.clone()
.ok_or(anyhow::anyhow!("couldn't get minecraft metadata"))?;
.ok_or_else(|| anyhow::anyhow!("couldn't get minecraft metadata"))?;
Ok(meta)
}
@ -31,7 +32,7 @@ pub async fn get_fabric_versions() -> crate::Result<Manifest> {
.await
.fabric
.clone()
.ok_or(anyhow::anyhow!("couldn't get fabric metadata"))?;
.ok_or_else(|| anyhow::anyhow!("couldn't get fabric metadata"))?;
Ok(meta)
}
@ -46,7 +47,7 @@ pub async fn get_quilt_versions() -> crate::Result<Manifest> {
.await
.quilt
.clone()
.ok_or(anyhow::anyhow!("couldn't get quilt metadata"))?;
.ok_or_else(|| anyhow::anyhow!("couldn't get quilt metadata"))?;
Ok(meta)
}
@ -61,7 +62,7 @@ pub async fn get_forge_versions() -> crate::Result<Manifest> {
.await
.forge
.clone()
.ok_or(anyhow::anyhow!("couldn't get forge metadata"))?;
.ok_or_else(|| anyhow::anyhow!("couldn't get forge metadata"))?;
Ok(meta)
}
@ -76,7 +77,7 @@ pub async fn get_neoforge_versions() -> crate::Result<Manifest> {
.await
.neoforge
.clone()
.ok_or(anyhow::anyhow!("couldn't get neoforge metadata"))?;
.ok_or_else(|| anyhow::anyhow!("couldn't get neoforge metadata"))?;
Ok(meta)
}
@ -91,7 +92,7 @@ pub async fn get_neoforge_versions() -> crate::Result<Manifest> {
// .await
// .legacy_fabric
// .clone()
// .ok_or(anyhow!("missing legacyfabric metadata"))?;
// .ok_or_else(|| anyhow!("missing legacyfabric metadata"))?;
// Ok(meta)
// }

View file

@ -1,4 +1,4 @@
//! **OneLauncher API**
//! **`OneLauncher` API**
//!
//! API for interacting with our Rust core.
@ -26,7 +26,6 @@ pub mod prelude {
pub use crate::data::*;
pub use crate::proxy::InternetPayload;
pub use crate::store::{ClusterPath, JavaVersions, PackageDependency, PackagePath};
pub use crate::utils::io::{canonicalize, IOError};
pub use crate::utils::java::JavaVersion;
pub use crate::{java, metadata, minecraft, package, processor, settings, State};
}

View file

@ -1,6 +1,6 @@
//! **OneLauncher Content Package**
//! **`OneLauncher` Content Package**
//!
//! Utilities for searching and downloading content packages to OneLauncher.
//! Utilities for searching and downloading content packages to `OneLauncher`.
use modrinth::{Facet, FacetOperation};
use serde::{Deserialize, Serialize};
@ -27,19 +27,22 @@ impl std::fmt::Display for Providers {
impl Providers {
/// Get the name of the provider
pub fn name(&self) -> &str {
#[must_use]
pub const fn name(&self) -> &str {
match self {
Providers::Modrinth => "Modrinth",
Self::Modrinth => "Modrinth",
}
}
/// Get the URL of the provider
pub fn url(&self) -> &str {
#[must_use]
pub const fn url(&self) -> &str {
match self {
Providers::Modrinth => "https://modrinth.com",
Self::Modrinth => "https://modrinth.com",
}
}
#[allow(clippy::too_many_arguments)]
pub async fn search(
&self,
query: Option<String>,
@ -52,7 +55,7 @@ impl Providers {
open_source: Option<bool>,
) -> Result<ProviderSearchResults> {
match self {
Providers::Modrinth => modrinth::search(
Self::Modrinth => modrinth::search(
query,
limit,
offset,
@ -112,17 +115,17 @@ impl Providers {
pub async fn list(&self) -> Result<Vec<ManagedPackage>> {
Ok(match self {
Providers::Modrinth => modrinth::list(),
Self::Modrinth => modrinth::list(),
}
.await?
.into_iter()
.map(|p| p.into())
.map(Into::into)
.collect())
}
pub async fn get(&self, slug_or_id: &str) -> Result<ManagedPackage> {
Ok(match self {
Providers::Modrinth => modrinth::get(slug_or_id),
Self::Modrinth => modrinth::get(slug_or_id),
}
.await?
.into())
@ -130,11 +133,11 @@ impl Providers {
pub async fn get_multiple(&self, slug_or_ids: &[String]) -> Result<Vec<ManagedPackage>> {
Ok(match self {
Providers::Modrinth => modrinth::get_multiple(slug_or_ids),
Self::Modrinth => modrinth::get_multiple(slug_or_ids),
}
.await?
.into_iter()
.map(|p| p.into())
.map(Into::into)
.collect())
}
@ -145,27 +148,27 @@ impl Providers {
loaders: Option<Vec<Loader>>,
) -> Result<Vec<ManagedVersion>> {
Ok(match self {
Providers::Modrinth => modrinth::get_all_versions(project_id, game_versions, loaders),
Self::Modrinth => modrinth::get_all_versions(project_id, game_versions, loaders),
}
.await?
.into_iter()
.map(|p| p.into())
.map(Into::into)
.collect())
}
pub async fn get_versions(&self, versions: Vec<String>) -> Result<Vec<ManagedVersion>> {
Ok(match self {
Providers::Modrinth => modrinth::get_versions(versions),
Self::Modrinth => modrinth::get_versions(versions),
}
.await?
.into_iter()
.map(|p| p.into())
.map(Into::into)
.collect())
}
pub async fn get_version(&self, version_id: &str) -> Result<ManagedVersion> {
Ok(match self {
Providers::Modrinth => modrinth::get_version(version_id),
Self::Modrinth => modrinth::get_version(version_id),
}
.await?
.into())
@ -173,7 +176,7 @@ impl Providers {
pub async fn get_authors(&self, author: &Author) -> Result<Vec<ManagedUser>> {
match self {
Providers::Modrinth => modrinth::get_authors(author),
Self::Modrinth => modrinth::get_authors(author),
}
.await
}

View file

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType};
@ -14,7 +15,7 @@ use serde_json::Value;
use super::Providers;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ModrinthPackage {
pub slug: String,
pub title: String,
@ -61,9 +62,9 @@ pub struct ModrinthPackage {
}
impl From<ModrinthPackage> for ManagedPackage {
fn from(value: ModrinthPackage) -> ManagedPackage {
ManagedPackage {
provider: super::Providers::Modrinth,
fn from(value: ModrinthPackage) -> Self {
Self {
provider: Providers::Modrinth,
id: value.id,
title: value.title,
description: value.description,
@ -93,14 +94,14 @@ impl From<ModrinthPackage> for ManagedPackage {
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DonationUrl {
pub id: String,
pub platform: String,
pub url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Gallery {
pub url: String,
pub featured: bool,
@ -119,7 +120,7 @@ struct SearchResults {
total_hits: u32,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ModrinthVersion {
pub game_versions: Vec<String>,
pub loaders: Vec<String>,
@ -140,7 +141,7 @@ pub struct ModrinthVersion {
pub dependencies: Vec<Value>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct File {
pub url: String,
pub filename: String,
@ -151,20 +152,20 @@ pub struct File {
impl From<File> for ManagedVersionFile {
fn from(value: File) -> Self {
ManagedVersionFile {
Self {
url: value.url,
file_name: value.filename,
primary: value.primary,
size: value.size,
file_type: value.file_type,
hashes: Default::default(), // TODO
hashes: HashMap::default(), // TODO
}
}
}
impl From<ModrinthVersion> for ManagedVersion {
fn from(value: ModrinthVersion) -> Self {
ManagedVersion {
Self {
id: value.id,
package_id: value.project_id,
author: value.author_id,
@ -179,7 +180,7 @@ impl From<ModrinthVersion> for ManagedVersion {
downloads: value.downloads,
version_type: value.version_type,
files: value.files.into_iter().map(|f| f.into()).collect(),
files: value.files.into_iter().map(Into::into).collect(),
deps: vec![], // TODO [`ManagedDependency`]?
game_versions: value.game_versions,
loaders: value
@ -204,12 +205,12 @@ pub enum FacetOperation {
impl Display for FacetOperation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
FacetOperation::NotEq => "!=",
FacetOperation::LargeEq => ">=",
FacetOperation::Large => ">",
FacetOperation::SmallEq => "<=",
FacetOperation::Small => "<",
FacetOperation::Eq => "=",
Self::NotEq => "!=",
Self::LargeEq => ">=",
Self::Large => ">",
Self::SmallEq => "<=",
Self::Small => "<",
Self::Eq => "=",
})
}
}
@ -217,7 +218,7 @@ impl Display for FacetOperation {
pub struct Facet(pub String, pub FacetOperation, pub String);
impl Display for Facet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(format!("{}{}{}", self.0, self.1, self.2).as_str())
}
}
@ -227,8 +228,8 @@ pub struct FacetBuilder {
}
impl FacetBuilder {
pub fn builder() -> Self {
FacetBuilder { facets: vec![] }
pub const fn builder() -> Self {
Self { facets: vec![] }
}
pub fn and(&mut self, facet: Facet) -> &Self {
@ -253,7 +254,7 @@ impl FacetBuilder {
if i != 0 {
stringified.push(',');
}
stringified.push_str(format!("\"{}\"", f.to_string()).as_str());
stringified.push_str(format!("\"{f}\"").as_str());
}
stringified.push(']');
@ -293,7 +294,7 @@ pub async fn get_multiple(ids: &[String]) -> Result<Vec<ModrinthPackage>> {
format_url!(
"/projects?ids=[{}]",
ids.iter()
.map(|id| format!("\"{}\"", id))
.map(|id| format!("\"{id}\""))
.collect::<Vec<String>>()
.join(",")
)
@ -335,9 +336,9 @@ where
limit.unwrap_or(10),
offset.unwrap_or(0),
if facets.is_empty() {
"".to_string()
String::new()
} else {
format!("&facets=[{}]", facets)
format!("&facets=[{facets}]")
}
)
.as_str(),
@ -456,7 +457,7 @@ pub async fn get_all_versions(
"game_versions",
&game_versions
.iter()
.map(|v| format!("\"{}\"", v.to_string()))
.map(|v| format!("\"{v}\""))
.collect::<Vec<String>>()
.join(","),
);
@ -469,7 +470,7 @@ pub async fn get_all_versions(
"[{}]",
&loaders
.iter()
.map(|l| format!("\"{}\"", l.to_string()))
.map(|l| format!("\"{l}\""))
.collect::<Vec<String>>()
.join(",")
)
@ -494,7 +495,7 @@ pub async fn get_versions(versions: Vec<String>) -> Result<Vec<ModrinthVersion>>
"/versions?ids=[{}]",
versions
.iter()
.map(|v| format!("\"{}\"", v))
.map(|v| format!("\"{v}\""))
.collect::<Vec<String>>()
.join(",")
)

View file

@ -6,8 +6,8 @@ use crate::proxy::send::{init_ingress, send_ingress};
use crate::proxy::IngressId;
use crate::store::{ClusterStage, PackageSide};
use crate::utils::http::{fetch, fetch_advanced, fetch_json, write_icon};
use crate::utils::io;
use crate::{IngressType, InnerPathLinux, State};
use onelauncher_utils::io;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@ -46,9 +46,9 @@ pub enum PackFileHash {
impl From<String> for PackFileHash {
fn from(s: String) -> Self {
return match s.as_str() {
"sha1" => PackFileHash::Sha1,
"sha512" => PackFileHash::Sha512,
_ => PackFileHash::Unknown(s),
"sha1" => Self::Sha1,
"sha512" => Self::Sha512,
_ => Self::Unknown(s),
};
}
}
@ -120,7 +120,7 @@ pub struct CreatePackCluster {
impl Default for CreatePackCluster {
fn default() -> Self {
CreatePackCluster {
Self {
name: "Untitled".to_string(),
mc_version: "1.20.4".to_string(),
mod_loader: Loader::Vanilla,
@ -150,6 +150,7 @@ pub struct CreatePackDescription {
pub cluster_path: ClusterPath,
}
#[must_use]
pub fn get_cluster_from_pack(location: CreatePackLocation) -> CreatePackCluster {
match location {
CreatePackLocation::FromModrinth {
@ -320,11 +321,11 @@ pub async fn generate_pack_from_file(
/// Sets generated cluster attributes to the pack attributes (using `cluster::edit`).
/// This includes the pack name, icon, game version, loader version, and loader.
#[onelauncher_macros::memory]
pub async fn set_cluster_information(
pub async fn set_cluster_information<S: ::std::hash::BuildHasher>(
cluster_path: ClusterPath,
description: &CreatePackDescription,
backup_name: &str,
deps: &HashMap<PackDependency, String>,
deps: &HashMap<PackDependency, String, S>,
ignore_lock: bool,
) -> crate::Result<()> {
let mut game_version: Option<&String> = None;
@ -353,22 +354,19 @@ pub async fn set_cluster_information(
}
}
let game_version = if let Some(game_version) = game_version {
game_version
} else {
let Some(game_version) = game_version else {
return Err(anyhow::anyhow!("pack did not specify Minecraft version").into());
};
let mod_loader = mod_loader.unwrap_or(Loader::Vanilla);
let loader_version = if mod_loader != Loader::Vanilla {
let loader_version = if mod_loader == Loader::Vanilla {
None
} else {
crate::cluster::create::get_loader_version(
game_version.clone(),
mod_loader,
loader_version.cloned(),
)
.await?
} else {
None
};
crate::api::cluster::edit(&cluster_path, |cl| {
@ -384,10 +382,10 @@ pub async fn set_cluster_information(
Some(PackageData {
package_id,
version_id,
locked: if !ignore_lock {
Some(true)
} else {
locked: if ignore_lock {
cl.meta.package_data.as_ref().and_then(|x| x.locked)
} else {
Some(true)
},
})
} else {

View file

@ -1,5 +1,5 @@
//! Launcher Import: ATLauncher
//! Source Code available at https://github.com/ATLauncher/ATLauncher
//! Launcher Import: `ATLauncher`
//! Source Code available at <https://github.com/ATLauncher/ATLauncher>
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@ -10,7 +10,7 @@ use crate::package::import::{self, copy_minecraft};
use crate::package::{self};
use crate::prelude::{Cluster, ClusterPath, Loader};
use crate::store::{ClusterStage, PackageData, State};
use crate::utils::io;
use onelauncher_utils::io;
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -97,7 +97,7 @@ pub struct ATLauncherMod {
pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool {
let instance: String = io::read_to_string(&instance_folder.join("instance.json"))
.await
.unwrap_or("".to_string());
.unwrap_or(String::new());
let instance: Result<ATInstance, serde_json::Error> =
serde_json::from_str::<ATInstance>(&instance);
if let Err(e) = instance {
@ -157,7 +157,7 @@ pub async fn import_atlauncher(
cluster_path: cluster_path.clone(),
};
let backup_name = format!("ATLauncher-{}", instance_folder);
let backup_name = format!("ATLauncher-{instance_folder}");
let minecraft_folder = atlauncher_instance_path;
import_atlauncher_unmanaged(
@ -186,15 +186,15 @@ async fn import_atlauncher_unmanaged(
let mod_loader: Loader = serde_json::from_str::<Loader>(&mod_loader)?;
let game_version = atinstance.id;
let loader_version = if mod_loader != Loader::Vanilla {
let loader_version = if mod_loader == Loader::Vanilla {
None
} else {
crate::cluster::create::get_loader_version(
game_version.clone(),
mod_loader,
Some(atinstance.launcher.loader_version.version.clone()),
)
.await?
} else {
None
};
crate::api::cluster::edit(&cluster_path, |cl| {

View file

@ -1,4 +1,4 @@
//! Launcher Import: CurseForge
//! Launcher Import: `CurseForge`
//! Closed Source, all this information is datamined unfortunately.
use serde::{Deserialize, Serialize};
@ -8,7 +8,7 @@ use super::{cache_icon, copy_minecraft};
use crate::prelude::{Cluster, ClusterPath, Loader};
use crate::store::{ClusterStage, State};
use crate::utils::http::{fetch, write_icon};
use crate::utils::io;
use onelauncher_utils::io;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
@ -37,12 +37,13 @@ pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool {
let minecraftinstance: String =
io::read_to_string(&instance_folder.join("minecraftinstance.json"))
.await
.unwrap_or("".to_string());
.unwrap_or(String::new());
let minecraftinstance: Result<MinecraftInstance, serde_json::Error> =
serde_json::from_str::<MinecraftInstance>(&minecraftinstance);
minecraftinstance.is_ok()
}
#[allow(clippy::too_many_lines)]
pub async fn import_curseforge(
// The path to the CurseForge instance
curseforge_instance_folder: PathBuf,
@ -58,8 +59,7 @@ pub async fn import_curseforge(
"Curseforge-{}",
curseforge_instance_folder
.file_name()
.map(|a| a.to_string_lossy().to_string())
.unwrap_or("Unknown".to_string())
.map_or("Unknown".to_string(), |a| a.to_string_lossy().to_string())
);
let state = State::get().await?;
@ -104,15 +104,15 @@ pub async fn import_curseforge(
}
let mod_loader = mod_loader.unwrap_or(Loader::Vanilla);
let loader_version = if mod_loader != Loader::Vanilla {
let loader_version = if mod_loader == Loader::Vanilla {
None
} else {
crate::cluster::create::get_loader_version(
game_version.clone(),
mod_loader,
loader_version,
)
.await?
} else {
None
};
crate::api::cluster::edit(&cluster_path, |cl| {

View file

@ -1,2 +1,2 @@
//! Launcher Import: FTB App
//! Source Code available at https://github.com/FTBTeam/FTB-App
//! Source Code available at <https://github.com/FTBTeam/FTB-App>

View file

@ -1,5 +1,5 @@
//! Launcher Import: GDLauncher (legacy)
//! Source Code available at https://github.com/gorilla-devs/GDLauncher
//! Launcher Import: `GDLauncher` (legacy)
//! Source Code available at <https://github.com/gorilla-devs/GDLauncher>
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
@ -7,7 +7,7 @@ use std::path::PathBuf;
use super::{cache_icon, copy_minecraft};
use crate::prelude::{Cluster, ClusterPath, Loader};
use crate::store::{ClusterStage, State};
use crate::utils::io;
use onelauncher_utils::io;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -30,7 +30,7 @@ pub struct GDLauncherLoader {
pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool {
let config: String = io::read_to_string(&instance_folder.join("config.json"))
.await
.unwrap_or("".to_string());
.unwrap_or(String::new());
let config: Result<GDLauncherConfig, serde_json::Error> =
serde_json::from_str::<GDLauncherConfig>(&config);
config.is_ok()
@ -48,8 +48,7 @@ pub async fn import_gdlauncher(
"GDLauncher-{}",
gdlauncher_instance_folder
.file_name()
.map(|a| a.to_string_lossy().to_string())
.unwrap_or("Unknown".to_string())
.map_or("Unknown".to_string(), |a| a.to_string_lossy().to_string())
);
let icon = config
@ -66,11 +65,11 @@ pub async fn import_gdlauncher(
let mod_loader = config.loader.loader_type;
let loader_version = config.loader.loader_version;
let loader_version = if mod_loader != Loader::Vanilla {
let loader_version = if mod_loader == Loader::Vanilla {
None
} else {
crate::cluster::create::get_loader_version(game_version.clone(), mod_loader, loader_version)
.await?
} else {
None
};
crate::api::cluster::edit(&cluster_path, |cl| {

View file

@ -6,7 +6,7 @@ use crate::proxy::IngressId;
use crate::store::Clusters;
use crate::utils::http::{self, IoSemaphore};
use crate::utils::io::{self, IOError};
use onelauncher_utils::io::{self, IOError};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::{Path, PathBuf};
@ -24,19 +24,19 @@ pub mod tlauncher;
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ImportType {
/// MultiMC based launchers
/// `MultiMC` based launchers
MultiMC,
/// Prism Launcher has its own category because it has different logic (also is objectively better than mmc)
PrismLauncher,
/// GDLauncher
/// `GDLauncher`
GDLauncher,
/// Curseforge's launcher
Curseforge,
/// ATLauncher
/// `ATLauncher`
ATLauncher,
/// Modrinth app.
Modrinth,
/// TLauncher
/// `TLauncher`
TLauncher,
/// FTB Launcher
FTBLauncher,
@ -50,16 +50,16 @@ pub enum ImportType {
impl fmt::Display for ImportType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ImportType::MultiMC => write!(f, "MultiMC"),
ImportType::ATLauncher => write!(f, "ATLauncher"),
ImportType::Curseforge => write!(f, "Curseforge"),
ImportType::GDLauncher => write!(f, "GDLauncher"),
ImportType::Modrinth => write!(f, "Modrinth App"),
ImportType::PrismLauncher => write!(f, "PrismLauncher"),
ImportType::TLauncher => write!(f, "TLauncher"),
ImportType::FTBLauncher => write!(f, "Feed The Beast"),
ImportType::Technic => write!(f, "Technic"),
ImportType::Unknown => write!(f, "Unknown"),
Self::MultiMC => write!(f, "MultiMC"),
Self::ATLauncher => write!(f, "ATLauncher"),
Self::Curseforge => write!(f, "Curseforge"),
Self::GDLauncher => write!(f, "GDLauncher"),
Self::Modrinth => write!(f, "Modrinth App"),
Self::PrismLauncher => write!(f, "PrismLauncher"),
Self::TLauncher => write!(f, "TLauncher"),
Self::FTBLauncher => write!(f, "Feed The Beast"),
Self::Technic => write!(f, "Technic"),
Self::Unknown => write!(f, "Unknown"),
}
}
}
@ -77,9 +77,9 @@ pub async fn import_instances(import: ImportType, path: PathBuf) -> crate::Resul
.unwrap_or_else(|| "instances".to_string())
}
ImportType::Modrinth => "profiles".to_string(),
ImportType::TLauncher => "instances".to_string(),
ImportType::FTBLauncher => "instances".to_string(),
ImportType::Technic => "instances".to_string(),
ImportType::TLauncher => "tinstances".to_string(),
ImportType::FTBLauncher => "ftbinstances".to_string(),
ImportType::Technic => "tecinstances".to_string(),
_ => return Err(anyhow::anyhow!("launcher type unknown, cant import").into()),
};
@ -152,7 +152,7 @@ pub async fn import_instance(
};
match result {
Ok(_) => {}
Ok(()) => {}
Err(e) => {
tracing::warn!("failed to import modpack: {:?}", e);
let _ = crate::api::cluster::remove(&cluster_path).await;
@ -167,6 +167,7 @@ pub async fn import_instance(
}
/// returns the default path for a given [`ImportType`].
#[must_use]
pub fn default_launcher_path(r#type: ImportType) -> Option<PathBuf> {
let path = match r#type {
ImportType::MultiMC => None, // MultiMC data is in it's application directory

View file

@ -1,11 +1,11 @@
//! Launcher Import: Modrinth App
//! Source Code available at https://github.com/modrinth/code
//! Source Code available at <https://github.com/modrinth/code>
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::prelude::ClusterPath;
use crate::utils::io;
use onelauncher_utils::io;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ModrinthProfile {}
@ -13,7 +13,7 @@ pub struct ModrinthProfile {}
pub async fn is_valid_modrinth(instance_folder: PathBuf) -> bool {
let config: String = io::read_to_string(&instance_folder.join("profile.json"))
.await
.unwrap_or("".to_string());
.unwrap_or(String::new());
let config: Result<ModrinthProfile, serde_json::Error> =
serde_json::from_str::<ModrinthProfile>(&config);
config.is_ok()

View file

@ -1,6 +1,6 @@
//! Launcher Import: MultiMC & Prism Launcher
//! Source Code available at https://github.com/PrismLauncher/PrismLauncher
//! Source Code available at https://github.com/MultiMC/Launcher
//! Launcher Import: `MultiMC` & Prism Launcher
//! Source Code available at <https://github.com/PrismLauncher/PrismLauncher>
//! Source Code available at <https://github.com/MultiMC/Launcher>
use serde::{de, Deserialize, Serialize};
use std::path::{Path, PathBuf};
@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
use crate::package::from::{self, CreatePackDescription, PackDependency};
use crate::package::import::{self, copy_minecraft};
use crate::prelude::{Cluster, ClusterPath, State};
use crate::utils::io;
use onelauncher_utils::io;
// instance.cfg: https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/MinecraftInstance.cpp
#[derive(Serialize, Deserialize, Debug)]
@ -53,15 +53,14 @@ fn deserialize_optional_bool<'de, D>(deserializer: D) -> Result<Option<bool>, D:
where
D: de::Deserializer<'de>,
{
let s = Option::<String>::deserialize(deserializer)?;
match s {
Some(string) => match string.as_str() {
Option::<String>::deserialize(deserializer)?.map_or_else(
|| Ok(None),
|string| match string.as_str() {
"true" => Ok(Some(true)),
"false" => Ok(Some(false)),
_ => Err(de::Error::custom("expected 'true' or 'false'")),
},
None => Ok(None),
}
)
}
#[derive(Serialize, Deserialize, Debug)]
@ -139,10 +138,8 @@ pub struct MMCLauncher {
pub async fn is_valid_multibased(instance_folder: PathBuf) -> bool {
let instance_cfg = instance_folder.join("instance.cfg");
let mmc_pack = instance_folder.join("mmc-pack.json");
let mmc_pack = match io::read_to_string(&mmc_pack).await {
Ok(mmc_pack) => mmc_pack,
Err(_) => return false,
let Ok(mmc_pack) = io::read_to_string(&mmc_pack).await else {
return false;
};
load_instance_cfg(&instance_cfg).await.is_ok()
@ -217,7 +214,7 @@ pub async fn import_mmc(
)
.await?;
}
Some(MMCManagedPackType::Flame) | Some(MMCManagedPackType::ATLauncher) => {
Some(MMCManagedPackType::Flame | MMCManagedPackType::ATLauncher) => {
// Same as unmanaged, but with 'minecraft' folder instead of '.minecraft'
let minecraft_folder = mmc_base_path
.join("instances")
@ -313,7 +310,7 @@ async fn import_mmc_unmanaged(
})
.collect();
from::set_cluster_information(
from::set_cluster_information::<std::hash::RandomState>(
cluster_path.clone(),
&description,
&backup_name,

View file

@ -1,2 +1,2 @@
//! Launcher Import: Technic Launcher
//! Source Code available at https://github.com/TechnicPack/LauncherV3
//! Source Code available at <https://github.com/TechnicPack/LauncherV3>

View file

@ -1,2 +1,2 @@
//! Launcher Import: TLauncher
//! Launcher Import: `TLauncher`
//! Closed source (technically) ;3

View file

@ -1,6 +1,6 @@
//! **OneLauncher Package**
//! **`OneLauncher` Package**
//!
//! Utilities for importing packages from and to OneLauncher.
//! Utilities for importing packages from and to `OneLauncher`.
pub mod content;
pub mod from;

View file

@ -6,10 +6,11 @@ use crate::proxy::ingress_try_for_each;
use crate::proxy::send::{init_or_edit_ingress, send_ingress};
use crate::store::{ClusterStage, Clusters, PackageSide};
use crate::utils::http::{fetch_from_mirrors, write};
use crate::utils::io;
use crate::{cluster, IngressType, State};
use async_zip::base::read::seek::ZipFileReader;
use onelauncher_utils::io;
use futures::StreamExt;
use std::io::Cursor;
use std::path::{Component, PathBuf};
@ -55,7 +56,7 @@ pub async fn install_zipped_mrpack(
match result {
Ok(cluster) => Ok(cluster),
Err(err) => {
let _ = crate::api::cluster::remove(&cluster_path).await;
let _ = cluster::remove(&cluster_path).await;
Err(err)
}
@ -121,7 +122,6 @@ pub async fn install_zipped_mrpack_files(
.await?;
let num_files = pack.files.len();
use futures::StreamExt;
ingress_try_for_each(
futures::stream::iter(pack.files.into_iter()).map(Ok::<PackFile, crate::Error>),
None,
@ -136,8 +136,7 @@ pub async fn install_zipped_mrpack_files(
if let Some(env) = pack.env {
if env
.get(&EnvType::Client)
.map(|x| x == &PackageSide::Unsupported)
.unwrap_or(false)
.is_some_and(|x| x == &PackageSide::Unsupported)
{
return Ok(());
}
@ -208,8 +207,8 @@ pub async fn install_zipped_mrpack_files(
send_ingress(
&ingress,
30.0 / total_len as f64,
Some(&format!("extracting override {}/{}", index, total_len)),
30.0 / f64::from(total_len),
Some(&format!("extracting override {index}/{total_len}")),
)
.await?;
}
@ -259,7 +258,7 @@ pub async fn remove_all_related_files(
return Err(anyhow::anyhow!("pack doesn't support Minecraft").into());
}
crate::api::cluster::edit(&cluster_path, |cl| {
cluster::edit(&cluster_path, |cl| {
cl.stage = ClusterStage::PackDownloading;
async { Ok(()) }
})

View file

@ -128,7 +128,7 @@ pub async fn send_ingress(
{
ingress.cli.set_message(
message
.map(|x| x.to_string())
.map(ToString::to_string)
.unwrap_or(ingress.message.clone()),
);
ingress

View file

@ -3,6 +3,7 @@
use crate::proxy::send::{init_ingress, send_ingress};
use crate::store::{Clusters, Directories, Settings};
use crate::State;
use onelauncher_utils::io;
use std::path::{Path, PathBuf};
use tokio::sync::RwLock;
@ -91,7 +92,7 @@ pub async fn set_directory(new: PathBuf) -> crate::Result<()> {
state_write.watcher = RwLock::new(fs_watcher);
tracing::trace!("collecting all config files to be transfered");
let mut cfg_entries = crate::utils::io::read_dir(&old).await?;
let mut cfg_entries = io::read_dir(&old).await?;
let across_filesystems = is_different_fs(&old, &new);
let mut entries = vec![];
let mut cleanable = vec![];
@ -99,7 +100,7 @@ pub async fn set_directory(new: PathBuf) -> crate::Result<()> {
while let Some(e) = cfg_entries
.next_entry()
.await
.map_err(|err| crate::utils::io::IOError::with_path(err, &old))?
.map_err(|err| io::IOError::with_path(err, &old))?
{
let epath = e.path();
if let Some(file_name) = epath.file_name() {
@ -127,7 +128,7 @@ pub async fn set_directory(new: PathBuf) -> crate::Result<()> {
if across_filesystems {
crate::utils::http::copy(&epath, &newpath, io_semaphore).await?;
} else {
crate::utils::io::rename(epath.clone(), newpath.clone()).await?;
io::rename(epath.clone(), newpath.clone()).await?;
}
send_ingress(&ingress, 80.0 * (1.0 / nentries), None).await?;
@ -161,7 +162,7 @@ pub async fn set_directory(new: PathBuf) -> crate::Result<()> {
tracing::trace!("deleting old config files");
}
for ce in cleanable {
crate::utils::io::remove_dir_all(ce).await?;
io::remove_dir_all(ce).await?;
send_ingress(&ingress, 10.0 * (1.0 / cleanable_nentries as f64), None).await?;
}
@ -181,18 +182,18 @@ pub async fn set_directory(new: PathBuf) -> crate::Result<()> {
}
/// checks if two paths are on different data storage devices
fn is_different_fs(a: &Path, b: &Path) -> bool {
let roota = a.components().next();
let rootb = b.components().next();
roota != rootb
fn is_different_fs(first: &Path, second: &Path) -> bool {
let first_root = first.components().next();
let second_root = second.components().next();
first_root != second_root
}
/// checks if a [`PathBuf`] is writable or locked.
pub async fn is_writable(path: PathBuf) -> crate::Result<bool> {
let tmp = path.join(".tmp");
match crate::utils::io::write(tmp.clone(), "test").await {
Ok(_) => {
crate::utils::io::remove_file(tmp).await?;
match io::write(tmp.clone(), "test").await {
Ok(()) => {
io::remove_file(tmp).await?;
Ok(true)
}
Err(e) => {

View file

@ -1,4 +1,4 @@
//! **OneLauncher Constants**
//! **`OneLauncher` Constants**
//!
//! Public constant variables and strings that are used throughout the launcher.
//! Can be shared publically, any secrets should be stored in [`crate::store::credentials`].
@ -39,7 +39,7 @@ pub const MODRINTH_V3_API_URL: &str = "https://api.modrinth.com/v3";
pub const CURSEFORGE_API_URL: &str = "https://api.curseforge.com/";
/// Our metadata API base url.
pub const METADATA_API_URL: &str = "https://meta.polyfrost.org";
/// https://mclo.gs/ API base url.
/// <https://mclo.gs>/ API base url.
pub const MCLOGS_API_URL: &str = "https://api.mclo.gs/1";
// =========== Paths ===========

View file

@ -1,6 +1,6 @@
//! **OneLauncher Errors**
//! **`OneLauncher` Errors**
//!
//! Public wrappers around common OneLauncher errors.
//! Public wrappers around common `OneLauncher` errors.
use tracing_error::InstrumentError;
@ -83,7 +83,7 @@ pub enum ErrorKind {
#[error("failed to watch file {0}")]
NotifyError(#[from] notify::Error),
/// Wrapper around [`regex::Error`] to handle RegExp errors.
/// Wrapper around [`regex::Error`] to handle `RegExp` errors.
#[error("string verification with regex failed: {0}")]
RegexError(#[from] regex::Error),
@ -99,11 +99,11 @@ pub enum ErrorKind {
#[error("checksum {0} did not match {1}!")]
HashError(String, String),
/// Wrapper around [`crate::utils::io::IOError`] to handle wrapped std::io errors.
/// Wrapper around [`onelauncher_utils::io::IOError`] to handle wrapped `std::io` errors.
#[error("error handling IO operations: {0}")]
IOError(#[from] crate::utils::io::IOError),
IOError(#[from] onelauncher_utils::io::IOError),
/// Wrapper around [`std::io::Error`] to handle non-wrapped std::io errors.
/// Wrapper around [`std::io::Error`] to handle non-wrapped `std::io` errors.
#[error("error handling standard IO operations: {0}")]
StdIOError(#[from] std::io::Error),

View file

@ -11,8 +11,8 @@ use interpulse::utils::get_path_from_artifact;
use crate::constants::DUMMY_REPLACE_NEWLINE;
use crate::store::{Memory, MinecraftCredentials, Resolution};
use crate::utils::io::IOError;
use crate::utils::platform::classpath_separator;
use onelauncher_utils::io::IOError;
use onelauncher_utils::platform::classpath_separator;
#[allow(clippy::too_many_arguments)]
pub fn java_arguments(
@ -45,7 +45,7 @@ pub fn java_arguments(
} else {
parsed.push(format!(
"-Djava.library.path={}",
dunce::canonicalize(natives_path)
onelauncher_utils::io::canonicalize(natives_path)
.map_err(|_| anyhow::anyhow!(
"specified natives path {} not found",
natives_path.to_string_lossy()
@ -146,12 +146,12 @@ pub fn processor_arguments<T: AsRef<str>>(
)?
} else {
entry.client.clone()
})
});
}
} else if arg.as_ref().starts_with('[') {
parsed.push(get_library(libraries_path, a, true)?)
parsed.push(get_library(libraries_path, a, true)?);
} else {
parsed.push(arg.as_ref().to_string())
parsed.push(arg.as_ref().to_string());
}
}
@ -214,36 +214,15 @@ fn parse_minecraft_argument(
.replace("${assets_index_name}", asset_index)
.replace(
"${game_directory}",
&dunce::canonicalize(game_directory)
.map_err(|_| {
anyhow::anyhow!(
"game directory {} doesn't exist",
game_directory.to_string_lossy()
)
})?
.to_string_lossy(),
&onelauncher_utils::io::canonicalize(game_directory)?.to_string_lossy(),
)
.replace(
"${assets_root}",
&dunce::canonicalize(assets_directory)
.map_err(|_| {
anyhow::anyhow!(
"assets directory {} doesn't exist",
assets_directory.to_string_lossy()
)
})?
.to_string_lossy(),
&onelauncher_utils::io::canonicalize(assets_directory)?.to_string_lossy(),
)
.replace(
"${game_assets}",
&dunce::canonicalize(assets_directory)
.map_err(|_| {
anyhow::anyhow!(
"assets directory {} doesn't exist",
assets_directory.to_string_lossy()
)
})?
.to_string_lossy(),
&onelauncher_utils::io::canonicalize(assets_directory)?.to_string_lossy(),
)
.replace("${version_type}", version_type.as_str())
.replace("${resolution_width}", &resolution.0.to_string())
@ -263,25 +242,11 @@ fn parse_java_argument(
Ok(argument
.replace(
"${natives_directory}",
&dunce::canonicalize(natives_path)
.map_err(|_| {
anyhow::anyhow!(
"natives path {} doesn't exist",
natives_path.to_string_lossy()
)
})?
.to_string_lossy(),
&onelauncher_utils::io::canonicalize(natives_path)?.to_string_lossy(),
)
.replace(
"${library_directory}",
&dunce::canonicalize(libraries_path)
.map_err(|_| {
anyhow::anyhow!(
"libraries path {} doesn't eixst",
libraries_path.to_string_lossy()
)
})?
.to_string_lossy(),
&onelauncher_utils::io::canonicalize(libraries_path)?.to_string_lossy(),
)
.replace("${classpath_seperator}", classpath_separator(java_arch))
.replace("${launcher_name}", crate::constants::NAME)
@ -315,13 +280,7 @@ pub fn classpaths(
.collect::<Result<HashSet<_>, _>>()?;
classpaths.insert(
dunce::canonicalize(client_path)
.map_err(|_| {
anyhow::anyhow!(
"specified classpath {} not found",
client_path.to_string_lossy()
)
})?
onelauncher_utils::io::canonicalize(client_path)?
.to_string_lossy()
.to_string(),
);
@ -357,8 +316,7 @@ pub fn get_library(
return Ok(path.to_string_lossy().to_string());
}
let path = &dunce::canonicalize(&path)
.map_err(|_| anyhow::anyhow!("library file {} not found", path.to_string_lossy()))?;
let path = &onelauncher_utils::io::canonicalize(&path)?;
Ok(path.to_string_lossy().to_string())
}

View file

@ -13,9 +13,9 @@ use crate::proxy::send::send_ingress;
use crate::proxy::utils::ingress_try_for_each;
use crate::proxy::IngressId;
use crate::store::State;
use crate::utils::http::*;
use crate::utils::io;
use crate::utils::platform::OsExt;
use crate::utils::http::{fetch, fetch_json, write};
use onelauncher_utils::io;
use onelauncher_utils::platform::OsExt;
#[tracing::instrument(skip(st, version))]
pub async fn download_minecraft(
@ -28,12 +28,7 @@ pub async fn download_minecraft(
) -> crate::Result<()> {
tracing::info!("downloading minecraft version {}", version.id);
let assets_index = download_assets_index(st, version, Some(ingress), force).await?;
let processors = if version
.processors
.as_ref()
.map(|p| !p.is_empty())
.unwrap_or(false)
{
let processors = if version.processors.as_ref().is_some_and(|p| !p.is_empty()) {
25.0
} else {
40.0
@ -294,11 +289,11 @@ pub async fn download_libraries(
let reader = std::io::Cursor::new(&data);
if let Ok(mut archive) = zip::ZipArchive::new(reader) {
match archive.extract(st.directories.version_natives_dir(version).await) {
Ok(_) => tracing::debug!("downloaded native {}", &lib.name),
Ok(()) => tracing::debug!("downloaded native {}", &lib.name),
Err(err) => tracing::error!("failed to download native {}: {}", &lib.name, err)
}
} else {
tracing::error!("failed to extract native {}", &lib.name)
tracing::error!("failed to extract native {}", &lib.name);
}
}
}

View file

@ -1,4 +1,4 @@
//! **OneLauncher Game**
//! **`OneLauncher` Game**
//!
//! Manages, installs, and launches the core Minecraft game.
@ -9,7 +9,7 @@ use crate::proxy::{IngressId, IngressType};
use crate::store::{
self as st, Cluster, ClusterStage, MinecraftCredentials, ProcessorChild, State,
};
use crate::utils::io::{self, IOError};
use onelauncher_utils::io::{self, IOError};
use chrono::Utc;
use interpulse as ip;
@ -74,7 +74,7 @@ pub async fn install_minecraft(
let instance_path = &io::canonicalize(cluster.get_full_path().await?)?;
let metadata = state.metadata.read().await;
let version_manifest = metadata.minecraft.to_owned();
let version_manifest = metadata.minecraft.clone();
let versions = version_manifest
.ok_or(anyhow::anyhow!("couldn't get minecraft manifest"))?
.versions;
@ -108,8 +108,7 @@ pub async fn install_minecraft(
let key = version_info
.java_version
.as_ref()
.map(|it| it.major_version)
.unwrap_or(8);
.map_or(8, |it| it.major_version);
let (java_version, set_java) =
if let Some(java_version) = java_version_from_cluster(cluster, &version_info).await? {
(std::path::PathBuf::from(java_version.path), false)
@ -177,8 +176,8 @@ pub async fn install_minecraft(
}
}
let cp = ref_owned!(cp = processor.classpath.clone() => {
cp.push(processor.jar.clone())
let cp = onelauncher_utils::ref_owned!(cp = processor.classpath.clone() => {
cp.push(processor.jar.clone());
});
let child = Command::new(&java_version.path)
@ -223,10 +222,7 @@ pub async fn install_minecraft(
send_ingress(
&ingress,
30.0 / total_length as f64,
Some(&format!(
"running forge processor {}/{}",
index, total_length
)),
Some(&format!("running forge processor {index}/{total_length}")),
)
.await?;
}
@ -273,7 +269,7 @@ pub async fn launch_minecraft(
let instance_path = cluster.get_full_path().await?;
let instance_path = &io::canonicalize(instance_path)?;
let version_manifest = metadata.minecraft.to_owned();
let version_manifest = metadata.minecraft.clone();
let versions = version_manifest
.ok_or(anyhow::anyhow!("couldn't get minecraft manifest"))?
.versions;
@ -316,7 +312,9 @@ pub async fn launch_minecraft(
.join(format!("{version_jar}.jar"));
let args = version_info.arguments.clone().unwrap_or_default();
let mut command = match wrapper {
Some(hook) => ref_owned!(it = Command::new(hook) => {it.arg(&java_version.path)}),
Some(hook) => {
onelauncher_utils::ref_owned!(it = Command::new(hook) => {it.arg(&java_version.path)})
}
None => Command::new(&java_version.path),
};
let env_args = Vec::from(env_args);
@ -335,7 +333,7 @@ pub async fn launch_minecraft(
.args(
arguments::java_arguments(
args.get(&ip::api::minecraft::ArgumentType::Jvm)
.map(|x| x.as_slice()),
.map(Vec::as_slice),
&state.directories.version_natives_dir(&version_jar).await,
&state.directories.libraries_dir().await,
&arguments::classpaths(
@ -350,14 +348,13 @@ pub async fn launch_minecraft(
Vec::from(java_args),
&java_version.arch,
)?
.into_iter()
.collect::<Vec<_>>(),
.into_iter(),
)
.arg(version_info.main_class.clone())
.args(
arguments::minecraft_arguments(
args.get(&ip::api::minecraft::ArgumentType::Game)
.map(|a| a.as_slice()),
.map(Vec::as_slice),
version_info.minecraft_arguments.as_deref(),
credentials,
&version.id,
@ -368,8 +365,7 @@ pub async fn launch_minecraft(
*resolution,
&java_version.arch,
)?
.into_iter()
.collect::<Vec<_>>(),
.into_iter(),
)
.current_dir(instance_path.clone());
@ -393,10 +389,10 @@ pub async fn launch_minecraft(
for (key, value) in mc_options {
let re = regex::Regex::new(&format!(r"(?m)^{}:.*$", regex::escape(key)))?;
if !re.is_match(&options_string) {
options_string.push_str(&format!("\n{}:{}", key, value));
options_string.push_str(&format!("\n{key}:{value}"));
} else {
let replaced_string = re
.replace_all(&options_string, &format!("{}:{}", key, value))
.replace_all(&options_string, &format!("{key}:{value}"))
.to_string();
options_string = replaced_string;
}
@ -416,16 +412,10 @@ pub async fn launch_minecraft(
let mut censors = HashMap::new();
let username = whoami::username();
let realname = whoami::realname();
censors.insert(format!("/{}/", username), "/{ENV_USERNAME}/".to_string());
censors.insert(
format!("\\{}\\", username),
"\\{ENV_USERNAME}\\".to_string(),
);
censors.insert(format!("/{}/", realname), "/{ENV_REALNAME}/".to_string());
censors.insert(
format!("\\{}\\", realname),
"\\{ENV_REALNAME}\\".to_string(),
);
censors.insert(format!("/{username}/"), "/{ENV_USERNAME}/".to_string());
censors.insert(format!("\\{username}\\"), "\\{ENV_USERNAME}\\".to_string());
censors.insert(format!("/{realname}/"), "/{ENV_REALNAME}/".to_string());
censors.insert(format!("\\{realname}\\"), "\\{ENV_REALNAME}\\".to_string());
censors.insert(
credentials.access_token.clone(),
"{MC_ACCESS_TOKEN}".to_string(),
@ -483,10 +473,10 @@ pub fn rules(rules: &[ip::api::minecraft::Rule], java_version: &str, updated: bo
.iter()
.all(|r| matches!(r.action, RuleAction::Disallow))
{
rule.push(Some(true))
rule.push(Some(true));
}
!(rule.iter().any(|r| r == &Some(false)) || rule.iter().all(|r| r.is_none()))
!(rule.iter().any(|r| r == &Some(false)) || rule.iter().all(Option::is_none))
}
/// Parses a Minecraft library feature or OS rule.
@ -500,7 +490,7 @@ pub fn rule(rule: &ip::api::minecraft::Rule, java_version: &str, updated: bool)
let result = match rule {
Rule {
os: Some(ref os), ..
} => crate::utils::platform::os_rule(os, java_version, updated),
} => onelauncher_utils::platform::os_rule(os, java_version, updated),
Rule {
features: Some(ref features),
..
@ -543,8 +533,7 @@ pub async fn java_version_from_cluster(
let key = version_info
.java_version
.as_ref()
.map(|x| x.major_version)
.unwrap_or(8);
.map_or(8, |x| x.major_version);
let state = State::get().await?;
let settings = state.settings.read().await;

View file

@ -1,9 +1,43 @@
//! # OneLauncher
//! # `OneLauncher`
//!
//! A library used as a core for our launcher and Rust APIs.
#![warn(unused_import_braces)]
#![warn(
clippy::all,
clippy::pedantic,
clippy::correctness,
clippy::perf,
clippy::style,
clippy::suspicious,
clippy::complexity,
clippy::nursery,
clippy::unwrap_used,
unused_qualifications,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_allocation,
clippy::unnecessary_cast,
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::dbg_macro,
unused_import_braces,
clippy::deprecated_cfg_attr,
clippy::separated_literal_suffix,
deprecated
)]
#![forbid(deprecated_in_future)]
#![deny(unused_must_use)]
#![allow(
clippy::missing_errors_doc,
clippy::future_not_send,
clippy::module_name_repetitions,
clippy::struct_field_names,
clippy::cast_precision_loss,
clippy::significant_drop_tightening //tmp
)]
#[macro_use]
pub mod utils;

View file

@ -1,9 +1,9 @@
//! **OneLauncher Logger**
//! **`OneLauncher` Logger**
//!
//! Public utilities for [`tracing`] logging in OneLauncher.
//! Public utilities for [`tracing`] logging in `OneLauncher`.
//!
//! - `RUST_LOG=xxx` is a variable which defines tracing configuration (`ERROR > WARN > INFO > DEBUG > TRACE`)
//! - `RUST_LOG="onelauncher=trace"` will show **all** mesages from OneLauncher and not other dependencies.
//! - `RUST_LOG="onelauncher=trace"` will show **all** mesages from `OneLauncher` and not other dependencies.
//! - `RUST_LOG="info"` will show info, warn, and error logs.
//!
//! Errors returned to [`tauri`] are displayed as tracing error logs.
@ -11,7 +11,7 @@
//! Tracing errors also provide Sentry metadata to be handled.
//! To set the environment variable on `unix` you can use the following command: `RUST_LOG="xxx" {your command}`
//!
//! The default `RUST_LOG` is `onelauncher=show`, meaning only logs from OneLauncher will be displayed at the info, warn, and error levels.
//! The default `RUST_LOG` is `onelauncher=show`, meaning only logs from `OneLauncher` will be displayed at the info, warn, and error levels.
use tracing_appender::non_blocking::WorkerGuard;
@ -19,6 +19,7 @@ use tracing_appender::non_blocking::WorkerGuard;
/// Initialize the global [`tracing`] logger in development.
#[cfg(debug_assertions)]
#[must_use]
pub fn start_logger() -> Option<WorkerGuard> {
use tracing_subscriber::prelude::*;

View file

@ -14,14 +14,14 @@ use crate::constants::CLUSTER_FILE;
use crate::proxy::send::{send_cluster, send_message};
use crate::proxy::ClusterPayloadType;
use crate::utils::http::{write_icon, IoSemaphore};
use crate::utils::io::{self, IOError};
use crate::utils::java::JavaVersion;
use crate::State;
use onelauncher_utils::io::{self, IOError};
use super::{Directories, InitHooks, Memory, PackageType, Resolution};
/// Core Cluster state manager with a [`HashMap<ClusterPath, Cluster>`].
pub(crate) struct Clusters(pub HashMap<ClusterPath, Cluster>);
pub struct Clusters(pub HashMap<ClusterPath, Cluster>);
/// Core Cluster stages used in package logic.
#[cfg_attr(feature = "specta", derive(specta::Type))]
@ -40,7 +40,8 @@ pub enum ClusterStage {
}
impl ClusterStage {
pub fn as_str(&self) -> &'static str {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match *self {
Self::Installed => "installed",
Self::Downloading => "downloading",
@ -73,7 +74,7 @@ pub struct ClusterPath(pub PathBuf);
impl ClusterPath {
/// Get a new [`ClusterPath`] instance from an [`Into<PathBuf>`].
pub fn new(path: impl Into<PathBuf>) -> Self {
ClusterPath(path.into())
Self(path.into())
}
pub async fn find_by_uuid(uuid: Uuid) -> crate::Result<Self> {
@ -106,7 +107,7 @@ impl ClusterPath {
}
/// Validate the cluster and clone the current [`ClusterPath`].
pub async fn cluster_path(&self) -> crate::Result<ClusterPath> {
pub async fn cluster_path(&self) -> crate::Result<Self> {
if let Some(c) = crate::cluster::get(self).await? {
Ok(c.cluster_path())
} else {
@ -146,6 +147,7 @@ pub struct InnerPathLinux(pub String);
impl InnerPathLinux {
/// Get the first 2 components of the inner path.
#[must_use]
pub fn get_components(&self) -> String {
self.to_string()
.split('/')
@ -163,7 +165,7 @@ impl std::fmt::Display for InnerPathLinux {
impl From<RawPackagePath> for InnerPathLinux {
fn from(value: RawPackagePath) -> Self {
InnerPathLinux(value.0.replace('\\', "/"))
Self(value.0.replace('\\', "/"))
}
}
@ -174,7 +176,7 @@ struct RawPackagePath(pub String);
impl From<InnerPathLinux> for RawPackagePath {
fn from(value: InnerPathLinux) -> Self {
RawPackagePath(value.0)
Self(value.0)
}
}
@ -210,6 +212,7 @@ impl PackagePath {
}
/// Get the [`InnerPathLinux`] of a [`PackagePath`].
#[must_use]
pub fn inner_path(&self) -> InnerPathLinux {
InnerPathLinux(
self.0
@ -220,9 +223,10 @@ impl PackagePath {
)
}
/// Create a new PackagePath from a relative path.
/// Create a new `PackagePath` from a relative path.
#[must_use]
pub fn new(path: &Path) -> Self {
PackagePath(PathBuf::from(path))
Self(PathBuf::from(path))
}
}
@ -319,7 +323,8 @@ pub struct PackageData {
pub locked: Option<bool>,
}
pub fn default_locked() -> Option<bool> {
#[must_use]
pub const fn default_locked() -> Option<bool> {
Some(true)
}
@ -331,11 +336,11 @@ pub enum Loader {
/// The default vanilla loader, no modding supported.
#[default]
Vanilla,
/// The MinecraftForge Minecraft mod loader.
/// The `MinecraftForge` Minecraft mod loader.
Forge,
/// The FabircMC Minecraft mod loader.
/// The `FabircMC` Minecraft mod loader.
Fabric,
/// The NeoForge Minecraft mod loader.
/// The `NeoForge` Minecraft mod loader.
NeoForge,
/// The Quilt Minecraft mod loader.
Quilt,
@ -344,7 +349,8 @@ pub enum Loader {
}
impl Loader {
pub fn as_str(&self) -> &'static str {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match *self {
Self::Vanilla => "vanilla",
Self::Forge => "forge",
@ -355,7 +361,8 @@ impl Loader {
}
}
pub fn as_meta_str(&self) -> &'static str {
#[must_use]
pub const fn as_meta_str(&self) -> &'static str {
match *self {
Self::Vanilla => "vanilla",
Self::Forge => "forge",
@ -366,6 +373,7 @@ impl Loader {
}
}
#[must_use]
pub fn from_string(val: &str) -> Self {
match val {
"vanilla" => Self::Vanilla,
@ -377,7 +385,8 @@ impl Loader {
}
}
pub fn supports_mods(&self) -> bool {
#[must_use]
pub const fn supports_mods(&self) -> bool {
!matches!(self, Self::Vanilla)
}
}
@ -463,6 +472,7 @@ impl Cluster {
/// Get the [`ClusterPath`] of the specified [`Cluster`].
#[inline]
#[must_use]
pub fn cluster_path(&self) -> ClusterPath {
ClusterPath::new(&self.path)
}
@ -645,7 +655,7 @@ impl Clusters {
{
let clusters = state.clusters.read().await;
for (cluster_path, cluster) in clusters.0.iter() {
for (cluster_path, cluster) in &clusters.0 {
if let Some(package_data) = &cluster.meta.package_data {
if let Some(linked_package) = &package_data.package_id {
updateable.push((cluster_path.clone(), linked_package.clone()));
@ -679,7 +689,7 @@ impl Clusters {
cluster.uuid,
&cluster.cluster_path(),
&cluster.meta.name,
crate::proxy::ClusterPayloadType::Inserted,
ClusterPayloadType::Inserted,
)
.await?;

View file

@ -2,7 +2,7 @@
use super::Directories;
use crate::utils::http::IoSemaphore;
use iota_stronghold::{KeyProvider, SnapshotPath};
use iota_stronghold::{KeyProvider, SnapshotPath, Stronghold};
use serde::{Serialize, Serializer};
use std::ops::Deref;
use std::path::Path;
@ -11,9 +11,9 @@ use zeroize::Zeroizing;
/// A K/V encrypted store to handle digital secrets using IOTA Stronghold.
#[derive(Debug)]
pub struct Credentials {
inner: iota_stronghold::Stronghold,
path: iota_stronghold::SnapshotPath,
keyprovider: iota_stronghold::KeyProvider,
inner: Stronghold,
path: SnapshotPath,
keyprovider: KeyProvider,
}
impl Credentials {
@ -25,7 +25,7 @@ impl Credentials {
_io_semaphore: &IoSemaphore,
) -> crate::Result<Self> {
let path = SnapshotPath::from_path(path);
let stronghold = iota_stronghold::Stronghold::default();
let stronghold = Stronghold::default();
let keyprovider = KeyProvider::try_from(password).map_err(StrongholdError::MemoryError)?;
if path.exists() {
stronghold
@ -46,13 +46,13 @@ impl Credentials {
Ok(())
}
pub fn inner(&self) -> &iota_stronghold::Stronghold {
pub const fn inner(&self) -> &Stronghold {
&self.inner
}
}
impl Deref for Credentials {
type Target = iota_stronghold::Stronghold;
type Target = Stronghold;
fn deref(&self) -> &Self::Target {
&self.inner
}
@ -71,7 +71,7 @@ pub enum StrongholdError {
}
impl Serialize for StrongholdError {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{

View file

@ -19,6 +19,7 @@ pub struct Directories {
impl Directories {
/// Bootstrap the initial settings directory on first startup.
#[must_use]
pub fn init_settings_dir() -> Option<PathBuf> {
Self::env_path("ONELAUNCHER_CONFIG")
.or_else(|| Some(dirs::data_dir()?.join(constants::NAME)))
@ -76,6 +77,7 @@ impl Directories {
/// Bootstrap the core logs directory and get it as a [`PathBuf`].
#[inline]
#[must_use]
pub fn logs_dir() -> Option<PathBuf> {
Self::init_settings_dir().map(|d| d.join("logs"))
}

View file

@ -36,7 +36,7 @@ impl DiscordRPC {
};
let client = Arc::new(RwLock::new(discord_ipc));
Ok(DiscordRPC { client, connected })
Ok(Self { client, connected })
}
/// Set the Discord IPC activity with a message and try to reconnect if the initial connection fails.

View file

@ -7,7 +7,7 @@ use std::path::PathBuf;
use crate::utils::java;
use crate::utils::java::JavaVersion;
/// A HashMap of all located and installed available Java versions
/// A `HashMap` of all located and installed available Java versions
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JavaVersions(HashMap<String, JavaVersion>);
@ -19,9 +19,10 @@ impl Default for JavaVersions {
}
impl JavaVersions {
/// Create an empty JavaVersions `HashMap`.
pub fn new() -> JavaVersions {
JavaVersions(HashMap::new())
/// Create an empty `JavaVersions` `HashMap`.
#[must_use]
pub fn new() -> Self {
Self(HashMap::new())
}
/// Inserts a key-value pair into the map.
@ -35,6 +36,7 @@ impl JavaVersions {
}
/// Returns a reference to the value corresponding to the key.
#[must_use]
pub fn get(&self, key: &String) -> Option<&JavaVersion> {
self.0.get(key)
}
@ -45,18 +47,20 @@ impl JavaVersions {
}
/// Returns the number of elements in the map.
#[must_use]
pub fn count(&self) -> usize {
self.0.len()
}
/// A collection visiting all keys in arbitrary order.
#[must_use]
pub fn keys(&self) -> Vec<String> {
self.0.keys().cloned().collect()
}
/// Validates all stored java versions.
pub async fn validate(&self) -> bool {
for (_, java) in self.0.iter() {
for java in self.0.values() {
let runtime = java::check_java_instance(PathBuf::from(&java.path).as_path()).await;
if let Some(runtime) = runtime {
if runtime.version != java.version {

View file

@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize};
use crate::constants::METADATA_API_URL;
use crate::utils::http::{fetch, read_json, write, FetchSemaphore, IoSemaphore};
use crate::utils::io::copy;
use crate::State;
use super::Directories;
@ -20,7 +19,7 @@ pub struct Metadata {
pub fabric: Option<ModdedManifest>,
/// The [`ModdedManifest`] associated with core Quilt versions.
pub quilt: Option<ModdedManifest>,
/// The [`ModdedManifest`] associated with core NeoForge versions.
/// The [`ModdedManifest`] associated with core `NeoForge` versions.
pub neoforge: Option<ModdedManifest>,
/// The [`ModdedManifest`] associated with core Forge versions.
pub forge: Option<ModdedManifest>,
@ -154,7 +153,7 @@ impl Metadata {
let backup = dirs.caches_dir().await.join("metadata.json.bak");
let mut should_write = false;
if let Ok(mut metadata_json) = read_json::<Metadata>(&path, io_semaphore).await {
if let Ok(mut metadata_json) = read_json::<Self>(&path, io_semaphore).await {
should_write = metadata_json.fetch_errored(fetch_semaphore).await?;
metadata = Some(metadata_json);
@ -171,12 +170,12 @@ impl Metadata {
match res {
Ok(()) => {}
Err(err) => {
tracing::warn!("failed to fetch metadata: {err}")
tracing::warn!("failed to fetch metadata: {err}");
}
}
} else if let Ok(metadata_json) = read_json::<Metadata>(&backup, io_semaphore).await {
} else if let Ok(metadata_json) = read_json::<Self>(&backup, io_semaphore).await {
metadata = Some(metadata_json);
copy(&backup, &path).await?;
onelauncher_utils::io::copy(&backup, &path).await?;
}
if should_write {
@ -205,7 +204,7 @@ impl Metadata {
pub async fn update() {
let res = async {
let state = State::get().await?;
let fetch_data = Metadata::fetch(&state.fetch_semaphore).await?;
let fetch_data = Self::fetch(&state.fetch_semaphore).await?;
let path = state.directories.caches_dir().await.join("metadata.json");
let backup = state
@ -215,7 +214,7 @@ impl Metadata {
.join("metadata.json.bak");
if path.exists() {
copy(&path, &backup).await?;
onelauncher_utils::io::copy(&path, &backup).await?;
}
write(
@ -235,7 +234,7 @@ impl Metadata {
match res {
Ok(()) => {}
Err(err) => {
tracing::warn!("failed to update launcher metadata: {err}")
tracing::warn!("failed to update launcher metadata: {err}");
}
};
}
@ -247,9 +246,8 @@ async fn fetch_version_manifest(
) -> crate::Result<MinecraftManifest> {
let url = url.unwrap_or(interpulse::api::minecraft::VERSION_MANIFEST_URL);
Ok(serde_json::from_slice(
&fetch(url, None, semaphore).await.map_err(|err| {
&fetch(url, None, semaphore).await.inspect_err(|_| {
tracing::error!("couldn't fetch version manifest at '{}'", url);
err
})?,
)?)
}
@ -259,9 +257,8 @@ async fn fetch_modded_manifest(
semaphore: &FetchSemaphore,
) -> crate::Result<ModdedManifest> {
Ok(serde_json::from_slice(
&fetch(url, None, semaphore).await.map_err(|err| {
&fetch(url, None, semaphore).await.inspect_err(|_| {
tracing::error!("couldn't fetch modded manifest at '{}'", url);
err
})?,
)?)
}

View file

@ -880,7 +880,7 @@ async fn send_signed_request<T: serde::de::DeserializeOwned>(
let auth = authorization.map_or(Vec::new(), |v| v.as_bytes().to_vec());
let body = serde_json::to_vec(&raw_body)
.map_err(|source| MinecraftAuthError::SerializeError { step, source })?;
let time: u128 = { ((current_date.timestamp() as u128) + 11644473600) * 10000000 };
let time: u128 = { ((current_date.timestamp() as u128) + 11_644_473_600) * 10_000_000 };
use byteorder::WriteBytesExt;
let mut buffer = Vec::new();
@ -896,7 +896,7 @@ async fn send_signed_request<T: serde::de::DeserializeOwned>(
buffer
.write_u8(0)
.map_err(|source| MinecraftAuthError::SigningError { step, source })?;
buffer.extend_from_slice("POST".as_bytes());
buffer.extend_from_slice(b"POST");
buffer
.write_u8(0)
.map_err(|source| MinecraftAuthError::SigningError { step, source })?;
@ -975,8 +975,7 @@ fn get_date_header(headers: &reqwest::header::HeaderMap) -> DateTime<Utc> {
.get(reqwest::header::DATE)
.and_then(|x| x.to_str().ok())
.and_then(|x| DateTime::parse_from_rfc2822(x).ok())
.map(|x| x.with_timezone(&Utc))
.unwrap_or(Utc::now())
.map_or(Utc::now(), |x| x.with_timezone(&Utc))
}
#[tracing::instrument]

View file

@ -1,4 +1,4 @@
//! **OneLauncher Store**
//! **`OneLauncher` Store**
//!
//! Core state and storage management for the launcher, managing all states.
@ -49,7 +49,7 @@ pub use self::oneconfig::*;
/// The static [`OnceCell<RwLock<State>>`] for storing the global runtime launcher state.
static LAUNCHER_STATE: OnceCell<RwLock<State>> = OnceCell::const_new();
/// The public OneLauncher state structure.
/// The public `OneLauncher` state structure.
pub struct State {
/// Handles a boolean to check if the launcher is connected to the internet
pub offline: RwLock<bool>,
@ -73,7 +73,7 @@ pub struct State {
pub users: RwLock<MinecraftState>,
/// Handles processes
pub processor: RwLock<Processor>,
/// Handles OneConfig integration with [`async-tungstenite`]
/// Handles `OneConfig` integration with [`async-tungstenite`]
pub oneconfig: RwLock<Option<OneConfig>>,
/// Handles secure credential management with [`iota_stronghold`].
pub credentials: RwLock<Option<Credentials>>,
@ -113,7 +113,7 @@ impl State {
/// Initializes the core OneLauncher state fully.
#[tracing::instrument]
#[onelauncher_macros::memory]
async fn initialize() -> crate::Result<RwLock<State>> {
async fn initialize() -> crate::Result<RwLock<Self>> {
let ingress =
init_ingress_internal(crate::IngressType::Initialize, 100.0, "initializing state")
.await?;
@ -195,7 +195,7 @@ impl State {
/// Updates all data if we are connected to the internet.
pub fn update() {
tokio::task::spawn(async {
if let Ok(state) = crate::State::get().await {
if let Ok(state) = Self::get().await {
if !*state.offline.read().await {
let version_up = Clusters::update_versions();
let meta_up = Metadata::update();

View file

@ -1,10 +1,10 @@
//! OneConfig state using [`async_tungstenite`] to communicate to the OneConfig server
//! `OneConfig` state using [`async_tungstenite`] to communicate to the `OneConfig` server
use super::ClusterPath;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// Wrapper over an [`async_tungstenite::WebSocketStream`] to connect to the OneConfig server.
/// Wrapper over an [`async_tungstenite::WebSocketStream`] to connect to the `OneConfig` server.
pub struct OneConfig {
/// The public tokio socket connection.
pub socket: async_tungstenite::WebSocketStream<async_tungstenite::tokio::ConnectStream>,
@ -22,7 +22,7 @@ pub struct ModUpdatePacket {
}
impl OneConfig {
/// Initializes a OneConfig socket connection on localhost port `4023`.
/// Initializes a `OneConfig` socket connection on localhost port `4023`.
pub async fn initialize(cluster: ClusterPath) -> crate::Result<Self> {
let (socket, _) = async_tungstenite::tokio::connect_async(format!(
"wss://localhost:4023/oneconfig/ws?cluster={cluster}"

View file

@ -3,9 +3,9 @@
use crate::package::content::Providers;
use crate::store::Loader;
use crate::utils::http::{write_icon, IoSemaphore};
use crate::utils::io;
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};
@ -34,58 +34,63 @@ pub enum PackageType {
impl PackageType {
/// attempt to get a [`PackageType`] from a loader string.
#[must_use]
pub fn from_loader(loaders: Vec<String>) -> Option<Self> {
if loaders
.iter()
.any(|x| ["fabric", "forge", "quilt"].contains(&&**x))
{
Some(PackageType::Mod)
Some(Self::Mod)
} else if loaders.iter().any(|x| x == "datapack") {
Some(PackageType::DataPack)
Some(Self::DataPack)
} else if loaders.iter().any(|x| ["iris", "optifine"].contains(&&**x)) {
Some(PackageType::ShaderPack)
Some(Self::ShaderPack)
} else if loaders
.iter()
.any(|x| ["vanilla", "canvas", "minecraft"].contains(&&**x))
{
Some(PackageType::ResourcePack)
Some(Self::ResourcePack)
} else {
None
}
}
#[must_use]
pub fn from_parent(path: PathBuf) -> Option<Self> {
let path = path.parent()?.file_name()?;
match path.to_str()? {
"mods" => Some(PackageType::Mod),
"datapacks" => Some(PackageType::DataPack),
"resourcepacks" => Some(PackageType::ResourcePack),
"shaderpacks" => Some(PackageType::ShaderPack),
"modpack" => Some(PackageType::ModPack),
"mods" => Some(Self::Mod),
"datapacks" => Some(Self::DataPack),
"resourcepacks" => Some(Self::ResourcePack),
"shaderpacks" => Some(Self::ShaderPack),
"modpack" => Some(Self::ModPack),
_ => None,
}
}
pub fn get_name(&self) -> &'static str {
#[must_use]
pub const fn get_name(&self) -> &'static str {
match self {
PackageType::Mod => "mod",
PackageType::DataPack => "datapack",
PackageType::ResourcePack => "resourcepack",
PackageType::ShaderPack => "shaderpack",
PackageType::ModPack => "modpack",
Self::Mod => "mod",
Self::DataPack => "datapack",
Self::ResourcePack => "resourcepack",
Self::ShaderPack => "shaderpack",
Self::ModPack => "modpack",
}
}
pub fn get_folder(&self) -> &'static str {
#[must_use]
pub const fn get_folder(&self) -> &'static str {
match self {
PackageType::Mod => "mods",
PackageType::DataPack => "datapacks",
PackageType::ResourcePack => "resourcepacks",
PackageType::ShaderPack => "shaderpacks",
PackageType::ModPack => "modpack",
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!(
"{}/{}",
@ -94,6 +99,7 @@ impl PackageType {
))
}
#[must_use]
pub fn get_meta_file_name(&self) -> String {
String::from(".packages.json")
}
@ -104,7 +110,7 @@ impl PackageType {
}
Ok(match self {
PackageType::Mod => {
Self::Mod => {
entry.file_type().await?.is_file()
&& entry.path().extension() == Some("jar".as_ref())
}
@ -112,19 +118,19 @@ impl PackageType {
})
}
pub fn iterator() -> impl Iterator<Item = PackageType> {
pub fn iterator() -> impl Iterator<Item = Self> {
[
PackageType::Mod,
PackageType::DataPack,
PackageType::ResourcePack,
PackageType::ShaderPack,
Self::Mod,
Self::DataPack,
Self::ResourcePack,
Self::ShaderPack,
]
.iter()
.copied()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub struct SearchResult {
pub slug: String,
@ -189,7 +195,7 @@ impl Package {
pub type PackagesMap = HashMap<PackagePath, Package>;
pub(crate) struct Packages {
pub struct Packages {
managers: HashMap<ClusterPath, PackageManager>,
}
@ -218,6 +224,7 @@ impl Packages {
Self { managers }
}
#[must_use]
pub fn get(&self, cluster_path: &ClusterPath) -> Option<&PackageManager> {
self.managers.get(cluster_path)
}
@ -235,6 +242,7 @@ pub struct PackagesMeta {
}
impl PackagesMeta {
#[must_use]
pub fn new(package_type: PackageType) -> Self {
Self {
packages: PackagesMap::new(),
@ -260,6 +268,7 @@ impl PackageManager {
Ok(manager)
}
#[must_use]
pub fn new(cluster_path: ClusterPath) -> Self {
Self {
cluster_path,
@ -282,7 +291,8 @@ impl PackageManager {
.join(package_type.get_meta()))
}
/// Get the PackagesMeta for a specific package type. Does not sync.
/// Get the `PackagesMeta` for a specific package type. Does not sync.
#[must_use]
pub fn get(&self, package_type: PackageType) -> &PackagesMeta {
match package_type {
PackageType::Mod => &self.mods,
@ -293,7 +303,7 @@ impl PackageManager {
}
}
/// Get the PackagesMeta for a specific package type. Does not sync.
/// 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,
@ -482,7 +492,7 @@ impl PackageManager {
}
if packages.len() != packages_to_keep.len() {
packages.retain(|pkg_path, _| packages_to_keep.contains(pkg_path))
packages.retain(|pkg_path, _| packages_to_keep.contains(pkg_path));
}
Ok(())
@ -538,6 +548,7 @@ pub enum PackageMetadata {
}
impl PackageMetadata {
#[must_use]
pub fn from_managed_package(package: ManagedPackage, version: ManagedVersion) -> Self {
Self::Managed {
package_id: package.id,
@ -549,11 +560,12 @@ impl PackageMetadata {
}
}
pub fn get_package_type(&self) -> Option<PackageType> {
#[must_use]
pub const fn get_package_type(&self) -> Option<PackageType> {
match self {
PackageMetadata::Managed { package_type, .. } => Some(*package_type),
PackageMetadata::Mapped { package_type, .. } => *package_type,
PackageMetadata::Unknown => None,
Self::Managed { package_type, .. } => Some(*package_type),
Self::Mapped { package_type, .. } => *package_type,
Self::Unknown => None,
}
}
}
@ -576,7 +588,7 @@ pub struct ManagedUser {
pub role: Option<String>,
}
fn default_is_organization_user() -> bool {
const fn default_is_organization_user() -> bool {
false
}
@ -623,7 +635,7 @@ pub struct ManagedPackage {
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct License {
#[serde(default)]
pub id: String,
@ -658,6 +670,7 @@ pub struct ManagedVersion {
}
impl ManagedVersion {
#[must_use]
pub fn get_primary_file(&self) -> Option<&ManagedVersionFile> {
self.files.iter().find(|f| f.primary)
}
@ -665,7 +678,7 @@ impl ManagedVersion {
/// Universal interface for managed package files.
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct ManagedVersionFile {
pub url: String,
pub file_name: String,
@ -709,7 +722,7 @@ pub enum PackageSide {
/// The file type of a [`Package`].
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PackageFile {
RequiredPack,

View file

@ -16,12 +16,12 @@ use crate::constants::PROCESSOR_FILE;
use crate::proxy::send::send_process;
use crate::proxy::ProcessPayloadType;
use crate::utils::http::read_json;
use crate::utils::io::IOError;
use crate::State;
use onelauncher_utils::io::IOError;
use super::{Cluster, ClusterPath};
/// Wrapper over a HashMap of PIDs to ProcessorChildren and unified apis
/// Wrapper over a `HashMap` of PIDs to `ProcessorChildren` and unified apis
pub struct Processor(HashMap<Uuid, Arc<RwLock<ProcessorChild>>>);
/// Manager for safe processes like ingress feeds which need to be safely shut down.
@ -48,13 +48,14 @@ impl Default for IngressProcessor {
impl IngressProcessor {
/// Initialize a new ingresss process manager.
pub fn new() -> Self {
#[must_use]
pub const fn new() -> Self {
Self {
ingress_feeds: Vec::new(),
}
}
/// Add an ingress safety process to the HashMap.
/// Add an ingress safety process to the `HashMap`.
pub async fn add_ingress(r#type: IngressProcessType, uuid: Uuid) -> crate::Result<Uuid> {
let state = State::get().await?;
let mut ingress_processor = state.ingress_processor.write().await;
@ -67,7 +68,7 @@ impl IngressProcessor {
Ok(uuid)
}
/// Complete and retain an ingress safety process from the HashMap of ingress feeds.
/// Complete and retain an ingress safety process from the `HashMap` of ingress feeds.
pub async fn finish(r#type: IngressProcessType, uuid: Uuid) -> crate::Result<()> {
let state = State::get().await?;
let mut ingress_processor = state.ingress_processor.write().await;
@ -127,15 +128,16 @@ pub struct ProcessorCache {
}
impl ChildType {
/// Get the PID of a specific ChildType process.
/// Get the PID of a specific `ChildType` process.
#[must_use]
pub fn id(&self) -> Option<u32> {
match self {
ChildType::ChildProcess(child) => child.id(),
ChildType::RescuedChild(pid) => Some(*pid),
Self::ChildProcess(child) => child.id(),
Self::RescuedChild(pid) => Some(*pid),
}
}
/// Store a cache of a specific ChildType.
/// Store a cache of a specific `ChildType`.
pub async fn cache(
&self,
uuid: Uuid,
@ -144,11 +146,11 @@ impl ChildType {
user: Option<Uuid>,
) -> crate::Result<()> {
let pid = match self {
ChildType::ChildProcess(child) => child.id().unwrap_or(0),
ChildType::RescuedChild(pid) => *pid,
Self::ChildProcess(child) => child.id().unwrap_or(0),
Self::RescuedChild(pid) => *pid,
};
let state = crate::State::get().await?;
let state = State::get().await?;
let mut system = sysinfo::System::new();
system.refresh_processes(sysinfo::ProcessesToUpdate::All);
@ -174,13 +176,9 @@ impl ChildType {
};
let path = state.directories.caches_dir().await.join(PROCESSOR_FILE);
let mut caches = if let Ok(proc_json) =
read_json::<HashMap<Uuid, ProcessorCache>>(&path, &state.io_semaphore).await
{
proc_json
} else {
HashMap::new()
};
let mut caches = (read_json::<HashMap<Uuid, ProcessorCache>>(&path, &state.io_semaphore)
.await)
.unwrap_or_default();
caches.insert(uuid, cached_proccess);
crate::utils::http::write(&path, &serde_json::to_vec(&caches)?, &state.io_semaphore)
@ -191,15 +189,11 @@ impl ChildType {
/// Remove a child from the global store of processes.
pub async fn remove(&self, uuid: Uuid) -> crate::Result<()> {
let state = crate::State::get().await?;
let state = State::get().await?;
let path = state.directories.caches_dir().await.join(PROCESSOR_FILE);
let mut caches = if let Ok(proc_json) =
read_json::<HashMap<Uuid, ProcessorCache>>(&path, &state.io_semaphore).await
{
proc_json
} else {
HashMap::new()
};
let mut caches = (read_json::<HashMap<Uuid, ProcessorCache>>(&path, &state.io_semaphore)
.await)
.unwrap_or_default();
caches.remove(&uuid);
crate::utils::http::write(&path, &serde_json::to_vec(&caches)?, &state.io_semaphore)
.await?;
@ -210,8 +204,8 @@ impl ChildType {
/// Kill a process in the global store of processes.
pub async fn kill(&mut self) -> crate::Result<()> {
match self {
ChildType::ChildProcess(child) => Ok(child.kill().await.map_err(IOError::from)?),
ChildType::RescuedChild(pid) => {
Self::ChildProcess(child) => Ok(child.kill().await.map_err(IOError::from)?),
Self::RescuedChild(pid) => {
let mut system = sysinfo::System::new();
if system.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[
sysinfo::Pid::from_u32(*pid),
@ -230,11 +224,11 @@ impl ChildType {
/// Wait for a process to complete.
pub async fn try_wait(&mut self) -> crate::Result<Option<i32>> {
match self {
ChildType::ChildProcess(child) => Ok(child
Self::ChildProcess(child) => Ok(child
.try_wait()
.map_err(IOError::from)?
.map(|x| x.code().unwrap_or(0))),
ChildType::RescuedChild(pid) => {
Self::RescuedChild(pid) => {
let mut system = sysinfo::System::new();
if system.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[
sysinfo::Pid::from_u32(*pid),
@ -279,18 +273,21 @@ pub struct ProcessorChild {
impl Processor {
/// Get a new [`Processor`] instance.
#[must_use]
pub fn new() -> Self {
Processor(HashMap::new())
Self(HashMap::new())
}
/// Returns a reference to the value corresponding to the key.
#[must_use]
pub fn get(&self, uuid: Uuid) -> Option<Arc<RwLock<ProcessorChild>>> {
self.0.get(&uuid).cloned()
}
/// An collection visiting all keys in arbitrary order.
#[must_use]
pub fn keys(&self) -> Vec<Uuid> {
self.0.keys().cloned().collect()
self.0.keys().copied().collect()
}
/// Get all running processes.
@ -390,13 +387,12 @@ impl Processor {
/// restore processes from the cache.
pub async fn restore(&mut self) -> crate::Result<()> {
let state = crate::State::get().await?;
let state = State::get().await?;
let processor_path = state.directories.caches_dir().await.join(PROCESSOR_FILE);
let mut processor_caches = if let Ok(processes_json) =
read_json::<HashMap<uuid::Uuid, ProcessorCache>>(&processor_path, &state.io_semaphore)
.await
read_json::<HashMap<Uuid, ProcessorCache>>(&processor_path, &state.io_semaphore).await
{
let zeros = HashMap::<uuid::Uuid, ProcessorCache>::new();
let zeros = HashMap::<Uuid, ProcessorCache>::new();
crate::utils::http::write(
&processor_path,
&serde_json::to_vec(&zeros)?,
@ -469,7 +465,7 @@ impl Processor {
} {
if let Some(line) = line {
let mut censored = line.clone();
for (key, value) in censors.iter() {
for (key, value) in &censors {
censored = censored.replace(key, value);
}
@ -521,7 +517,7 @@ impl Processor {
&mut self,
cache: ProcessorCache,
) -> crate::Result<Arc<RwLock<ProcessorChild>>> {
let _state = crate::State::get().await?;
let _state = State::get().await?;
// ensure located stray process matches with our pid recorded process
{
@ -680,7 +676,7 @@ impl Processor {
});
tokio::spawn(async {
let state = match crate::State::get().await {
let state = match State::get().await {
Ok(state) => state,
Err(err) => {
tracing::warn!("failed to get state: {}", err);
@ -717,8 +713,7 @@ impl Processor {
let mut cmd = hook.split(' ');
if let Some(c) = cmd.next() {
let mut c = Command::new(c);
c.args(cmd.collect::<Vec<&str>>())
.current_dir(cluster_path.full_path().await?);
c.args(cmd).current_dir(cluster_path.full_path().await?);
Some(c)
} else {
None

View file

@ -2,7 +2,7 @@
use super::{Directories, JavaVersions};
use crate::constants::CURRENT_SETTINGS_FORMAT_VERSION;
use crate::utils::io;
use onelauncher_utils::io;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
@ -10,15 +10,15 @@ use std::path::{Path, PathBuf};
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Settings {
/// A OneLauncher [`Theme`] managed by the core GUI.
/// A `OneLauncher` [`Theme`] managed by the core GUI.
pub theme: Theme,
/// A global browser list view for the OneLauncher GUI.
/// A global browser list view for the `OneLauncher` GUI.
#[serde(default)]
pub browser_list_view: BrowserListView,
/// Does not ask for confirmation when closing the OneLauncher GUI
/// Does not ask for confirmation when closing the `OneLauncher` GUI
#[serde(default)]
pub hide_close_prompt: bool,
/// Disables animations in the OneLauncher GUI
/// Disables animations in the `OneLauncher` GUI
#[serde(default)]
pub disable_animations: bool,
/// A global fullscreen Minecraft state.
@ -78,7 +78,7 @@ impl Settings {
let read_settings = io::read(&file)
.await
.map_err(|err| anyhow::anyhow!("error reading settings file: {0}", err).into())
.and_then(|it| serde_json::from_slice::<Settings>(&it).map_err(crate::Error::from));
.and_then(|it| serde_json::from_slice::<Self>(&it).map_err(crate::Error::from));
if let Err(ref err) = read_settings {
tracing::error!("failed to load settings file: {err}.");
@ -143,7 +143,7 @@ impl Settings {
}
}
/// OneLauncher browser list type.
/// `OneLauncher` browser list type.
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@ -154,7 +154,7 @@ pub enum BrowserListView {
Preview,
}
/// A OneLauncher theme managed by the GUI.
/// A `OneLauncher` theme managed by the GUI.
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@ -164,7 +164,7 @@ pub enum Theme {
/// A default Light theme.
Light,
/// OLED Dark Theme.
Contrast,
Oled,
/// Cute and colorful theme.
Cat,
}

View file

@ -4,7 +4,6 @@
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::time::Duration;
use bytes::Bytes;
use reqwest::Method;
@ -15,9 +14,9 @@ use tokio::sync::{RwLock, Semaphore};
use crate::constants::FETCH_ATTEMPTS;
use crate::proxy::send::send_ingress;
use crate::proxy::IngressId;
use crate::utils::io;
use super::io::IOError;
use onelauncher_utils::io;
use onelauncher_utils::io::IOError;
/// A [`Semaphore`] used for all I/O operations.
#[derive(Debug)]
@ -34,7 +33,7 @@ lazy_static::lazy_static! {
"{}/{} (https://polyfrost.org)",
crate::constants::NAME,
crate::constants::VERSION,
)).unwrap();
)).expect("failed to build reqwest headers!");
headers.insert(reqwest::header::USER_AGENT, header);
reqwest::Client::builder()
.tcp_keepalive(Some(std::time::Duration::from_secs(15)))
@ -91,7 +90,7 @@ pub async fn fetch_advanced(
let mut req = REQWEST_CLIENT.request(method.clone(), url);
if let Some(body) = json_body.clone() {
req = req.json(&body)
req = req.json(&body);
}
if let Some(header) = header {
@ -120,7 +119,7 @@ pub async fn fetch_advanced(
.await?;
}
Ok(bytes::Bytes::from(bytes))
Ok(Bytes::from(bytes))
} else {
x.bytes().await
}
@ -134,14 +133,13 @@ pub async fn fetch_advanced(
if &*hash != sha1 {
if attempt <= 3 {
continue;
} else {
return Err(anyhow::anyhow!(
"hash {0} does not match {1}",
sha1.to_string(),
hash
)
.into());
}
return Err(anyhow::anyhow!(
"hash {0} does not match {1}",
sha1.to_string(),
hash
)
.into());
}
}
@ -191,7 +189,7 @@ pub async fn fetch_from_mirrors(
pub async fn check_internet_connection(timeout: u64) -> bool {
REQWEST_CLIENT
.get("https://api.polyfrost.org/")
.timeout(Duration::from_secs(timeout))
.timeout(std::time::Duration::from_secs(timeout))
.send()
.await
.is_ok()
@ -255,8 +253,8 @@ pub async fn write<'a>(path: &Path, bytes: &[u8], semaphore: &IoSemaphore) -> cr
/// Copy a file from one [`AsRef<Path>`] to another.
pub async fn copy(
src: impl AsRef<std::path::Path>,
dest: impl AsRef<std::path::Path>,
src: impl AsRef<Path>,
dest: impl AsRef<Path>,
semaphore: &IoSemaphore,
) -> crate::Result<()> {
let src = src.as_ref();

View file

@ -18,14 +18,14 @@ use winreg::{
RegKey,
};
use super::io;
use onelauncher_utils::io;
/// A struct representing a single version of the Java Runtime Environment.
/// Use [`locate_java`] to get an array of all located java instances on the machine.
///
/// common java paths from https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/java/JavaUtils.cpp under GPL 3.0
/// common java paths from <https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/java/JavaUtils.cpp> under GPL 3.0
///
/// java check functionality from https://github.com/modrinth/theseus/blob/master/theseus/src/util/jre.rs under MIT
/// java check functionality from <https://github.com/modrinth/theseus/blob/master/theseus/src/util/jre.rs> under MIT
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Clone)]
pub struct JavaVersion {
@ -192,9 +192,13 @@ pub async fn locate_java() -> Result<Vec<JavaVersion>, JavaError> {
#[onelauncher_macros::memory]
async fn locate_installed_java() -> Result<HashSet<PathBuf>, JavaError> {
Box::pin(async move {
let state = State::get().await.map_err(|_| JavaError::MutexError)?;
let mut paths = HashSet::new();
let java_base_path = state.directories.java_dir().await;
let java_base_path = State::get()
.await
.map_err(|_| JavaError::MutexError)?
.directories
.java_dir()
.await;
if java_base_path.is_dir() {
if let Ok(dir) = std::fs::read_dir(java_base_path) {
@ -239,7 +243,7 @@ pub async fn check_java(paths: HashSet<PathBuf>) -> HashSet<JavaVersion> {
java_instances
.into_iter()
.flat_map(|p| p.ok())
.filter_map(Result::ok)
.flatten()
.collect()
}
@ -253,10 +257,10 @@ pub async fn check_java_instance(path: &Path) -> Option<JavaVersion> {
let Ok(path) = io::canonicalize(path) else {
return None;
};
let java = if path.file_name()?.to_str()? != JAVA_BIN {
path.join(JAVA_BIN)
} else {
let java = if path.file_name()?.to_str()? == JAVA_BIN {
path
} else {
path.join(JAVA_BIN)
};
if !java.exists() {
return None;
@ -277,7 +281,7 @@ pub async fn check_java_instance(path: &Path) -> Option<JavaVersion> {
let output = Command::new(&java)
.arg("-cp")
.arg(file_path.parent().unwrap())
.arg(file_path.parent().expect("failed to get java parent"))
.arg("JavaInfo")
.env_remove("_JAVA_OPTIONS")
.output()

View file

@ -1,32 +1,10 @@
//! **OneLauncher Utilities**
//! Standard asynchronous utilities and wrappers for use in the launcher.
//! **`OneLauncher` Core Utilities**
//! Standard asynchronous utilities and wrappers for use within the launcher core.
//!
//! - [`io`]: Async wrapper around [`tokio::fs`] and [`std::io::Error`] for our error system.
//! - [`http`]: Async extensions and wrappers around [`reqwest`] functions.
//! - [`java`]: Async utilities for managing and downloading Java versions.
//! - [`platform`]: Async utilities for managing OS-specific [`interpulse`] operations and rules.
//! - [`watcher`]: Async utilities for watching files with [`notify`].
//! - [`logging`]: Async utilities for log4j parsing with [`nom`].
pub mod http;
pub mod io;
pub mod java;
pub mod logging;
pub mod platform;
pub mod watcher;
/// Simple macro that takes a mutable reference and inserts it into a codeblock closure
/// as an owned reference.
///
/// mutable reference gets epically owned by free thinking macro!!!!! (not clickbait)
/// im going insane insane insane insane insane insane insane insane insane
macro_rules! ref_owned {
($id:ident = $init:expr => $transform:block) => {{
let mut it = $init;
{
let $id = &mut it;
$transform;
}
it
}};
}

View file

@ -19,8 +19,11 @@ pub async fn initialize_watcher() -> crate::Result<Debouncer<RecommendedWatcher>
Duration::from_secs_f32(2.0),
move |result: DebounceEventResult| {
futures::executor::block_on(async {
sender.send(result).await.unwrap();
})
sender
.send(result)
.await
.expect("failed to send debouse event");
});
},
)?;
@ -34,7 +37,7 @@ pub async fn initialize_watcher() -> crate::Result<Debouncer<RecommendedWatcher>
Ok(mut events) => {
let mut paths = Vec::new();
events.sort_by(|a, b| a.path.cmp(&b.path));
events.iter().for_each(|a| {
for a in &events {
let mut formatted = PathBuf::new();
let mut components = a.path.components();
let mut matched = false;
@ -57,7 +60,7 @@ pub async fn initialize_watcher() -> crate::Result<Debouncer<RecommendedWatcher>
if a.path
.components()
.any(|c| c.as_os_str() == crate::constants::CRASH_PATH)
&& a.path.extension().map(|e| e == "txt").unwrap_or(false)
&& a.path.extension().is_some_and(|e| e == "txt")
{
Cluster::handle_crash(cluster_path);
} else if !paths.contains(&formatted) {
@ -72,7 +75,7 @@ pub async fn initialize_watcher() -> crate::Result<Debouncer<RecommendedWatcher>
Clusters::sync_cluster(cluster_path);
}
}
});
}
}
Err(err) => tracing::warn!("fs watching error: {err}"),
}

View file

@ -1,4 +1,37 @@
#![cfg(target_os = "linux")]
#![warn(
clippy::all,
clippy::pedantic,
clippy::correctness,
clippy::perf,
clippy::style,
clippy::suspicious,
clippy::complexity,
clippy::nursery,
clippy::unwrap_used,
unused_qualifications,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_allocation,
clippy::unnecessary_cast,
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::dbg_macro,
unused_import_braces,
clippy::deprecated_cfg_attr,
clippy::separated_literal_suffix,
deprecated
)]
#![forbid(deprecated_in_future)]
#![deny(unused_must_use)]
#![allow(
clippy::missing_errors_doc,
clippy::future_not_send,
clippy::module_name_repetitions
)]
use libc::pid_t;
use std::ffi::c_int;

View file

@ -1,4 +1,36 @@
extern crate proc_macro;
#![warn(
clippy::all,
clippy::pedantic,
clippy::correctness,
clippy::perf,
clippy::style,
clippy::suspicious,
clippy::complexity,
clippy::nursery,
clippy::unwrap_used,
unused_qualifications,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_allocation,
clippy::unnecessary_cast,
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::dbg_macro,
unused_import_braces,
clippy::deprecated_cfg_attr,
clippy::separated_literal_suffix,
deprecated
)]
#![forbid(deprecated_in_future)]
#![deny(unused_must_use)]
#![allow(
clippy::missing_errors_doc,
clippy::future_not_send,
clippy::module_name_repetitions
)]
use proc_macro::TokenStream;
use quote::quote;

View file

@ -2,3 +2,14 @@
#[allow(warnings, unused)]
#[rustfmt::skip]
pub mod prisma;
pub async fn test_db() -> std::sync::Arc<prisma::PrismaClient> {
std::sync::Arc::new(
prisma::PrismaClient::_builder()
.with_url(format!("file:/tmp/test-db-{}", uuid::Uuid::new_v4()))
.build()
.await
.unwrap(),
)
}

32
packages/utils/Cargo.toml Normal file
View file

@ -0,0 +1,32 @@
[package]
name = "onelauncher_utils"
description = "rust utils for onelauncher"
version = { workspace = true }
license = { workspace = true }
edition = { workspace = true }
repository = { workspace = true }
documentation = { workspace = true }
readme = { workspace = true }
homepage = { workspace = true }
authors = { workspace = true }
[dependencies]
onelauncher_prisma = { workspace = true }
serde = { workspace = true }
uuid = { workspace = true }
async-channel = { workspace = true }
futures = { workspace = true }
pin-project-lite = { workspace = true }
tokio = { workspace = true }
tempfile = { workspace = true }
sys-info = { workspace = true }
zip = { workspace = true }
dunce = { workspace = true }
async-compression = { workspace = true }
chrono = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
nom = { workspace = true }
prisma-client-rust = { workspace = true }
regex = { workspace = true }
interpulse = { workspace = true }

View file

@ -12,7 +12,7 @@ use tokio::task::spawn_blocking;
#[derive(Debug, thiserror::Error)]
pub enum IOError {
/// A wrapped [`std::io::Error`] along with the path involved in the error.
#[error("{source}, path: {path}")]
#[error("error acessing path: {source}, path: {path}")]
IOErrorWrapper {
#[source]
source: std::io::Error,
@ -26,14 +26,29 @@ pub enum IOError {
IOError(#[from] std::io::Error),
}
impl<P: AsRef<std::path::Path>> From<(P, std::io::Error)> for IOError {
fn from((path, source): (P, std::io::Error)) -> Self {
Self::IOErrorWrapper {
source,
path: path.as_ref().to_string_lossy().to_string(),
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("received a non UTF-8 path: <lossy_path='{}'>", .0.to_string_lossy())]
pub struct NonUtf8PathError(pub Box<std::path::Path>);
impl IOError {
/// Converts a [`std::io::Error`] into an [`IOError`].
pub fn from(source: std::io::Error) -> Self {
#[must_use]
pub const fn from(source: std::io::Error) -> Self {
Self::IOError(source)
}
/// Converts a [`zip::result::ZipError`] into an [`IOError`].
pub fn from_zip(source: zip::result::ZipError) -> Self {
#[must_use]
pub const fn from_zip(source: zip::result::ZipError) -> Self {
Self::ZipError(source)
}

82
packages/utils/src/lib.rs Normal file
View file

@ -0,0 +1,82 @@
//! **`OneLauncher` Utilities**
//! Standard utilities for use within all Rust subprojects.
//!
//! - [`logging`]: Async utilities for log4j parsing with [`nom`].
//! - [`platform`]: Async utilities for managing OS-specific [`interpulse`] operations and rules.
//! - [`io`]: Async wrapper around [`tokio::fs`] and [`std::io::Error`] for our error system.
#![warn(
clippy::all,
clippy::pedantic,
clippy::correctness,
clippy::perf,
clippy::style,
clippy::suspicious,
clippy::complexity,
clippy::nursery,
clippy::unwrap_used,
unused_qualifications,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_allocation,
clippy::unnecessary_cast,
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::dbg_macro,
unused_import_braces,
clippy::deprecated_cfg_attr,
clippy::separated_literal_suffix,
deprecated
)]
#![forbid(deprecated_in_future)]
#![deny(unused_must_use)]
#![allow(
clippy::missing_errors_doc,
clippy::future_not_send,
clippy::module_name_repetitions
)]
pub mod io;
pub mod logging;
pub mod platform;
pub mod prisma;
/// Simple macro that takes a mutable reference and inserts it into a codeblock closure
/// as an owned reference.
///
/// mutable reference gets epically owned by free thinking macro!!!!! (not clickbait)
/// im going insane insane insane insane insane insane insane insane insane
#[macro_export]
macro_rules! ref_owned {
($id:ident = $init:expr => $transform:block) => {{
let mut it = $init;
{
let $id = &mut it;
$transform;
}
it
}};
}
/// Combines an iterator of `T` and an iterator of [`Option<T>`],
/// removing and ensuring the safety of any [`None`] values in the process.
pub fn chain_iterator<T>(
required: impl IntoIterator<Item = T>,
optional: impl IntoIterator<Item = Option<T>>,
) -> Vec<T> {
required
.into_iter()
.map(Some)
.chain(optional)
.flatten()
.collect()
}
#[inline]
#[must_use]
pub fn test_type_of<T>(_: T) -> &'static str {
std::any::type_name::<T>()
}

View file

@ -34,7 +34,7 @@ fn whitespace<'a, O, E: ParseError<&'a str>>(
delimited(multispace0, inner, multispace0)
}
pub fn parse_logentry(input: &str) -> IResult<&str, LogEntry> {
pub fn parse_logentry(input: &str) -> IResult<&str, LogEntry<'_>> {
let (o, (attributes, _, message)) = preceded(
multispace0,
alt((
@ -74,7 +74,7 @@ pub struct Attributes<'a> {
pub thread_name: &'a str,
}
fn attributes(input: &str) -> IResult<&str, Attributes> {
fn attributes(input: &str) -> IResult<&str, Attributes<'_>> {
let (o, attributes) = count(whitespace(attribute), 4)(input)?;
macro_rules! extract_attribute {
($field:ident) => {{
@ -114,11 +114,11 @@ enum Attribute<'a> {
ThreadName(&'a str),
}
fn attribute(input: &str) -> IResult<&str, Attribute> {
fn attribute(input: &str) -> IResult<&str, Attribute<'_>> {
alt((attr_logger, attr_timestamp, attr_level, attr_thread))(input)
}
fn attr_logger(input: &str) -> IResult<&str, Attribute> {
fn attr_logger(input: &str) -> IResult<&str, Attribute<'_>> {
map(
separated_pair(
tag("logger"),
@ -129,7 +129,7 @@ fn attr_logger(input: &str) -> IResult<&str, Attribute> {
)(input)
}
fn attr_timestamp(input: &str) -> IResult<&str, Attribute> {
fn attr_timestamp(input: &str) -> IResult<&str, Attribute<'_>> {
map(
separated_pair(
tag("timestamp"),
@ -140,7 +140,7 @@ fn attr_timestamp(input: &str) -> IResult<&str, Attribute> {
)(input)
}
fn attr_level(input: &str) -> IResult<&str, Attribute> {
fn attr_level(input: &str) -> IResult<&str, Attribute<'_>> {
map(
separated_pair(
tag("level"),
@ -151,7 +151,7 @@ fn attr_level(input: &str) -> IResult<&str, Attribute> {
)(input)
}
fn attr_thread(input: &str) -> IResult<&str, Attribute> {
fn attr_thread(input: &str) -> IResult<&str, Attribute<'_>> {
map(
separated_pair(
tag("thread"),
@ -185,7 +185,7 @@ fn message(input: &str) -> IResult<&str, &str> {
)(input)
}
fn plain_text(input: &str) -> IResult<&str, (Attributes, &str, &str)> {
fn plain_text(input: &str) -> IResult<&str, (Attributes<'_>, &str, &str)> {
map(take_until("<"), |text| {
(
Attributes {
@ -201,15 +201,16 @@ fn plain_text(input: &str) -> IResult<&str, (Attributes, &str, &str)> {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn parse_message() {
message(
r#"<log4j:Message>
r"<log4j:Message>
<![CDATA[200 Datafixer optimizations took 2000 years]]>
</log4j:Message>"#,
</log4j:Message>",
)
.unwrap();
}
@ -232,7 +233,7 @@ mod tests {
Attributes {
logger: "com.mojang.datafixers.DataFixerBuilder",
level: LogLevel::Debug,
timestamp: 3213819318239,
timestamp: 3_213_819_318_239,
thread_name: "Datafixer"
}
);
@ -257,7 +258,7 @@ mod tests {
#[test]
fn parse_timestamp_attribute() {
let (_, attr) = attr_timestamp(r#"timestamp="313213473127""#).unwrap();
assert_eq!(attr, Attribute::Timestamp(313213473127));
assert_eq!(attr, Attribute::Timestamp(313_213_473_127));
}
#[test]
@ -296,7 +297,7 @@ mod tests {
LogEntry {
logger: "com.mojang.datafixers.DataFixerBuilder",
level: LogLevel::Debug,
timestamp: 3213712731731,
timestamp: 3_213_712_731_731,
thread_name: "Datafixer",
message: "200 Datafixer optimizations took 2000 years",
}

View file

@ -19,26 +19,26 @@ impl OsExt for Os {
fn native_arch(java_arch: &str) -> Self {
if std::env::consts::OS == "windows" {
if java_arch == "aarch64" {
Os::WindowsArm64
Self::WindowsArm64
} else {
Os::Windows
Self::Windows
}
} else if std::env::consts::OS == "linux" {
if java_arch == "aarch64" {
Os::LinuxArm64
Self::LinuxArm64
} else if java_arch == "arm" {
Os::LinuxArm32
Self::LinuxArm32
} else {
Os::Linux
Self::Linux
}
} else if std::env::consts::OS == "macos" {
if java_arch == "aarch64" {
Os::OsxArm64
Self::OsxArm64
} else {
Os::Osx
Self::Osx
}
} else {
Os::Unknown
Self::Unknown
}
}
@ -53,6 +53,7 @@ impl OsExt for Os {
}
/// Handles an os-specific [`OsRule`], returning if the [`Os`] matches the rule.
#[must_use]
pub fn os_rule(rule: &OsRule, java_arch: &str, updated: bool) -> bool {
let mut rule_match = true;
@ -78,6 +79,7 @@ pub fn os_rule(rule: &OsRule, java_arch: &str, updated: bool) -> bool {
}
/// Gets the seperator between Java classpaths in metadata (`";"` on Windows, `";"` on others).
#[must_use]
pub fn classpath_separator(java_arch: &str) -> &'static str {
match Os::native_arch(java_arch) {
Os::Osx | Os::OsxArm64 | Os::Linux | Os::LinuxArm32 | Os::LinuxArm64 | Os::Unknown => ":",

View file

@ -0,0 +1,136 @@
use onelauncher_prisma::prisma::PrismaClient;
use prisma_client_rust::migrations::{DbPushError, MigrateDeployError};
use prisma_client_rust::NewClientError;
/// Represents an error that occured while opening and running migrations
/// on the [`prisma_client_rust`] database.
#[derive(thiserror::Error, Debug)]
pub enum MigrationError {
#[error("failed to initialize a new database connection: {0}")]
NewClient(#[from] Box<NewClientError>),
#[error("failed to migrate the database: {0}")]
MigrateFailed(#[from] MigrateDeployError),
#[cfg(debug_assertions)]
#[error("failed to migrate the database: {0}")]
DbPushFailed(#[from] DbPushError),
}
/// Loads the database from the given path and migrates it to the latest schema.
pub async fn load_and_migrate(db_url: &str) -> Result<PrismaClient, MigrationError> {
let client = PrismaClient::_builder()
.with_url(db_url.to_string())
.build()
.await
.map_err(Box::new)?;
client._migrate_deploy().await?;
#[cfg(debug_assertions)]
{
let mut builder = client._db_push();
if std::env::var("ONELAUNCHER_DO_NOT_USE_THIS")
.map(|v| v == "true")
.unwrap_or(false)
{
builder = builder.accept_data_loss();
}
if std::env::var("ONELAUNCHER_FORCE_RESET")
.map(|v| v == "true")
.unwrap_or(false)
{
builder = builder.force_reset();
}
let res = builder.await;
match res {
Ok(_) => {}
Err(e @ DbPushError::PossibleDataLoss(_)) => {
eprintln!("pushing prisma schema may result in data loss. use `ONELAUNCHER_DO_NOT_USE_THIS=true` to force it.");
Err(e)?;
}
Err(e) => Err(e)?,
}
}
Ok(client)
}
/// Construct back an inode after storing it in database
#[must_use]
pub const fn inode_from_db(db_inode: &[u8]) -> u64 {
u64::from_le_bytes([
db_inode[0],
db_inode[1],
db_inode[2],
db_inode[3],
db_inode[4],
db_inode[5],
db_inode[6],
db_inode[7],
])
}
#[must_use]
pub fn inode_to_db(inode: u64) -> Vec<u8> {
inode.to_le_bytes().to_vec()
}
#[must_use]
pub const fn size_in_bytes_from_db(db_size_in_bytes: &[u8]) -> u64 {
u64::from_be_bytes([
db_size_in_bytes[0],
db_size_in_bytes[1],
db_size_in_bytes[2],
db_size_in_bytes[3],
db_size_in_bytes[4],
db_size_in_bytes[5],
db_size_in_bytes[6],
db_size_in_bytes[7],
])
}
#[must_use]
pub fn size_in_bytes_to_db(size: u64) -> Vec<u8> {
size.to_be_bytes().to_vec()
}
#[derive(thiserror::Error, Debug)]
#[error("missing database field {0}")]
pub struct MissingFieldError(&'static str);
impl MissingFieldError {
#[must_use]
pub const fn new(value: &'static str) -> Self {
Self(value)
}
}
pub trait OptionalField: Sized {
type Out;
fn transform(self) -> Option<Self::Out>;
}
impl<T> OptionalField for Option<T> {
type Out = T;
fn transform(self) -> Self {
self
}
}
impl<'a, T> OptionalField for &'a Option<T> {
type Out = &'a T;
fn transform(self) -> Option<Self::Out> {
self.as_ref()
}
}
pub fn maybe_missing<T: OptionalField>(
data: T,
field: &'static str,
) -> Result<T::Out, MissingFieldError> {
data.transform().ok_or(MissingFieldError(field))
}