Skip to content

Commit 1697331

Browse files
Add installer infrastructure and test bpx installer
This commit contains two major features: Traits that define the behavior of filesystem operations and installer functionality, and an early implementation of the bepinex package installer which uses this functionality. New traits: - TrackedFs, which defines the API by which the installer will interact with the filesystem. - PackageInstaller, which defines the functionality and types that an installer implementee must define / consume to function.
1 parent 5f0e8d4 commit 1697331

File tree

5 files changed

+250
-3
lines changed

5 files changed

+250
-3
lines changed

src/package/install/bepinex.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use std::{fs, path::Path};
2+
3+
use walkdir::WalkDir;
4+
5+
use crate::error::Error;
6+
use crate::package::install::tracked::TrackedFs;
7+
use crate::package::install::PackageInstaller;
8+
use crate::ts::package_reference::PackageReference;
9+
10+
pub struct BpxInstaller<T: TrackedFs> {
11+
fs: T,
12+
}
13+
14+
impl<T: TrackedFs> BpxInstaller<T> {
15+
pub fn new(fs: T) -> Self {
16+
BpxInstaller { fs }
17+
}
18+
}
19+
20+
impl<T: TrackedFs> PackageInstaller<T> for BpxInstaller<T> {
21+
async fn install_package(
22+
&self,
23+
_package: &PackageReference,
24+
_package_deps: &[PackageReference],
25+
package_dir: &Path,
26+
state_dir: &Path,
27+
_staging_dir: &Path,
28+
game_dir: &Path,
29+
_is_modloader: bool,
30+
) -> Result<(), Error> {
31+
// Figure out the root bepinex directory. This should, in theory, always be the folder
32+
// that contains the winhttp.dll binary.
33+
let bepinex_root = WalkDir::new(package_dir)
34+
.into_iter()
35+
.filter_map(|x| x.ok())
36+
.filter(|x| x.path().is_file())
37+
.find(|x| x.path().file_name().unwrap() == "winhttp.dll")
38+
.expect("Failed to find winhttp.dll within BepInEx directory.");
39+
let bepinex_root = bepinex_root.path().parent().unwrap();
40+
41+
let bep_dir = bepinex_root.join("BepInEx");
42+
let bep_dst = state_dir.join("BepInEx");
43+
44+
// self.fs.dir_copy(&bep_dir, &bep_dst).await.unwrap();
45+
46+
// Install top-level doorstop files.
47+
let files = fs::read_dir(bepinex_root)
48+
.unwrap()
49+
.filter_map(|x| x.ok())
50+
.filter(|x| x.path().is_file());
51+
52+
for file in files {
53+
let dest = game_dir.join(file.path().file_name().unwrap());
54+
// self.fs.file_copy(&file.path(), &dest, None).await?;
55+
}
56+
57+
Ok(())
58+
}
59+
60+
async fn uninstall_package(
61+
&self,
62+
_package: &PackageReference,
63+
_package_deps: &[PackageReference],
64+
_package_dir: &Path,
65+
_state_dir: &Path,
66+
_staging_dir: &Path,
67+
_game_dir: &Path,
68+
_is_modloader: bool,
69+
) -> Result<(), Error> {
70+
todo!()
71+
}
72+
}

src/package/install/mod.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,58 @@ use super::error::PackageError;
1515
use super::Package;
1616
use crate::error::Error;
1717
use crate::error::IoError;
18+
use crate::package::install::bepinex::BpxInstaller;
19+
use crate::package::install::tracked::ConcreteFs;
20+
use crate::package::install::tracked::TrackedFs;
21+
use crate::project::state::StateEntry;
22+
use crate::ts::package_reference::PackageReference;
23+
use crate::ts::v1::models::ecosystem::R2MLLoader;
1824
use crate::ui::reporter::{Progress, ProgressBarTrait, VoidProgress};
1925

