feat: Package downloading and linking
This commit is contained in:
parent
17f8b382a7
commit
f939169aa6
31 changed files with 999 additions and 272 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3116,6 +3116,7 @@ name = "onelauncher_migration"
|
|||
version = "0.2.0-alpha.2"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"sea-orm-migration",
|
||||
"tokio",
|
||||
]
|
||||
|
|
|
|||
178
Cargo.toml
178
Cargo.toml
|
|
@ -1,22 +1,22 @@
|
|||
[workspace]
|
||||
resolver = "2" # virtual workspace resolver so we don't need a root package
|
||||
members = [
|
||||
"packages/core",
|
||||
"packages/entity",
|
||||
"packages/gamemode",
|
||||
"packages/macro",
|
||||
"packages/migration",
|
||||
"packages/serde_json_path_to_error"
|
||||
"packages/serde_json_path_to_error",
|
||||
]
|
||||
resolver = "2" # virtual workspace resolver so we don't need a root package
|
||||
|
||||
[workspace.package]
|
||||
license = "GPL-3.0-only"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/Polyfrost/OneLauncher"
|
||||
documentation = "https://docs.polyfrost.org/onelauncher"
|
||||
readme = "README.md"
|
||||
homepage = "https://polyfrost.org/projects/onelauncher"
|
||||
authors = [ "Polyfrost", "Polyfrost Contributors" ]
|
||||
documentation = "https://docs.polyfrost.org/onelauncher"
|
||||
edition = "2024"
|
||||
homepage = "https://polyfrost.org/projects/onelauncher"
|
||||
license = "GPL-3.0-only"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Polyfrost/OneLauncher"
|
||||
version = "0.2.0-alpha.2"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
|
@ -34,16 +34,16 @@ tauri = { version = "2.0", features = [
|
|||
"tracing",
|
||||
"specta",
|
||||
"uuid",
|
||||
"protocol-asset"
|
||||
"protocol-asset",
|
||||
] }
|
||||
tauri-build = { version = "2.0" }
|
||||
tauri-plugin-shell = { version = "2.0" }
|
||||
tauri-plugin-window-state = { version = "2.0" }
|
||||
tauri-plugin-single-instance = { version = "2.0" }
|
||||
tauri-plugin-clipboard-manager = { version = "2.0" }
|
||||
tauri-plugin-updater = { version = "2.0" }
|
||||
tauri-plugin-dialog = { version = "2.0" }
|
||||
tauri-plugin-deep-link = { version = "2.0" }
|
||||
tauri-plugin-dialog = { version = "2.0" }
|
||||
tauri-plugin-shell = { version = "2.0" }
|
||||
tauri-plugin-single-instance = { version = "2.0" }
|
||||
tauri-plugin-updater = { version = "2.0" }
|
||||
tauri-plugin-window-state = { version = "2.0" }
|
||||
|
||||
# core
|
||||
interpulse = { version = "=1.2.2", features = [ "specta" ] }
|
||||
|
|
@ -56,10 +56,10 @@ specta = { version = "2.0.0-rc.20", features = [
|
|||
"serde_json",
|
||||
"function",
|
||||
"derive",
|
||||
"time"
|
||||
"time",
|
||||
] }
|
||||
tauri-specta = { version = "2.0.0-rc.20", features = [ "javascript", "typescript", "derive" ] }
|
||||
specta-typescript = { version = "=0.0.7" }
|
||||
tauri-specta = { version = "2.0.0-rc.20", features = [ "javascript", "typescript", "derive" ] }
|
||||
|
||||
# security
|
||||
iota_stronghold = { version = "2.1" }
|
||||
|
|
@ -67,27 +67,28 @@ zeroize = { version = "1.8", features = [ "zeroize_derive" ] }
|
|||
|
||||
# logging
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3", features = [ "chrono", "env-filter" ] }
|
||||
tracing-error = { version = "0.2" }
|
||||
tracing-appender = { version = "0.2" }
|
||||
tracing-error = { version = "0.2" }
|
||||
tracing-subscriber = { version = "0.3", features = [ "chrono", "env-filter" ] }
|
||||
|
||||
# serialization & text
|
||||
base64 = { version = "0.22" }
|
||||
byteorder = { version = "1.5.0" }
|
||||
chrono = { version = "0.4", features = [ "serde" ] }
|
||||
nom = { version = "7.1" }
|
||||
p256 = { version = "0.13.2", features = [ "ecdsa" ] }
|
||||
rand = { version = "0.8.5" }
|
||||
regex = { version = "1.10" }
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
serde_json = { version = "1.0" }
|
||||
serde_with = { version = "=3.12.0", features = [ "chrono_0_4", "json", "macros" ] }
|
||||
regex = { version = "1.10" }
|
||||
url = { version = "2.5.2" }
|
||||
strum = { version = "0.26", features = [ "derive" ] }
|
||||
chrono = { version = "0.4", features = [ "serde" ] }
|
||||
rand = { version = "0.8.5" }
|
||||
byteorder = { version = "1.5.0" }
|
||||
base64 = { version = "0.22" }
|
||||
p256 = { version = "0.13.2", features = [ "ecdsa" ] }
|
||||
uuid = { version = "1.10", features = [ "serde", "v4" ] }
|
||||
toml = { version = "0.8" }
|
||||
nom = { version = "7.1" }
|
||||
url = { version = "2.5.2" }
|
||||
uuid = { version = "1.10", features = [ "serde", "v4" ] }
|
||||
|
||||
# network
|
||||
async-tungstenite = { version = "0.28", features = [ "tokio-runtime", "tokio-rustls-webpki-roots" ] }
|
||||
reqwest = { version = "0.12", default-features = false, features = [
|
||||
"json",
|
||||
"stream",
|
||||
|
|
@ -99,33 +100,32 @@ reqwest = { version = "0.12", default-features = false, features = [
|
|||
"macos-system-configuration",
|
||||
"deflate",
|
||||
"gzip",
|
||||
"brotli"
|
||||
"brotli",
|
||||
] }
|
||||
async-tungstenite = { version = "0.28", features = [ "tokio-runtime", "tokio-rustls-webpki-roots" ] }
|
||||
webbrowser = { version = "1.0" }
|
||||
|
||||
# io
|
||||
tokio = { version = "1.40", features = [ "full" ] }
|
||||
tokio-util = { version = "0.7", features = [ "compat" ] }
|
||||
tokio-stream = { version = "0.1.16", features = [ "fs" ] }
|
||||
async-compression = { version = "0.4.12", features = [ "flate2", "tokio" ] }
|
||||
async_zip = { version = "0.0.17", features = [ "full" ] }
|
||||
bytes = { version = "1.7" }
|
||||
dirs = { version = "5.0" }
|
||||
dunce = { version = "1.0.5" }
|
||||
flate2 = { version = "1.0" }
|
||||
notify = { version = "6.1", default-features = false }
|
||||
notify-debouncer-mini = { version = "0.4", default-features = false }
|
||||
tempfile = { version = "3.19" }
|
||||
dunce = { version = "1.0.5" }
|
||||
zip = { version = "2.2.0" }
|
||||
dirs = { version = "5.0" }
|
||||
async_zip = { version = "0.0.17", features = [ "full" ] }
|
||||
async-compression = { version = "0.4.12", features = [ "flate2", "tokio" ] }
|
||||
flate2 = { version = "1.0" }
|
||||
bytes = { version = "1.7" }
|
||||
sanitize-filename = { version = "0.6" }
|
||||
tempfile = { version = "3.19" }
|
||||
tokio = { version = "1.40", features = [ "full" ] }
|
||||
tokio-stream = { version = "0.1.16", features = [ "fs" ] }
|
||||
tokio-util = { version = "0.7", features = [ "compat" ] }
|
||||
zip = { version = "2.2.0" }
|
||||
|
||||
# crypto
|
||||
digest = { version = "0.10" }
|
||||
sha1 = { version = "0.10" }
|
||||
sha2 = { version = "0.10" }
|
||||
md-5 = { version = "0.10" }
|
||||
murmur2 = { version = "0.1.0" }
|
||||
sha1 = { version = "0.10" }
|
||||
sha2 = { version = "0.10" }
|
||||
|
||||
# db
|
||||
sea-orm = { version = "1.1", features = [
|
||||
|
|
@ -134,85 +134,91 @@ sea-orm = { version = "1.1", features = [
|
|||
"macros",
|
||||
"with-chrono",
|
||||
"with-json",
|
||||
"with-uuid"
|
||||
"with-uuid",
|
||||
] }
|
||||
sea-orm-migration = { version = "1.1", features = [
|
||||
"sqlx-sqlite",
|
||||
"runtime-tokio-rustls",
|
||||
"with-chrono",
|
||||
"with-json",
|
||||
"with-uuid",
|
||||
] }
|
||||
sea-orm-migration = { version = "1.1", features = [ "sqlx-sqlite", "runtime-tokio-rustls" ] }
|
||||
|
||||
# utils
|
||||
thiserror = { version = "1.0" }
|
||||
anyhow = { version = "1.0" }
|
||||
sysinfo = { version = "0.31", features = [ "serde" ] }
|
||||
async-trait = { version = "0.1" }
|
||||
cc = { version = "1.1" }
|
||||
discord-rich-presence = { version = "0.2" }
|
||||
indicatif = { version = "0.17" }
|
||||
futures = { version = "0.3" }
|
||||
futures-lite = { version = "2.6" }
|
||||
async-trait = { version = "0.1" }
|
||||
whoami = { version = "1.5" }
|
||||
cc = { version = "1.1" }
|
||||
indicatif = { version = "0.17" }
|
||||
libc = { version = "0.2" }
|
||||
merge = { version = "0.2" }
|
||||
sysinfo = { version = "0.31", features = [ "serde" ] }
|
||||
thiserror = { version = "1.0" }
|
||||
whoami = { version = "1.5" }
|
||||
|
||||
# macro
|
||||
syn = { version = "2" }
|
||||
quote = { version = "1" }
|
||||
paste = { version = "1" }
|
||||
quote = { version = "1" }
|
||||
syn = { version = "2" }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
correctness = { level = "warn", priority = -1 }
|
||||
perf = { level = "warn", priority = -1 }
|
||||
style = { level = "warn", priority = -1 }
|
||||
suspicious = { level = "warn", priority = -1 }
|
||||
complexity = { level = "warn", priority = -1 }
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
unnecessary_cast = "warn"
|
||||
cast_lossless = "warn"
|
||||
cast_precision_loss = "allow"
|
||||
complexity = { level = "warn", priority = -1 }
|
||||
correctness = { level = "warn", priority = -1 }
|
||||
dbg_macro = "warn"
|
||||
deprecated_cfg_attr = "warn"
|
||||
separated_literal_suffix = "warn"
|
||||
missing_errors_doc = "allow"
|
||||
future_not_send = "allow"
|
||||
module_name_repetitions = "allow"
|
||||
struct_field_names = "allow"
|
||||
cast_precision_loss = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
significant_drop_tightening = "allow" # tmp
|
||||
module_name_repetitions = "allow"
|
||||
needless_return = "allow"
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
perf = { level = "warn", priority = -1 }
|
||||
separated_literal_suffix = "warn"
|
||||
significant_drop_tightening = "allow" # tmp
|
||||
struct_field_names = "allow"
|
||||
style = { level = "warn", priority = -1 }
|
||||
suspicious = { level = "warn", priority = -1 }
|
||||
unnecessary_cast = "warn"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unused_qualifications = "warn"
|
||||
deprecated = "warn"
|
||||
deprecated_in_future = "forbid"
|
||||
rust_2018_idioms = { level = "warn", priority = -1 }
|
||||
trivial_casts = "warn"
|
||||
trivial_numeric_casts = "warn"
|
||||
unused_allocation = "warn"
|
||||
unused_import_braces = "warn"
|
||||
deprecated = "warn"
|
||||
deprecated_in_future = "forbid"
|
||||
unused_must_use = "deny"
|
||||
unused_qualifications = "warn"
|
||||
|
||||
[profile.dev]
|
||||
# add debug symbols/logic and optimize for reduced build time (parallel)
|
||||
debug = true # add debug info
|
||||
opt-level = 0 # reduce optimizations
|
||||
strip = "none" # don't remove debug symbols
|
||||
incremental = true # enable incremental compilation
|
||||
split-debuginfo = "unpacked" # unpack debug info
|
||||
lto = false # disable link time optimizations, reduce compile time
|
||||
codegen-units = 256 # make parallel incremental builds faster
|
||||
rpath = false # disable rpath
|
||||
debug = true # add debug info
|
||||
incremental = true # enable incremental compilation
|
||||
lto = false # disable link time optimizations, reduce compile time
|
||||
opt-level = 0 # reduce optimizations
|
||||
panic = "unwind" # add unwind panic logic because some deps require it
|
||||
rpath = false # disable rpath
|
||||
split-debuginfo = "unpacked" # unpack debug info
|
||||
strip = "none" # don't remove debug symbols
|
||||
|
||||
# add debug symbols/logic and optimize for reduced build time (parallel)
|
||||
[profile.dev-debug]
|
||||
inherits = "dev" # extend the dev profile
|
||||
split-debuginfo = "none" # don't split debug info
|
||||
opt-level = 0 # reduce optimizations
|
||||
debug = "full" # add full debug info
|
||||
strip = "none" # don't remove debug symbols
|
||||
lto = "off" # disable link time optimizations, reduce compile time
|
||||
codegen-units = 256 # make parallel incremental builds faster
|
||||
debug = "full" # add full debug info
|
||||
incremental = true # enable incremental compilation
|
||||
inherits = "dev" # extend the dev profile
|
||||
lto = "off" # disable link time optimizations, reduce compile time
|
||||
opt-level = 0 # reduce optimizations
|
||||
split-debuginfo = "none" # don't split debug info
|
||||
strip = "none" # don't remove debug symbols
|
||||
|
||||
# set the settings for build scripts and proc-macros.
|
||||
[profile.dev.build-override]
|
||||
|
|
@ -220,20 +226,20 @@ opt-level = 3 # optimize build
|
|||
|
||||
# set the default for dependencies, except workspace members.
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3 # optimize build
|
||||
incremental = false # disable incremental compilation
|
||||
opt-level = 3 # optimize build
|
||||
|
||||
# set the default for dependencies, except workspace members.
|
||||
[profile.dev-debug.package."*"]
|
||||
inherits = "dev" # extend the dev profile
|
||||
opt-level = 3 # optimize build
|
||||
debug = "full" # add full debug info
|
||||
incremental = false # disable incremental compilation
|
||||
inherits = "dev" # extend the dev profile
|
||||
opt-level = 3 # optimize build
|
||||
|
||||
# optimize for speed and reduce size for release
|
||||
[profile.release]
|
||||
panic = "abort" # strip panic cleanup logic
|
||||
codegen-units = 1 # compile crate one after another for better optimization
|
||||
lto = true # enable link time optimization
|
||||
opt-level = "s" # optimize binary
|
||||
panic = "abort" # strip panic cleanup logic
|
||||
strip = true # remove debug symbols
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ pub async fn main() -> LauncherResult<()> {
|
|||
let cluster_name = "Launchable";
|
||||
let path = dirs.clusters_dir().join(cluster_name);
|
||||
|
||||
let mut cluster = if let Some(cluster) = api::cluster::dao::get_cluster_by_path(&path).await? {
|
||||
let mut cluster = if let Some(cluster) = api::cluster::dao::get_cluster_by_folder_name(&path).await? {
|
||||
cluster
|
||||
} else {
|
||||
api::cluster::create_cluster(cluster_name, "1.20.4", GameLoader::Fabric, None, None).await?
|
||||
|
|
@ -66,7 +66,7 @@ pub async fn main() -> LauncherResult<()> {
|
|||
Ok(cluster)
|
||||
}).await?;
|
||||
|
||||
println!("Using cluster: {} at '{}'", cluster.name, cluster.path);
|
||||
println!("Using cluster: {} at '{}'", cluster.name, cluster.folder_name);
|
||||
|
||||
// Launch the game
|
||||
let process = api::game::launch::launch_minecraft(&mut cluster, creds, None).await?;
|
||||
|
|
|
|||
39
packages/core/examples/packages.rs
Normal file
39
packages/core/examples/packages.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use onelauncher_core::{api::{cluster, packages::{self, download_package, provider::ProviderExt}, proxy::ProxyDynamic}, error::LauncherResult, initialize_core, store::{CoreOptions, Dirs}};
|
||||
use onelauncher_entity::{loader::GameLoader, package::Provider};
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> LauncherResult<()> {
|
||||
initialize_core(CoreOptions::default(), ProxyDynamic::default()).await?;
|
||||
|
||||
let dirs = Dirs::get().await?;
|
||||
|
||||
// Here we create the cluster
|
||||
let cluster_name = "Chatting Profile";
|
||||
let path = dirs.clusters_dir().join(cluster_name);
|
||||
|
||||
let cluster = if let Some(cluster) = cluster::dao::get_cluster_by_folder_name(&path).await? {
|
||||
cluster
|
||||
} else {
|
||||
cluster::create_cluster(cluster_name, "1.8.9", GameLoader::Forge, None, None).await?
|
||||
};
|
||||
|
||||
// Fetch our package and its versions
|
||||
let provider = Provider::Modrinth;
|
||||
let package = &provider.get("chatting").await?;
|
||||
let versions = provider.get_versions(package.version_ids.as_slice()).await?;
|
||||
|
||||
let version = versions.iter().find(|v| v.mc_versions.contains(&"1.8.9".to_string())).expect("Version supporting 1.8.9 not found");
|
||||
|
||||
// We can now download the package
|
||||
let model = download_package(package, version, None).await?;
|
||||
|
||||
// Link the package to the cluster
|
||||
packages::link_package(&model, &cluster, None).await?;
|
||||
|
||||
// Check if the package is hard linked
|
||||
let cluster_dir = dirs.clusters_dir().join(cluster.folder_name.clone());
|
||||
let pkg_path = cluster_dir.join(model.package_type.folder_name()).join(model.file_name.as_str());
|
||||
assert!(tokio::fs::metadata(&pkg_path).await.is_ok(), "Package not found in cluster directory");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ pub type ClusterId = i64;
|
|||
/// Inserts a cluster in the database.
|
||||
pub async fn insert_cluster(
|
||||
name: &str,
|
||||
path: &str,
|
||||
folder_name: &str,
|
||||
mc_version: &str,
|
||||
mc_loader: GameLoader,
|
||||
mc_loader_version: Option<&str>,
|
||||
|
|
@ -21,7 +21,7 @@ pub async fn insert_cluster(
|
|||
|
||||
let model = clusters::ActiveModel {
|
||||
name: Set(name.to_string()),
|
||||
path: Set(path.to_string()),
|
||||
folder_name: Set(folder_name.to_string()),
|
||||
mc_version: Set(mc_version.to_string()),
|
||||
mc_loader: Set(mc_loader),
|
||||
mc_loader_version: Set(mc_loader_version.map(String::from)),
|
||||
|
|
@ -96,12 +96,12 @@ pub async fn get_cluster_by_id(id: ClusterId) -> LauncherResult<Option<clusters:
|
|||
}
|
||||
|
||||
/// Gets a cluster by its path from the database.
|
||||
pub async fn get_cluster_by_path(path: &Path) -> LauncherResult<Option<clusters::Model>> {
|
||||
pub async fn get_cluster_by_folder_name(folder_name: &Path) -> LauncherResult<Option<clusters::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let model = clusters::Entity::find()
|
||||
.filter(clusters::Column::Path.eq(path.to_string_lossy().to_string()))
|
||||
.filter(clusters::Column::FolderName.eq(folder_name.to_string_lossy().to_string()))
|
||||
.one(db)
|
||||
.await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,15 +55,17 @@ pub async fn create_cluster(
|
|||
let cluster_dir = Dirs::get_clusters_dir().await?;
|
||||
|
||||
// Get the directory for the cluster
|
||||
let mut path = cluster_dir.join(&name);
|
||||
let mut folder_name = name.clone();
|
||||
let mut path = cluster_dir.join(&folder_name);
|
||||
|
||||
// Folder name conflict resolution
|
||||
if path.exists() {
|
||||
let mut which = 1;
|
||||
loop {
|
||||
let new_name = format!("{name} ({which})");
|
||||
let new_name = format!("{folder_name} ({which})");
|
||||
path = cluster_dir.join(&new_name);
|
||||
if !path.exists() {
|
||||
folder_name = new_name;
|
||||
break;
|
||||
}
|
||||
which += 1;
|
||||
|
|
@ -84,7 +86,7 @@ pub async fn create_cluster(
|
|||
// Finally add the cluster to the database
|
||||
dao::insert_cluster(
|
||||
name.as_str(),
|
||||
path.to_string_lossy().into_owned().as_str(),
|
||||
&folder_name,
|
||||
mc_version,
|
||||
mc_loader,
|
||||
mc_loader_version,
|
||||
|
|
@ -253,7 +255,7 @@ async fn run_forge_processors(
|
|||
client => cluster.mc_version.clone(),
|
||||
server => "";
|
||||
"ROOT":
|
||||
client => cluster.path.clone(),
|
||||
client => dirs.clusters_dir().join(cluster.folder_name.clone()).to_string_lossy(),
|
||||
server => "";
|
||||
"LIBRARY_DIR":
|
||||
client => libraries.to_string_lossy(),
|
||||
|
|
@ -314,7 +316,7 @@ pub async fn update_playtime(id: ClusterId, duration: i64) -> LauncherResult<clu
|
|||
let overall_played = cluster.overall_played.take().unwrap_or_default().unwrap_or_default();
|
||||
|
||||
cluster.overall_played = Set(Some(overall_played + duration));
|
||||
cluster.last_played = Set(Some(Utc::now().timestamp()));
|
||||
cluster.last_played = Set(Some(Utc::now()));
|
||||
|
||||
Ok(cluster)
|
||||
}).await
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ pub async fn sync_clusters() -> LauncherResult<Vec<ClusterId>> {
|
|||
let clusters = dao::get_all_clusters().await?;
|
||||
|
||||
let mut missing_ids = Vec::new();
|
||||
let mut checked_paths = HashSet::new();
|
||||
let mut checked_folders = HashSet::new();
|
||||
let dirs = Dirs::get().await?;
|
||||
|
||||
for cluster in clusters {
|
||||
if sync_cluster(&cluster)
|
||||
|
|
@ -40,14 +41,14 @@ pub async fn sync_clusters() -> LauncherResult<Vec<ClusterId>> {
|
|||
{
|
||||
missing_ids.push(cluster.id);
|
||||
|
||||
let path = cluster.path.clone();
|
||||
let path = cluster.folder_name.clone();
|
||||
send_error!(
|
||||
"cluster {} is missing from the filesystem",
|
||||
path.clone()
|
||||
);
|
||||
}
|
||||
|
||||
checked_paths.insert(PathBuf::from(cluster.path));
|
||||
checked_folders.insert(dirs.clusters_dir().join(cluster.folder_name.clone()));
|
||||
}
|
||||
|
||||
// Check for clusters in the filesystem that are not in the database
|
||||
|
|
@ -58,7 +59,7 @@ pub async fn sync_clusters() -> LauncherResult<Vec<ClusterId>> {
|
|||
let mut stream = io::read_dir(cluster_dir).await?;
|
||||
while let Ok(Some(entry)) = stream.next_entry().await {
|
||||
let path = entry.path();
|
||||
if !path.is_dir() || checked_paths.contains(&path) {
|
||||
if !path.is_dir() || checked_folders.contains(&path) {
|
||||
// Skip if it's not a directory or if we've already checked it
|
||||
continue;
|
||||
}
|
||||
|
|
@ -79,7 +80,7 @@ pub async fn sync_clusters() -> LauncherResult<Vec<ClusterId>> {
|
|||
/// Checks if the cluster in the database exists and if the directory exists.
|
||||
#[tracing::instrument]
|
||||
pub async fn sync_cluster(cluster: &clusters::Model) -> LauncherResult<SyncAction> {
|
||||
let path = PathBuf::from(cluster.path.clone());
|
||||
let path = Dirs::get_clusters_dir().await?.join(cluster.folder_name.clone());
|
||||
if !path.exists() {
|
||||
return Ok(SyncAction::MissingFs);
|
||||
}
|
||||
|
|
@ -104,7 +105,7 @@ pub async fn sync_cluster_by_id(id: ClusterId) -> LauncherResult<SyncAction> {
|
|||
/// Checks if the cluster in the database exists and if the directory exists.
|
||||
#[tracing::instrument]
|
||||
pub async fn sync_cluster_by_path(path: &PathBuf) -> LauncherResult<SyncAction> {
|
||||
let Some(cluster) = dao::get_cluster_by_path(path).await? else {
|
||||
let Some(cluster) = dao::get_cluster_by_folder_name(path).await? else {
|
||||
tracing::warn!("cluster with path {path:?} not found in database");
|
||||
return Ok(SyncAction::MissingDb);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,12 +5,10 @@ use chrono::Utc;
|
|||
use discord_rich_presence::activity::Timestamps;
|
||||
use merge::Merge;
|
||||
use onelauncher_entity::prelude::model::*;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::api::cluster::dao::update_cluster;
|
||||
use crate::api::cluster::{ClusterError, prepare_cluster};
|
||||
use crate::api::cluster::{prepare_cluster, update_playtime, ClusterError};
|
||||
use crate::api::game::{arguments, log, metadata};
|
||||
use crate::api::java::{self, JavaError};
|
||||
use crate::api::setting_profiles::dao::get_profile_by_name;
|
||||
|
|
@ -41,6 +39,7 @@ pub async fn launch_minecraft(
|
|||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
let dirs = Dirs::get().await?;
|
||||
|
||||
if !state.settings.read().await.allow_parallel_running_clusters {
|
||||
let running = state.processes.has_running(cluster.id).await;
|
||||
|
|
@ -49,7 +48,7 @@ pub async fn launch_minecraft(
|
|||
}
|
||||
}
|
||||
|
||||
let cwd = &io::canonicalize(&cluster.path)?;
|
||||
let cwd = &io::canonicalize(dirs.clusters_dir().join(cluster.folder_name.clone()))?;
|
||||
|
||||
let metadata = state.metadata.read().await;
|
||||
let versions = &metadata.get_vanilla()?.versions;
|
||||
|
|
@ -85,7 +84,6 @@ pub async fn launch_minecraft(
|
|||
let java_path = PathBuf::from(&java.absolute_path);
|
||||
java::check_java_runtime(&java_path, false).await?;
|
||||
|
||||
let dirs = Dirs::get().await?;
|
||||
let client_jar_path = dirs
|
||||
.versions_dir()
|
||||
.join(&version_name)
|
||||
|
|
@ -187,19 +185,14 @@ pub async fn launch_minecraft(
|
|||
io::write(&options_path, options_string).await?;
|
||||
}
|
||||
|
||||
let start_time = Utc::now().timestamp();
|
||||
update_cluster(cluster, async |mut cluster| {
|
||||
cluster.last_played = Set(Some(start_time));
|
||||
Ok(cluster)
|
||||
})
|
||||
.await?;
|
||||
update_playtime(cluster.id, 0).await?;
|
||||
|
||||
let censors = log::create_censors(&creds);
|
||||
|
||||
if let Some(discord) = &state.rpc {
|
||||
discord.set_message(
|
||||
&format!("Playing {}", cluster.name),
|
||||
Some(Timestamps::new().start(start_time)),
|
||||
Some(Timestamps::new().start(Utc::now().timestamp())),
|
||||
).await;
|
||||
}
|
||||
|
||||
|
|
|
|||
165
packages/core/src/api/packages/dao.rs
Normal file
165
packages/core/src/api/packages/dao.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
use onelauncher_entity::loader::GameLoader;
|
||||
use onelauncher_entity::package::PackageType;
|
||||
use onelauncher_entity::{cluster_packages, clusters, packages};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter,
|
||||
};
|
||||
|
||||
use crate::error::{DaoError, LauncherResult};
|
||||
use crate::store::State;
|
||||
|
||||
use super::PackageError;
|
||||
|
||||
pub type PackageId = String;
|
||||
|
||||
/// Inserts a new package into the database.
|
||||
pub async fn insert_package(package: packages::ActiveModel) -> LauncherResult<packages::Model> {
|
||||
if package
|
||||
.package_type
|
||||
.try_as_ref()
|
||||
.is_some_and(|p| p == &PackageType::ModPack)
|
||||
{
|
||||
return Err(PackageError::IsModPack.into());
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
Ok(package.insert(db).await?)
|
||||
}
|
||||
|
||||
/// Links a package to a cluster in database.
|
||||
pub async fn link_package_to_cluster(
|
||||
package: &packages::Model,
|
||||
cluster: &clusters::Model,
|
||||
) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let cluster_package = cluster_packages::Model {
|
||||
cluster_id: cluster.id,
|
||||
package_hash: package.hash.clone(),
|
||||
}
|
||||
.into_active_model();
|
||||
|
||||
cluster_packages::Entity::insert(cluster_package)
|
||||
.exec(db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieves a clusters linked packages from the database.
|
||||
pub async fn get_linked_packages(
|
||||
cluster: &clusters::Model,
|
||||
) -> LauncherResult<Vec<packages::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let linked_packages = cluster_packages::Entity::find()
|
||||
.filter(cluster_packages::Column::ClusterId.eq(cluster.id))
|
||||
.find_with_related(packages::Entity)
|
||||
.all(db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|(_cluster_packages, packages)| packages)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(linked_packages)
|
||||
}
|
||||
|
||||
/// Is the package linked to the cluster?
|
||||
pub async fn is_package_linked_to_cluster(
|
||||
package: &packages::Model,
|
||||
cluster: &clusters::Model,
|
||||
) -> LauncherResult<bool> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let linked_packages = cluster_packages::Entity::find()
|
||||
.filter(cluster_packages::Column::ClusterId.eq(cluster.id))
|
||||
.filter(cluster_packages::Column::PackageHash.eq(package.hash.clone()))
|
||||
.one(db)
|
||||
.await?;
|
||||
|
||||
Ok(linked_packages.is_some())
|
||||
}
|
||||
|
||||
/// Retrieves a package by its hash from the database.
|
||||
pub async fn get_package_by_hash(hash: PackageId) -> LauncherResult<Option<packages::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
Ok(packages::Entity::find_by_id(hash).one(db).await?)
|
||||
}
|
||||
|
||||
/// Retrieves all packages that match the package type, loader and version(s) in the database.
|
||||
pub async fn get_packages_for(
|
||||
package_type: PackageType,
|
||||
loader: GameLoader,
|
||||
versions: Vec<String>,
|
||||
) -> LauncherResult<Vec<packages::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
Ok(packages::Entity::find()
|
||||
.filter(packages::Column::PackageType.eq(package_type))
|
||||
.filter(packages::Column::McLoader.eq(loader))
|
||||
.all(db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|p| {
|
||||
let mc_versions = p.mc_versions.clone();
|
||||
versions.iter().any(|v| mc_versions.0.contains(v))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Updates an existing package in the database.
|
||||
pub async fn update_package_by_hash<B>(id: PackageId, block: B) -> LauncherResult<packages::Model>
|
||||
where
|
||||
B: AsyncFnOnce(packages::ActiveModel) -> LauncherResult<packages::ActiveModel>,
|
||||
{
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let model = get_package_by_hash(id).await?.ok_or(DaoError::NotFound)?;
|
||||
let model = block(model.into_active_model()).await?;
|
||||
let model = model.update(db).await?;
|
||||
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
/// Updates an existing package in the database.
|
||||
pub async fn update_package<B>(
|
||||
package: &mut packages::Model,
|
||||
block: B,
|
||||
) -> LauncherResult<&mut packages::Model>
|
||||
where
|
||||
B: AsyncFnOnce(packages::ActiveModel) -> LauncherResult<packages::ActiveModel>,
|
||||
{
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let model = package.clone().into_active_model();
|
||||
let model = block(model).await?;
|
||||
let model = model.update(db).await?;
|
||||
|
||||
*package = model;
|
||||
|
||||
Ok(package)
|
||||
}
|
||||
|
||||
/// Deletes a package by its hash from the **database**.
|
||||
pub async fn delete_package_by_id(id: PackageId) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let deleted = packages::Entity::delete_by_id(id).exec(db).await?;
|
||||
|
||||
if deleted.rows_affected == 0 {
|
||||
return Err(DaoError::NotFound.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -40,8 +40,8 @@ pub struct SearchQuery {
|
|||
#[serde_as]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ManagedPackage {
|
||||
pub id: String,
|
||||
pub provider: Provider,
|
||||
pub provider_id: String,
|
||||
pub package_type: PackageType,
|
||||
pub name: String,
|
||||
pub short_desc: String,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,180 @@
|
|||
use data::{ManagedPackage, ManagedVersion};
|
||||
use onelauncher_entity::icon::Icon;
|
||||
use onelauncher_entity::package::PackageType;
|
||||
use onelauncher_entity::{clusters, packages};
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::error::{LauncherError, LauncherResult};
|
||||
use crate::store::Dirs;
|
||||
use crate::store::ingress::SubIngress;
|
||||
use crate::utils::crypto::HashAlgorithm;
|
||||
use crate::utils::io::IOError;
|
||||
use crate::utils::{http, icon, io};
|
||||
|
||||
pub mod dao;
|
||||
pub mod data;
|
||||
pub mod provider;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PackageError {
|
||||
#[error("no file marked for download")]
|
||||
NoPrimaryFile,
|
||||
#[error("package is a modpack, it must be handled differently")]
|
||||
IsModPack,
|
||||
#[error(transparent)]
|
||||
Incompatible(#[from] IncompatiblePackageType),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum IncompatiblePackageType {
|
||||
#[error("package is not compatible with the current version")]
|
||||
McVersion,
|
||||
#[error("package is not compatible with the current loader")]
|
||||
Loader,
|
||||
}
|
||||
|
||||
/// Downloads a package and registers it in the database from the given provider.
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn download_package(
|
||||
package: &ManagedPackage,
|
||||
version: &ManagedVersion,
|
||||
ingress: Option<SubIngress<'_>>,
|
||||
) -> LauncherResult<packages::Model> {
|
||||
// Modpacks are downloaded and managed differently.
|
||||
if package.package_type == PackageType::ModPack {
|
||||
return Err(PackageError::IsModPack.into());
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
"downloading package '{}' version '{}' from '{}'",
|
||||
package.name,
|
||||
version.version_id,
|
||||
package.provider
|
||||
);
|
||||
|
||||
// Check if the package is already downloaded
|
||||
|
||||
let primary_file = version
|
||||
.files
|
||||
.iter()
|
||||
.find(|f| f.primary)
|
||||
.ok_or(PackageError::NoPrimaryFile)?;
|
||||
|
||||
if let Some(model) = dao::get_package_by_hash(primary_file.sha1.clone()).await? {
|
||||
tracing::debug!("package is already downloaded");
|
||||
return Ok(model);
|
||||
}
|
||||
|
||||
let dir = Dirs::get_package_dir(&package.package_type, &package.provider, &package.id).await?;
|
||||
|
||||
io::create_dir_all(&dir).await?;
|
||||
|
||||
let dest = join_package_file(dir, &version.version_id, primary_file.file_name.as_str());
|
||||
|
||||
http::download_advanced(
|
||||
Method::GET,
|
||||
&primary_file.url,
|
||||
dest,
|
||||
None,
|
||||
None,
|
||||
Some((HashAlgorithm::Sha1, primary_file.sha1.as_str())),
|
||||
ingress.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let icon = if let Some(icon_url) = &package.icon_url {
|
||||
if let Some(icon) = Icon::try_from_url(url::Url::parse(icon_url)?) {
|
||||
Some(icon::cache_icon(&icon).await?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let model = packages::Model {
|
||||
hash: primary_file.sha1.clone(),
|
||||
display_name: package.name.clone(),
|
||||
display_version: version.display_version.clone(),
|
||||
file_name: primary_file.file_name.clone(),
|
||||
version_id: version.version_id.clone(),
|
||||
published_at: version.published,
|
||||
provider: package.provider.clone(),
|
||||
icon,
|
||||
package_id: package.id.clone(),
|
||||
mc_loader: version.loaders.clone().into(),
|
||||
mc_versions: version.mc_versions.clone().into(),
|
||||
package_type: package.package_type.clone(),
|
||||
};
|
||||
|
||||
dao::insert_package(model.into()).await
|
||||
}
|
||||
|
||||
/// Links a package to a cluster on the file system and in database.
|
||||
/// * `skip_compatibility` - Checks whether the `mc_loader` and `mc_version` is compatible. Default is false
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn link_package(
|
||||
package: &packages::Model,
|
||||
cluster: &clusters::Model,
|
||||
skip_compatibility: Option<bool>,
|
||||
) -> LauncherResult<()> {
|
||||
if package.package_type == PackageType::ModPack {
|
||||
return Err(PackageError::IsModPack.into());
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
"linking package '{}' version '{}' to cluster '{}'",
|
||||
package.display_name,
|
||||
package.display_version,
|
||||
cluster.name
|
||||
);
|
||||
|
||||
tracing::trace!("checking if package is already linked to cluster");
|
||||
if dao::is_package_linked_to_cluster(package, cluster).await? {
|
||||
tracing::debug!("package is already linked to cluster");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::trace!("checking compatibility of package with cluster");
|
||||
if !skip_compatibility.unwrap_or(false) {
|
||||
if !package.mc_loader.iter().any(|v| cluster.mc_loader.compatible_with(v)) {
|
||||
return Err(LauncherError::from(PackageError::from(IncompatiblePackageType::Loader)));
|
||||
}
|
||||
|
||||
if !package.mc_versions.iter().any(|v| cluster.mc_version.contains(v)) {
|
||||
return Err(LauncherError::from(PackageError::from(IncompatiblePackageType::McVersion)));
|
||||
}
|
||||
}
|
||||
|
||||
let dirs = Dirs::get().await?;
|
||||
let cluster_dir = dirs.clusters_dir().join(cluster.folder_name.clone());
|
||||
|
||||
tracing::trace!("hard linking package to cluster");
|
||||
let src_pkg = path_from_model(dirs, package);
|
||||
let dest_dir = cluster_dir.join(package.package_type.folder_name());
|
||||
io::create_dir_all(&dest_dir).await?;
|
||||
|
||||
let dest_pkg = dest_dir.join(package.file_name.as_str());
|
||||
tokio::fs::hard_link(src_pkg, dest_pkg).await.map_err(IOError::from)?;
|
||||
|
||||
dao::link_package_to_cluster(package, cluster).await?;
|
||||
|
||||
tracing::debug!(
|
||||
"linked package '{}' version '{}' to cluster '{}'",
|
||||
package.display_name,
|
||||
package.display_version,
|
||||
cluster.name
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn path_from_model(dirs: &Dirs, model: &packages::Model) -> std::path::PathBuf {
|
||||
join_package_file(dirs.package_dir(&model.package_type, &model.provider, &model.package_id), &model.version_id, model.file_name.as_str())
|
||||
}
|
||||
|
||||
fn join_package_file(path: impl AsRef<std::path::Path>, version_id: &str, file_name: &str) -> std::path::PathBuf {
|
||||
path.as_ref().join(format!("{version_id}-{file_name}"))
|
||||
}
|
||||
|
|
@ -308,7 +308,7 @@ impl From<ModrinthPackage> for ManagedPackage {
|
|||
fn from(value: ModrinthPackage) -> Self {
|
||||
Self {
|
||||
provider: Provider::Modrinth,
|
||||
provider_id: value.id,
|
||||
id: value.id,
|
||||
package_type: value.project_type,
|
||||
name: value.title,
|
||||
short_desc: value.description,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
use onelauncher_entity::package::Provider;
|
||||
|
||||
use crate::{api::packages::provider::ProviderExt, error::LauncherResult, store::{Core, CoreOptions}};
|
||||
use crate::{api::packages::{self, provider::ProviderExt}, error::LauncherResult, store::{Core, CoreOptions, Dirs}};
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_get_provider() -> LauncherResult<()> {
|
||||
|
|
@ -10,7 +11,7 @@ pub async fn test_get_provider() -> LauncherResult<()> {
|
|||
|
||||
let res = provider.get("oneconfig").await?;
|
||||
|
||||
assert_eq!(res.provider_id, "AibBIVmj");
|
||||
assert_eq!(res.id, "AibBIVmj");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -41,4 +42,26 @@ pub async fn test_get_versions() -> LauncherResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_download_chatting() -> LauncherResult<()> {
|
||||
Core::initialize(CoreOptions::default()).await?;
|
||||
|
||||
let provider = Provider::Modrinth;
|
||||
|
||||
let pkg = provider.get("chatting").await?;
|
||||
let ver_id = pkg.version_ids.first().expect("No version found");
|
||||
let versions = provider.get_versions(&[ver_id.clone()]).await?;
|
||||
let ver = versions.first().expect("No version found");
|
||||
|
||||
let db_model = packages::download_package(&pkg, ver, None).await;
|
||||
assert!(db_model.is_ok_and(|m| m.display_name == pkg.name));
|
||||
|
||||
let dir = Dirs::get_package_dir(&pkg.package_type, &provider, &pkg.id).await?;
|
||||
let dest = dir.join(&ver.files.first().unwrap().file_name);
|
||||
|
||||
assert!(dest.exists(), "File does not exist: {dest:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Add more tests
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ pub enum LauncherError {
|
|||
#[error(transparent)]
|
||||
ProcessError(#[from] crate::store::processes::ProcessError),
|
||||
#[error(transparent)]
|
||||
PackageError(#[from] crate::api::packages::PackageError),
|
||||
#[error(transparent)]
|
||||
DaoError(#[from] DaoError),
|
||||
|
||||
#[error(transparent)]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use onelauncher_entity::package::{PackageType, Provider};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use crate::{utils, LauncherResult};
|
||||
|
|
@ -46,19 +47,19 @@ impl Dirs {
|
|||
}
|
||||
|
||||
macro_rules! dirs_impl {
|
||||
($($name:ident => |$self:ident| $path:expr),*$(,)?) => {
|
||||
($($name:ident = |$self:ident$(, $param:ident : $type:ty )*| $path:expr),*$(,)?) => {
|
||||
paste::paste! {
|
||||
impl Dirs {
|
||||
$(
|
||||
#[must_use]
|
||||
pub fn $name(&self) -> PathBuf {
|
||||
pub fn $name(&self $(, $param: $type)*) -> PathBuf {
|
||||
let $self = self;
|
||||
$path
|
||||
}
|
||||
|
||||
pub async fn [<get _ $name>]() -> LauncherResult<PathBuf> {
|
||||
pub async fn [<get _ $name>]($($param: $type),*) -> LauncherResult<PathBuf> {
|
||||
Dirs::get().await.map(|$self| {
|
||||
$self.$name()
|
||||
$self.$name($($param),*)
|
||||
})
|
||||
}
|
||||
)*
|
||||
|
|
@ -68,22 +69,30 @@ macro_rules! dirs_impl {
|
|||
}
|
||||
|
||||
dirs_impl! {
|
||||
db_file => |this| this.base_dir.join("user_data.db"),
|
||||
settings_file => |this| this.base_dir.join("settings.json"),
|
||||
auth_file => |this| this.base_dir.join("auth.json"),
|
||||
db_file = |this| this.base_dir.join("user_data.db"),
|
||||
settings_file = |this| this.base_dir.join("settings.json"),
|
||||
auth_file = |this| this.base_dir.join("auth.json"),
|
||||
|
||||
launcher_logs_dir => |this| this.base_dir.join("logs"),
|
||||
clusters_dir => |this| this.base_dir.join("clusters"),
|
||||
metadata_dir => |this| this.base_dir.join("metadata"),
|
||||
launcher_logs_dir = |this| this.base_dir.join("logs"),
|
||||
clusters_dir = |this| this.base_dir.join("clusters"),
|
||||
metadata_dir = |this| this.base_dir.join("metadata"),
|
||||
|
||||
versions_dir => |this| this.metadata_dir().join("versions"),
|
||||
libraries_dir => |this| this.metadata_dir().join("libraries"),
|
||||
natives_dir => |this| this.metadata_dir().join("natives"),
|
||||
java_dir => |this| this.metadata_dir().join("java"),
|
||||
caches_dir => |this| this.metadata_dir().join("caches"),
|
||||
versions_dir = |this| this.metadata_dir().join("versions"),
|
||||
libraries_dir = |this| this.metadata_dir().join("libraries"),
|
||||
natives_dir = |this| this.metadata_dir().join("natives"),
|
||||
java_dir = |this| this.metadata_dir().join("java"),
|
||||
caches_dir = |this| this.metadata_dir().join("caches"),
|
||||
packages_dir = |this| this.metadata_dir().join("packages"),
|
||||
|
||||
assets_dir => |this| this.metadata_dir().join("assets"),
|
||||
legacy_assets_dir => |this| this.metadata_dir().join("resources"),
|
||||
assets_index_dir => |this| this.assets_dir().join("indexes"),
|
||||
assets_object_dir => |this| this.assets_dir().join("objects"),
|
||||
}
|
||||
assets_dir = |this| this.metadata_dir().join("assets"),
|
||||
legacy_assets_dir = |this| this.metadata_dir().join("resources"),
|
||||
assets_index_dir = |this| this.assets_dir().join("indexes"),
|
||||
assets_object_dir = |this| this.assets_dir().join("objects"),
|
||||
|
||||
package_dir = |this, package_type: &PackageType, provider: &Provider, project_id: &str| {
|
||||
this.packages_dir()
|
||||
.join(package_type.folder_name())
|
||||
.join(provider.name())
|
||||
.join(project_id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ pub async fn fetch_advanced(
|
|||
}
|
||||
}
|
||||
|
||||
tracing::debug!("finished downloading {url}");
|
||||
tracing::debug!("finished fetching {url}");
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
|
|
|
|||
40
packages/core/src/utils/icon.rs
Normal file
40
packages/core/src/utils/icon.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use onelauncher_entity::icon::{Icon, IconType};
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::{error::LauncherResult, store::Dirs};
|
||||
|
||||
use super::{crypto::HashAlgorithm, http, io};
|
||||
|
||||
/// Caches a icon on the filesystem. Name is the equivalent to the sha1 hash of the icon.
|
||||
pub async fn cache_icon(icon: &Icon) -> LauncherResult<Icon> {
|
||||
Ok(match icon.get_type() {
|
||||
IconType::Cache => icon.clone(),
|
||||
IconType::Path => {
|
||||
let bytes = io::read(&**icon).await?;
|
||||
cache_icon_bytes(&bytes).await?
|
||||
},
|
||||
IconType::Url => {
|
||||
let bytes = http::fetch(Method::GET, icon).await?;
|
||||
cache_icon_bytes(&bytes).await?
|
||||
},
|
||||
IconType::Unknown => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"icon type is unknown, cannot cache icon: {}",
|
||||
icon
|
||||
).into());
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Caches a icon bytes on the filesystem. Name is the equivalent to the sha1 hash of the icon.
|
||||
pub async fn cache_icon_bytes(bytes: &[u8]) -> LauncherResult<Icon> {
|
||||
let hash = HashAlgorithm::Sha1.hash(bytes).await?;
|
||||
|
||||
let dir = Dirs::get_caches_dir().await?.join(&hash[0..2]);
|
||||
io::create_dir_all(&dir).await?;
|
||||
|
||||
let path = dir.join(&hash);
|
||||
io::write(&path, bytes).await?;
|
||||
|
||||
Ok(Icon::from_hash(hash))
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod crypto;
|
||||
pub mod os_ext;
|
||||
pub mod http;
|
||||
pub mod icon;
|
||||
pub mod io;
|
||||
pub mod pagination;
|
||||
|
|
@ -13,8 +13,25 @@ impl Nullable for Icon {
|
|||
}
|
||||
|
||||
impl Icon {
|
||||
pub fn new(icon: String) -> Self {
|
||||
Self(icon)
|
||||
pub fn from_hash(hash: String) -> Self {
|
||||
Self(format!("cache://{hash}"))
|
||||
}
|
||||
|
||||
pub fn try_from_path(path: impl AsRef<std::path::Path>) -> Option<Self> {
|
||||
let path = path.as_ref();
|
||||
if !path.is_absolute() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self(format!("file://{}", path.display())))
|
||||
}
|
||||
|
||||
pub fn try_from_url(url: url::Url) -> Option<Self> {
|
||||
if url.scheme() != "http" && url.scheme() != "https" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self(url.as_str().to_string()))
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> IconType {
|
||||
|
|
@ -22,29 +39,36 @@ impl Icon {
|
|||
IconType::Path
|
||||
} else if self.is_url() {
|
||||
IconType::Url
|
||||
} else if self.is_cached() {
|
||||
IconType::Cache
|
||||
} else {
|
||||
IconType::Stored
|
||||
IconType::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the icon is a path to a file.
|
||||
pub fn is_path(&self) -> bool {
|
||||
self.0.starts_with("file://")
|
||||
}
|
||||
|
||||
/// Returns true if the icon is a HTTP URL.
|
||||
pub fn is_url(&self) -> bool {
|
||||
self.0.starts_with("http://") || self.0.starts_with("https://")
|
||||
}
|
||||
|
||||
pub fn is_stored(&self) -> bool {
|
||||
!self.is_path() && !self.is_url()
|
||||
/// Returns true if the icon is a cached icon. Relative to the cache directory.
|
||||
pub fn is_cached(&self) -> bool {
|
||||
self.0.starts_with("cache://")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum IconType {
|
||||
#[default]
|
||||
Unknown,
|
||||
Path,
|
||||
Url,
|
||||
Stored,
|
||||
Cache,
|
||||
}
|
||||
|
||||
impl Display for IconType {
|
||||
|
|
@ -63,7 +87,7 @@ impl Deref for Icon {
|
|||
|
||||
impl From<String> for Icon {
|
||||
fn from(s: String) -> Self {
|
||||
Self::new(s)
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +95,7 @@ impl FromStr for Icon {
|
|||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::new(s.to_string()))
|
||||
Ok(Self(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,17 @@ impl GameLoader {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn compatible_with(&self, other: &Self) -> bool {
|
||||
match self {
|
||||
Self::Vanilla => matches!(other, Self::Vanilla),
|
||||
Self::Forge => matches!(other, Self::Forge),
|
||||
Self::NeoForge => matches!(other, Self::NeoForge),
|
||||
Self::Quilt => matches!(other, Self::Quilt | Self::Fabric),
|
||||
Self::Fabric => matches!(other, Self::Fabric),
|
||||
Self::LegacyFabric => matches!(other, Self::LegacyFabric),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl TryFrom<String> for GameLoader {
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@ pub mod loader;
|
|||
pub mod package;
|
||||
pub mod resolution;
|
||||
pub mod settings;
|
||||
pub mod utility;
|
||||
|
|
|
|||
68
packages/entity/src/domain/utility.rs
Normal file
68
packages/entity/src/domain/utility.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct DbVec<T>(pub Vec<T>);
|
||||
|
||||
impl<T> Deref for DbVec<T> {
|
||||
type Target = Vec<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Vec<T>> for DbVec<T> {
|
||||
fn from(v: Vec<T>) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> sea_orm::TryGetableFromJson for DbVec<T> where for<'de> T: Deserialize<'de> {}
|
||||
|
||||
impl<T> std::convert::From<DbVec<T>> for sea_orm::Value
|
||||
where
|
||||
DbVec<T>: Serialize,
|
||||
{
|
||||
fn from(source: DbVec<T>) -> Self {
|
||||
sea_orm::Value::Json(
|
||||
serde_json::to_value(&source)
|
||||
.ok()
|
||||
.map(std::boxed::Box::new),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> sea_orm::sea_query::ValueType for DbVec<T>
|
||||
where
|
||||
DbVec<T>: DeserializeOwned,
|
||||
{
|
||||
fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
|
||||
match v {
|
||||
sea_orm::Value::Json(Some(json)) => {
|
||||
Ok(serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?)
|
||||
}
|
||||
_ => Err(sea_orm::sea_query::ValueTypeErr),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name() -> String {
|
||||
stringify!(#ident).to_owned()
|
||||
}
|
||||
|
||||
fn array_type() -> sea_orm::sea_query::ArrayType {
|
||||
sea_orm::sea_query::ArrayType::Json
|
||||
}
|
||||
|
||||
fn column_type() -> sea_orm::sea_query::ColumnType {
|
||||
sea_orm::sea_query::ColumnType::Json
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> sea_orm::sea_query::Nullable for DbVec<T> {
|
||||
fn null() -> sea_orm::Value {
|
||||
sea_orm::Value::Json(None)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize};
|
|||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i64,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
|||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub cluster_id: i64,
|
||||
#[sea_orm(primary_key, auto_increment = false, column_type = "Text")]
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub package_hash: String,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,25 +9,22 @@ use crate::{cluster_stage::ClusterStage, icon::Icon, loader::GameLoader};
|
|||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i64,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub path: String,
|
||||
pub folder_name: String,
|
||||
pub stage: ClusterStage,
|
||||
pub created_at: i64,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub group_id: Option<i64>,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub name: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub mc_version: String,
|
||||
pub mc_loader: GameLoader,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub mc_loader_version: Option<String>,
|
||||
pub last_played: Option<i64>,
|
||||
pub last_played: Option<DateTimeUtc>,
|
||||
pub overall_played: Option<i64>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub icon_url: Option<Icon>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub setting_profile_name: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub linked_pack_id: Option<String>,
|
||||
pub linked_pack_version: Option<String>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,9 @@ pub struct Model {
|
|||
#[sea_orm(primary_key)]
|
||||
pub id: i64,
|
||||
pub major_version: u32,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub vendor_name: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub absolute_path: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub full_version: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub arch: String,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,30 +3,26 @@
|
|||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{icon::Icon, package::{PackageType, Provider}};
|
||||
use crate::{icon::Icon, loader::GameLoader, package::{PackageType, Provider}, utility::DbVec};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "packages")]
|
||||
#[onelauncher_macro::specta]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false, column_type = "Text")]
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub hash: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub file_name: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub version_id: String,
|
||||
pub published_at: DateTimeUtc,
|
||||
pub display_name: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub display_version: String,
|
||||
pub project_type_id: PackageType,
|
||||
pub provider_id: Provider,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub provider_version: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub mc_versions: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub mc_loader: String,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub icon_url: Option<Icon>,
|
||||
pub package_type: PackageType,
|
||||
pub provider: Provider,
|
||||
pub package_id: String,
|
||||
pub mc_versions: DbVec<String>,
|
||||
pub mc_loader: DbVec<GameLoader>,
|
||||
#[sea_orm(nullable)]
|
||||
pub icon: Option<Icon>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
|
|
|||
|
|
@ -15,21 +15,21 @@ pub struct Model {
|
|||
#[merge(skip)]
|
||||
pub name: String,
|
||||
pub java_id: Option<i64>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub res: Option<Resolution>,
|
||||
pub force_fullscreen: Option<bool>,
|
||||
pub mem_max: Option<u32>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub launch_args: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub launch_env: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub hook_pre: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub hook_wrapper: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub hook_post: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
#[sea_orm(nullable)]
|
||||
pub os_extra: Option<SettingsOsExtra>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,3 +19,4 @@ path = "src/lib.rs"
|
|||
tokio = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
sea-orm-migration = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
create table cluster_groups (
|
||||
id integer not null,
|
||||
name text not null,
|
||||
constraint cluster_groups_pk primary key (id autoincrement)
|
||||
);
|
||||
|
||||
create table java_versions (
|
||||
id integer not null,
|
||||
major_version integer not null,
|
||||
vendor_name text not null,
|
||||
absolute_path text not null,
|
||||
full_version text not null,
|
||||
arch text not null,
|
||||
constraint java_versions_pk primary key (id autoincrement)
|
||||
);
|
||||
|
||||
create index java_versions_major_version_idx on java_versions (major_version);
|
||||
|
||||
create table packages (
|
||||
hash text not null,
|
||||
file_name text not null,
|
||||
display_name text not null,
|
||||
display_version text not null,
|
||||
project_type_id integer not null,
|
||||
provider_id integer not null,
|
||||
provider_version text not null,
|
||||
mc_versions text not null,
|
||||
mc_loader text not null,
|
||||
icon_url text,
|
||||
constraint packages_pk primary key (hash)
|
||||
);
|
||||
|
||||
create index packages_project_type_id_idx on packages (project_type_id);
|
||||
create index packages_provider_id_idx on packages (provider_id);
|
||||
create index packages_mc_loader_mc_versions_idx on packages (mc_loader, mc_versions);
|
||||
|
||||
create table setting_profiles (
|
||||
name text not null,
|
||||
java_id integer,
|
||||
res json_text,
|
||||
force_fullscreen boolean,
|
||||
mem_max integer,
|
||||
launch_args text,
|
||||
launch_env text,
|
||||
hook_pre text,
|
||||
hook_wrapper text,
|
||||
hook_post text,
|
||||
os_extra json_text,
|
||||
constraint setting_profiles_pk primary key (name),
|
||||
constraint setting_profiles_java_versions_id_fk foreign key (java_id) references java_versions(id)
|
||||
);
|
||||
|
||||
create table clusters (
|
||||
id integer not null,
|
||||
path text not null,
|
||||
stage integer default (0) not null,
|
||||
created_at integer default (unixepoch()) not null,
|
||||
group_id integer,
|
||||
name text not null,
|
||||
mc_version text not null,
|
||||
mc_loader integer not null,
|
||||
mc_loader_version text,
|
||||
last_played integer,
|
||||
overall_played integer,
|
||||
icon_url text,
|
||||
setting_profile_name text,
|
||||
linked_pack_id text,
|
||||
linked_pack_version text,
|
||||
constraint clusters_pk primary key (id autoincrement),
|
||||
constraint clusters_cluster_groups_id_fk foreign key (group_id) references cluster_groups(id),
|
||||
constraint clusters_setting_profiles_name_fk foreign key (setting_profile_name) references setting_profiles(name)
|
||||
);
|
||||
|
||||
create index clusters_path_idx on clusters (path);
|
||||
|
||||
create table cluster_packages (
|
||||
cluster_id integer not null,
|
||||
package_hash text not null,
|
||||
constraint cluster_packages_pk primary key (cluster_id, package_hash),
|
||||
constraint cluster_packages_clusters_id_fk foreign key (cluster_id) references clusters(id),
|
||||
constraint cluster_packages_packages_hash_fk foreign key (package_hash) references packages(hash)
|
||||
);
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
use chrono::Utc;
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
|
|
@ -6,12 +7,270 @@ pub struct Migration;
|
|||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.get_connection().execute_unprepared(include_str!("./init.sql")).await?;
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ClusterGroups::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(ClusterGroups::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ClusterGroups::Name).text().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(JavaVersions::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(JavaVersions::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(JavaVersions::MajorVersion).integer().not_null())
|
||||
.col(ColumnDef::new(JavaVersions::VendorName).text().not_null())
|
||||
.col(ColumnDef::new(JavaVersions::AbsolutePath).text().not_null())
|
||||
.col(ColumnDef::new(JavaVersions::FullVersion).text().not_null())
|
||||
.col(ColumnDef::new(JavaVersions::Arch).text().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("java_versions_major_version_idx")
|
||||
.table(JavaVersions::Table)
|
||||
.col(JavaVersions::MajorVersion)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Packages::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Packages::Hash).text().not_null().primary_key())
|
||||
.col(ColumnDef::new(Packages::FileName).text().not_null())
|
||||
.col(ColumnDef::new(Packages::VersionId).text().not_null())
|
||||
.col(ColumnDef::new(Packages::PublishedAt).date_time().not_null())
|
||||
.col(ColumnDef::new(Packages::DisplayName).text().not_null())
|
||||
.col(ColumnDef::new(Packages::DisplayVersion).text().not_null())
|
||||
.col(ColumnDef::new(Packages::PackageType).integer().not_null())
|
||||
.col(ColumnDef::new(Packages::Provider).integer().not_null())
|
||||
.col(ColumnDef::new(Packages::PackageId).text().not_null())
|
||||
.col(ColumnDef::new(Packages::McVersions).json().not_null())
|
||||
.col(ColumnDef::new(Packages::McLoader).integer().not_null())
|
||||
.col(ColumnDef::new(Packages::Icon).text().null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("packages_mc_loader_mc_versions_idx")
|
||||
.table(Packages::Table)
|
||||
.col(Packages::PackageType)
|
||||
.col(Packages::McLoader)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(SettingProfiles::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(SettingProfiles::Name).text().not_null().primary_key())
|
||||
.col(ColumnDef::new(SettingProfiles::JavaId).integer().null())
|
||||
.col(ColumnDef::new(SettingProfiles::Res).json().null())
|
||||
.col(ColumnDef::new(SettingProfiles::ForceFullscreen).boolean().null())
|
||||
.col(ColumnDef::new(SettingProfiles::MemMax).integer().null())
|
||||
.col(ColumnDef::new(SettingProfiles::LaunchArgs).text().null())
|
||||
.col(ColumnDef::new(SettingProfiles::LaunchEnv).text().null())
|
||||
.col(ColumnDef::new(SettingProfiles::HookPre).text().null())
|
||||
.col(ColumnDef::new(SettingProfiles::HookWrapper).text().null())
|
||||
.col(ColumnDef::new(SettingProfiles::HookPost).text().null())
|
||||
.col(ColumnDef::new(SettingProfiles::OsExtra).json().null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(SettingProfiles::Table, SettingProfiles::JavaId)
|
||||
.to(JavaVersions::Table, JavaVersions::Id),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Clusters::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Clusters::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Clusters::FolderName).text().not_null())
|
||||
.col(ColumnDef::new(Clusters::Stage).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Clusters::CreatedAt).date_time().not_null().default(Utc::now()))
|
||||
.col(ColumnDef::new(Clusters::GroupId).integer().null())
|
||||
.col(ColumnDef::new(Clusters::Name).text().not_null())
|
||||
.col(ColumnDef::new(Clusters::McVersion).text().not_null())
|
||||
.col(ColumnDef::new(Clusters::McLoader).integer().not_null())
|
||||
.col(ColumnDef::new(Clusters::McLoaderVersion).text().null())
|
||||
.col(ColumnDef::new(Clusters::LastPlayed).date_time().null())
|
||||
.col(ColumnDef::new(Clusters::OverallPlayed).integer().null())
|
||||
.col(ColumnDef::new(Clusters::IconUrl).text().null())
|
||||
.col(ColumnDef::new(Clusters::SettingProfileName).text().null())
|
||||
.col(ColumnDef::new(Clusters::LinkedPackId).text().null())
|
||||
.col(ColumnDef::new(Clusters::LinkedPackVersion).text().null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(Clusters::Table, Clusters::GroupId)
|
||||
.to(ClusterGroups::Table, ClusterGroups::Id),
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(Clusters::Table, Clusters::SettingProfileName)
|
||||
.to(SettingProfiles::Table, SettingProfiles::Name),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("clusters_path_idx")
|
||||
.table(Clusters::Table)
|
||||
.col(Clusters::FolderName)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ClusterPackages::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(ClusterPackages::ClusterId).integer().not_null())
|
||||
.col(ColumnDef::new(ClusterPackages::PackageHash).text().not_null())
|
||||
.primary_key(
|
||||
Index::create()
|
||||
.col(ClusterPackages::ClusterId)
|
||||
.col(ClusterPackages::PackageHash)
|
||||
.primary(),
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(ClusterPackages::Table, ClusterPackages::ClusterId)
|
||||
.to(Clusters::Table, Clusters::Id),
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(ClusterPackages::Table, ClusterPackages::PackageHash)
|
||||
.to(Packages::Table, Packages::Hash),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
unreachable!("Down migration is not supported for init migration");
|
||||
Err(DbErr::Migration("no rollback available".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum ClusterGroups {
|
||||
Table,
|
||||
Id,
|
||||
Name,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum JavaVersions {
|
||||
Table,
|
||||
Id,
|
||||
MajorVersion,
|
||||
VendorName,
|
||||
AbsolutePath,
|
||||
FullVersion,
|
||||
Arch,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum Packages {
|
||||
Table,
|
||||
Hash,
|
||||
FileName,
|
||||
VersionId,
|
||||
PublishedAt,
|
||||
DisplayName,
|
||||
DisplayVersion,
|
||||
PackageType,
|
||||
Provider,
|
||||
PackageId,
|
||||
McVersions,
|
||||
McLoader,
|
||||
Icon,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum SettingProfiles {
|
||||
Table,
|
||||
Name,
|
||||
JavaId,
|
||||
Res,
|
||||
ForceFullscreen,
|
||||
MemMax,
|
||||
LaunchArgs,
|
||||
LaunchEnv,
|
||||
HookPre,
|
||||
HookWrapper,
|
||||
HookPost,
|
||||
OsExtra,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum Clusters {
|
||||
Table,
|
||||
Id,
|
||||
FolderName,
|
||||
Stage,
|
||||
CreatedAt,
|
||||
GroupId,
|
||||
Name,
|
||||
McVersion,
|
||||
McLoader,
|
||||
McLoaderVersion,
|
||||
LastPlayed,
|
||||
OverallPlayed,
|
||||
IconUrl,
|
||||
SettingProfileName,
|
||||
LinkedPackId,
|
||||
LinkedPackVersion,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum ClusterPackages {
|
||||
Table,
|
||||
ClusterId,
|
||||
PackageHash,
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue