summaryrefslogtreecommitdiffstats
path: root/src/launcher.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/launcher.rs')
-rw-r--r--src/launcher.rs150
1 files changed, 138 insertions, 12 deletions
diff --git a/src/launcher.rs b/src/launcher.rs
index 25cdcc4..f712e69 100644
--- a/src/launcher.rs
+++ b/src/launcher.rs
@@ -7,16 +7,21 @@ mod rules;
mod assets;
use std::borrow::Cow;
+use std::cmp::min;
use std::collections::HashMap;
use std::env::consts::{ARCH, OS};
use std::error::Error;
use std::ffi::OsStr;
use std::fmt::{Display, Formatter};
+use std::fs::FileType;
use std::io::ErrorKind;
use std::io::ErrorKind::AlreadyExists;
use std::path::{Component, Path, PathBuf};
+use std::process;
+use std::time::{SystemTime, UNIX_EPOCH};
+use chrono::NaiveDateTime;
use const_format::formatcp;
-use futures::TryStreamExt;
+use futures::{stream, FutureExt, StreamExt, TryStreamExt};
use log::{debug, info, warn};
use serde::{Deserialize, Serialize};
use sha1_smol::Sha1;
@@ -24,10 +29,12 @@ use sysinfo::System;
use tokio::{fs, io};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
+use tokio_stream::wrappers::ReadDirStream;
+use zip::ZipArchive;
use download::{MultiDownloader, VerifiedDownload};
use rules::{CompatCheck, IncompatibleError};
use version::{VersionList, VersionResolveError, VersionResult};
-use crate::version::{Logging, Library, OSRestriction, OperatingSystem, DownloadType, DownloadInfo};
+use crate::version::{Logging, Library, OSRestriction, OperatingSystem, DownloadType, DownloadInfo, LibraryExtractRule};
pub use profile::{Instance, Profile};
use crate::launcher::assets::{AssetError, AssetRepository};
@@ -125,7 +132,8 @@ struct SystemInfo {
}
struct LibraryRepository {
- home: PathBuf
+ home: PathBuf,
+ natives: PathBuf
}
pub struct Launcher {
@@ -154,6 +162,7 @@ pub enum LaunchError {
LibraryDirError(PathBuf, io::Error),
LibraryVerifyError(FileVerifyError),
LibraryDownloadError,
+ LibraryExtractZipError(PathBuf, zip::result::ZipError),
// ensure file errors
MissingURL,
@@ -180,6 +189,7 @@ impl Display for LaunchError {
LaunchError::LibraryDirError(path, e) => write!(f, "failed to create library directory {}: {}", path.display(), e),
LaunchError::LibraryVerifyError(e) => write!(f, "failed to verify library: {}", e),
LaunchError::LibraryDownloadError => f.write_str("library download failed (see above logs for details)"), // TODO: booo this sucks
+ LaunchError::LibraryExtractZipError(path, e) => write!(f, "library extract zip error ({}): {e}", path.display()),
LaunchError::MissingURL => f.write_str("cannot download required file, URL is missing"),
LaunchError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error),
LaunchError::Offline => f.write_str("cannot download file in offline mode"),
@@ -201,6 +211,7 @@ impl Error for LaunchError {
LaunchError::IncompatibleVersion(e) => Some(e),
LaunchError::LibraryDirError(_, e) => Some(e),
LaunchError::LibraryVerifyError(e) => Some(e),
+ LaunchError::LibraryExtractZipError(_, e) => Some(e),
LaunchError::IO { error: e, .. } => Some(e),
LaunchError::Download { error: e, .. } => Some(e),
LaunchError::Integrity(e) => Some(e),
@@ -245,12 +256,24 @@ impl Launcher {
system_info: SystemInfo::new(),
libraries: LibraryRepository {
home: home.join("libraries"),
+ natives: home.join("natives")
},
assets: AssetRepository::new(online, &assets_path).await?,
home
})
}
+ fn choose_lib_classifier<'lib>(&self, lib: &'lib Library) -> Option<&'lib str> {
+ lib.natives.as_ref().map_or(None, |n| n.get(&self.system_info.os)).map(|s| s.as_str())
+ }
+
+ fn create_extract_job<'lib>(&self, lib: &'lib Library) -> Option<LibraryExtractJob<'lib>> {
+ Some(LibraryExtractJob {
+ source: LibraryRepository::get_artifact_path(lib.name.as_str(), self.choose_lib_classifier(lib))?,
+ rule: lib.extract.as_ref()
+ })
+ }
+
async fn ensure_file(&self, path: &Path, dlinfo: &DownloadInfo) -> Result<(), LaunchError> {
// verify the file
match util::verify_file(path, dlinfo.size, dlinfo.sha1).await {
@@ -387,7 +410,7 @@ impl Launcher {
* - (done) download them
* - (done) (if offline mode, explode)
* - if virtual or resource-mapped, copy (or perhaps hardlink? that would be cool)
- * - the actual client jar
+ * - (done) the actual client jar
* - (done) check integriddy and download if needed
* - (done) (explode if offline mode)
* - launch the game
@@ -418,7 +441,7 @@ impl Launcher {
}
libs.push(lib);
- if let Some(dl) = self.libraries.create_download(lib, self.system_info.os) {
+ if let Some(dl) = self.libraries.create_download(lib, self.choose_lib_classifier(lib)) {
dl.make_dirs().await.map_err(|e| LaunchError::LibraryDirError(dl.get_path().to_path_buf(), e))?;
downloads.push(dl);
}
@@ -477,27 +500,45 @@ impl Launcher {
client_jar_path = None;
}
+ // clean up old natives
+ let nnatives = self.libraries.clean_old_natives().await?;
+ info!("Cleaned up {} old natives directories.", nnatives);
+
+ // extract natives
+
//todo!()
Ok(())
}
}
-#[derive(Debug, Clone)]
+#[derive(Debug)]
enum LibraryError {
InvalidName(String),
- IOError(ErrorKind)
+ IO { what: &'static str, error: io::Error }
}
impl Display for LibraryError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
LibraryError::InvalidName(name) => write!(f, "invalid name: {name}"),
- LibraryError::IOError(e) => write!(f, "io error reading library: {e}"),
+ LibraryError::IO { what, error } => write!(f, "library i/o error ({what}): {error}"),
+ }
+ }
+}
+
+impl Error for LibraryError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ LibraryError::IO { error, .. } => Some(error),
+ _ => None
}
}
}
-impl Error for LibraryError {}
+struct LibraryExtractJob<'lib> {
+ source: PathBuf,
+ rule: Option<&'lib LibraryExtractRule>
+}
const ARCH_BITS: &'static str = formatcp!("{}", usize::BITS);
@@ -542,9 +583,7 @@ impl LibraryRepository {
Some(p)
}
- fn create_download(&self, lib: &Library, os: OperatingSystem) -> Option<VerifiedDownload> {
- let classifier = lib.natives.as_ref().map_or(None, |n| n.get(&os)).map(|s| s.as_str());
-
+ fn create_download(&self, lib: &Library, classifier: Option<&str>) -> Option<VerifiedDownload> {
if lib.url.is_some() || lib.downloads.is_none() {
// TODO: derive download URL in this situation?
warn!("BUG: Deprecated case for library {}: url present or downloads missing. The launcher does not support out-of-line checksums at this time. Not downloading this library.", lib.name);
@@ -557,6 +596,93 @@ impl LibraryRepository {
Some(VerifiedDownload::new(dlinfo.url.as_ref()?, path.as_path(), dlinfo.size, dlinfo.sha1))
}
+
+ async fn clean_old_natives(&self) -> Result<usize, LaunchError> {
+ info!("Cleaning up old natives folders...");
+
+ let boot_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() - min(System::uptime(), 7u64*24*60*60);
+
+ let readdir = match fs::read_dir(&self.natives).await {
+ Ok(readdir) => readdir,
+ Err(e) if e.kind() == ErrorKind::NotFound => return Ok(0),
+ Err(e) => return Err(LaunchError::IO { what: "reading natives directory", error: e })
+ };
+
+ ReadDirStream::new(readdir)
+ .map(|entry| Ok(async move {
+ let entry = entry.map_err(|e| LaunchError::IO { what: "reading natives entry", error: e })?;
+ let ftype = entry.file_type().await.map_err(|e| LaunchError::IO { what: "'stat'ing natives entry", error: e })?;
+
+ if !ftype.is_dir() { return Ok(false); }
+
+ let Some(ftime) = entry.file_name().to_str()
+ .map_or(None, |s| constants::NATIVES_DIR_PATTERN.captures(s))
+ .map_or(None, |c| c.get(1))
+ .map_or(None, |cap| cap.as_str().parse::<u64>().ok()) else {
+ return Ok(false);
+ };
+
+ if ftime < boot_time {
+ let path = entry.path();
+ info!("Deleting old natives directory {}", path.display());
+
+ /*fs::remove_dir_all(&path).await.map_err(|e| LaunchError::IO {
+ what: "reading natives entry",
+ error: e
+ })?;*/
+
+ return Ok(true);
+ }
+
+ Ok(false)
+ }))
+ .try_buffer_unordered(8)
+ .try_fold(0usize, |accum, res| async move {
+ match res {
+ true => Ok(accum + 1),
+ _ => Ok(accum)
+ }
+ }).await
+ }
+
+ fn extract_natives_for<'lib>(natives: &Path, job: LibraryExtractJob<'lib>) -> Result<usize, LaunchError> {
+ /*let file = File::open(&job.source).await.map_err(|e| LaunchError::IO {
+ what: "extracting library natives (open)",
+ error: e
+ })?;
+
+ let z = ZipArchive::new(file);*/
+
+ todo!()
+ }
+
+ async fn extract_natives<'lib>(&self, libs: impl IntoIterator<Item = LibraryExtractJob<'lib>>) -> Result<PathBuf, LaunchError> {
+ fs::create_dir_all(&self.natives).await.map_err(|e| LaunchError::IO {
+ what: "creating natives directory",
+ error: e
+ })?;
+
+ let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
+ let natives_dir = self.natives.join(format!("{}{}-{}", constants::NATIVES_PREFIX, time, process::id()));
+
+ // create_dir_all suppresses "AlreadyExists", but this is a fatal error here.
+ fs::create_dir(&natives_dir).await.map_err(|e| LaunchError::IO {
+ what: "creating natives directory",
+ error: e
+ })?;
+
+ let natives_dir_ref = natives_dir.as_path();
+
+ stream::iter(libs)
+ .map(|lib| Ok(async move {
+ // TODO: Self::extract_natives_for(natives_dir_ref, lib)
+ todo!() as Result<usize, _>
+ }))
+ .try_buffer_unordered(8)
+ .try_fold(0usize, |accum, num| async move {Ok(accum + num)})
+ .await.inspect(|num| debug!("Extracted {} native entries to {}", num, natives_dir_ref.display()))
+ .map(|_| natives_dir)
+ }
}
impl SystemInfo {