2026
pub mod api;
2127
mod legacy_compat;
2228
pub mod manifest;
29+
pub mod bepinex;
30+
mod tracked;
31+
32+
pub trait PackageInstaller<T: TrackedFs> {
33+
/// Install a package into this profile.
34+
///
35+
/// `state_dir` is the directory that is "linked" to at runtime by the modloader.
36+
/// `staging_dir` is the directory that contains files that are directly installed into the game directory.
37+
async fn install_package(
38+
&self,
39+
package: &PackageReference,
40+
package_deps: &[PackageReference],
41+
package_dir: &Path,
42+
state_dir: &Path,
43+
staging_dir: &Path,
44+
game_dir: &Path,
45+
is_modloader: bool,
46+
) -> Result<(), Error>;
47+
48+
/// Uninstall a package from this profile.
49+
async fn uninstall_package(
50+
&self,
51+
package: &PackageReference,
52+
package_deps: &[PackageReference],
53+
package_dir: &Path,
54+
state_dir: &Path,
55+
staging_dir: &Path,
56+
game_dir: &Path,
57+
is_modloader: bool,
58+
) -> Result<(), Error>;
59+
60+
}
61+
62+
63+
/// Get the proper installer for the provided modloader variant.
64+
pub fn get_installer<T: TrackedFs>(ml_variant: &R2MLLoader, fs: T) -> Option<impl PackageInstaller<T>> {
65+
match ml_variant {
66+
R2MLLoader::BepInEx => Some(BpxInstaller::new(fs)),
67+
_ => None,
68+
}
69+
}
2370

2471
pub struct Installer {
2572
pub exec_path: PathBuf,
@@ -119,6 +166,14 @@ impl Installer {
119166
) -> Result<Vec<TrackedFile>, Error> {
120167
// Determine if the package is a modloader or not.
121168
let is_modloader = package.identifier.name.to_lowercase().contains("bepinex");
169+
BpxInstaller::new(ConcreteFs::new(StateEntry::default()));
170+
171+
let fs = ConcreteFs::new(StateEntry::default());
172+
let test = get_installer(&R2MLLoader::BepInEx, fs);
173+
174+
// bepinex::install_package(package.identifier.clone(), &package.dependencies, package_dir, state_dir, staging_dir, is_modloader).await;
175+
176+
panic!();
122177

123178
let request = Request::PackageInstall {
124179
is_modloader,

src/package/install/tracked.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use std::path::Path;
2+
use tokio::fs;
3+
use walkdir::WalkDir;
4+
5+
use crate::package::install::api::{FileAction, TrackedFile};
6+
use crate::project::state::{StagedFile, StateEntry};
7+
8+
use crate::error::Error;
9+
10+
pub trait TrackedFs {
11+
/// Create a new instance dedicated to tracking filesystem edits during the
12+
/// installation of the provided package.
13+
///
14+
/// This essentially creates or opens the cooresponding entry within the
15+
/// tracked_files.json file and writes any tracked fs modifications to it.
16+
fn new(state: StateEntry) -> Self;
17+
18+
/// Extract the new StateEntry from this instance.
19+
fn extract_state(self) -> StateEntry;
20+
21+
/// Copy a file from a source to a destination, overwriting it if the file
22+
/// already exists.
23+
///
24+
/// This will append (or overwrite) a FileAction::Create entry.
25+
async fn file_copy(&mut self, src: &Path, dst: &Path, stage_dst: Option<&Path>) -> Result<(), Error>;
26+
27+
/// Delete some target file.
28+
///
29+
/// If `tracked` is set this this will append a FileAction::Delete entry,
30+
/// overwriting one if it already exists for this file.
31+
async fn file_delete(&mut self, target: &Path, tracked: bool);
32+
33+
/// Recursively copy a source directory to a destination, overwriting it if
34+
/// it already exists.
35+
///
36+
/// This will append (or overwrite) a FileAction::Create entry for each file
37+
/// copied while recursing.
38+
async fn dir_copy(&mut self, src: &Path, dst: &Path) -> Result<(), Error>;
39+
40+
/// Recursively delete some target directory.
41+
///
42+
/// If `tracked` if set then this will append a FileAction::Delete entry
43+
/// for each file deleted while recursing, otherwise matching entries are
44+
/// deleted.
45+
async fn dir_delete(&mut self, target: &Path, tracked: bool);
46+
}
47+
48+
#[derive(Debug)]
49+
pub struct ConcreteFs {
50+
state: StateEntry,
51+
}
52+
53+
impl TrackedFs for ConcreteFs {
54+
fn new(state: StateEntry) -> Self {
55+
ConcreteFs {
56+
state
57+
}
58+
}
59+
60+
fn extract_state(self) -> StateEntry {
61+
self.state
62+
}
63+
64+
async fn file_copy(&mut self, src: &Path, dst: &Path, stage_dst: Option<&Path>) -> Result<(), Error> {
65+
fs::copy(src, dst).await?;
66+
let tracked = TrackedFile { action: FileAction::Create, path: dst.to_path_buf(), context: None };
67+
68+
if let Some(stage_dst) = stage_dst {
69+
let mut staged = StagedFile::new(tracked)?;
70+
staged.dest.push(stage_dst.to_path_buf());
71+
self.state.add_staged(staged, false);
72+
} else {
73+
self.state.add_linked(tracked, false);
74+
}
75+
76+
Ok(())
77+
}
78+
79+
async fn file_delete(&mut self, target: &Path, tracked: bool) {
80+
todo!()
81+
}
82+
83+
async fn dir_copy(&mut self, src: &Path, dst: &Path) -> Result<(), Error> {
84+
let files = WalkDir::new(&src)
85+
.into_iter()
86+
.filter_map(|e| e.ok())
87+
.filter(|x| x.path().is_file());
88+
89+
for file in files {
90+
let dest = dst.join(file.path().strip_prefix(&src).unwrap());
91+
let dest_parent = dest.parent().unwrap();
92+
93+
if !dest_parent.is_dir() {
94+
fs::create_dir_all(dest_parent).await?;
95+
}
96+
97+
self.file_copy(file.path(), &dest, None).await?;
98+
}
99+
100+
Ok(())
101+
}
102+
103+
async fn dir_delete(&mut self, target: &Path, tracked: bool) {
104+
todo!()
105+
}
106+
}

src/project/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ pub mod error;
3434
pub mod lock;
3535
pub mod manifest;
3636
pub mod overrides;
37-
mod publish;
38-
mod state;
37+
pub mod publish;
38+
pub mod state;
3939

4040
pub enum ProjectKind {
4141
Dev(ProjectOverrides),

src/project/state.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,26 @@ impl StagedFile {
3737
}
3838
}
3939

40-
#[derive(Serialize, Deserialize, Default)]
40+
#[derive(Serialize, Deserialize, Default, Debug)]
4141
pub struct StateEntry {
4242
pub staged: Vec<StagedFile>,
4343
pub linked: Vec<TrackedFile>,
4444
}
4545

46+
impl StateEntry {
47+
/// Add a new staged file. If overwrite is set then already existing
48+
/// entries with the same path will be replaced.
49+
pub fn add_staged(&mut self, file: StagedFile, overwrite: bool) {
50+
todo!()
51+
}
52+
53+
/// Add a new linked file. If overwrite is set then already existing
54+
/// entries with the same path will be replaced.
55+
pub fn add_linked(&mut self, file: TrackedFile, overwrite: bool) {
56+
todo!()
57+
}
58+
}
59+
4660
#[derive(Serialize, Deserialize, Default)]
4761
pub struct StateFile {
4862
pub state: HashMap<PackageReference, StateEntry>,

0 commit comments

Comments
 (0)