Skip to content

Commit

Permalink
Merge pull request #7 from AdastralGroup/develop
Browse files Browse the repository at this point in the history
v1.4.0 - QoL and stability improvements.
  • Loading branch information
ktwrd authored Jun 11, 2024
2 parents a2fcc74 + 512ea4d commit 6b95b81
Show file tree
Hide file tree
Showing 34 changed files with 995 additions and 252 deletions.
1 change: 0 additions & 1 deletion .config/beans/mod_name.txt

This file was deleted.

1 change: 0 additions & 1 deletion .config/beans/mod_name_short.txt

This file was deleted.

1 change: 0 additions & 1 deletion .config/beans/remote_url_base.txt

This file was deleted.

1 change: 0 additions & 1 deletion .config/beans/version_url.txt

This file was deleted.

26 changes: 26 additions & 0 deletions .github/workflows/compile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Compile

on:
push:
branches: [ "main", "develop" ]
pull_request:
branches: [ "main", "develop" ]

env:
CARGO_TERM_COLOR: always

jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
filename: 'beans-rs'
- os: windows-latest
filename: 'beans-rs.exe'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Build
run: cargo build --verbose
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
tag_name: ${{ github.event.inputs.tag }}
draft: false
prerelease: true
target_commitish: ${{ github.sha }}
#- name: Create Sentry release
# if: ${{ matrix.os == 'ubuntu-latest' }}
# uses: getsentry/action-release@v1
Expand Down
Binary file removed Binaries/7z.dll
Binary file not shown.
Binary file removed Binaries/7z.so
Binary file not shown.
Binary file removed Binaries/c7zip.dll
Binary file not shown.
Binary file removed Binaries/libc7zip.so
Binary file not shown.
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "beans-rs"
version = "1.4.0-rc1"
version = "1.4.0"
edition = "2021"
authors = [
"Adastral Group (https://adastral.net)",
Expand Down Expand Up @@ -31,12 +31,16 @@ clap = { version = "4.5.4", features = ["cargo"] }
bitflags = "2.5.0"
log = "0.4.21"
native-dialog = "0.7.0"
simple-logging = "2.0.2"
sentry = "0.33.0"
lazy_static = "1.4.0"
thread-id = "4.2.1"
colored = "2.1.0"
sentry-log = "0.33.0"

[target.'cfg(target_os = "windows")'.dependencies]
winconsole = "0.11.1"
winreg = "0.52.0"
dunce = "1.0.4"

[dependencies.tokio]
version = "1.37.0"
Expand Down
54 changes: 50 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,64 @@
# beans-rs
A rewrite of the original [beans](https://github.com/int-72h/ofinstaller-beans) installer, but in rust!
A Sourcemod Installer written with Rust, using the kachemak versioning system. Intended for general-purpose use, and for server owners.

Currently, everything is a 1:1 port from the python version, and things may be buggy or incomplete.
This is a complete rewrite of the original [beans](https://github.com/int-72h/ofinstaller-beans) installer, but with rust, and extended support.

`beans-rs` is licensed under `GPLv3-only`, so please respect it!

## Developing
Requirements
- Rust Toolchain (nightly, only for building)
- Recommended to use [rustup](https://rustup.rs/) to install.
- Recommended to use [rustup](https://rustup.rs/) to install.
- x86-64/AMD64 Processor ([see notes](#notes-binaries))
- **Following requirements are only required for testing**
- Steam Installed
- Source SDK Base 2013 Multiplayer ([install](steam://instal/243750))
- Source SDK Base 2013 Multiplayer ([install](steam://instal/243750))

## Contributing
When creating a PR, please please please branch off the `develop` branch. Any PRs that are created after the 7th of June 2024 that **do not** use `develop` as the base branch will be closed.

When adding a new feature (that a user will interact with), create a new file in `src/workflows/` with the name of the feature (for example, `launch.rs`). Inside of `launch.rs` you would have a struct with the name of `LaunchWorkflow`. It would look something like this;
```rust
use crate::{RunnerContext, BeansError};

#[derive(Debug, Clone)]
pub struct LaunchWorkflow {
pub context: RunnerContext
}
impl LaunchWorkflow {
pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError>
{
todo!("Logic for handling the LaunchWorkflow")
}
}
```
You would also be adding a subcommand for this ins `main.rs`. In `Launcher::run()` you would add the following ***before*** `.args` is called on `cmd`, and after the last `.subcommand()` that is used. What you would add would look like the following;
```rust
.subcommand(Command::new("launch")
.about("Launch the currently installed game")
.arg(Launcher::create_location_arg()))
```

All sub-commands must have the `--location` argument added so the end-user can specify if they have a custom location for their `sourcemods` folder.

Next you'd add a match case so `Launcher::subcommand_processor(&mut self)`, which would look like the following;
```rust
Some(("launch", install_matches)) => {
self.task_launch(install_matches).await;
}
```

Then, you'd add a new function to `Launcher`, which would actually call `LaunchWorkflow`. It would look something like the following (if there is only the `--location` argument);
```rust
pub async fn task_launch(&mut self, matches: &ArgMatches) {
self.to_location = Launcher::find_arg_sourcemods_location(&matches); // must be done when the `--launcher` argument is provided on the subcommand!
if let Err(e) = LaunchWorkflow::wizard(&mut ctx).await {
panic!("Failed to run LaunchWorkflow {:#?}", e);
} else {
logic_done(); // must be called when any flow of logic has completed.
}
}
```

## Notes
### Binaries
Expand Down
11 changes: 11 additions & 0 deletions src/appvar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mod": {
"sm_name": "open_fortress",
"short_name": "of",
"name_stylized": "Open Fortress"
},
"remote": {
"base_url": "https://of-proxy.kate.pet/",
"versions_url": "https://of-proxy.kate.pet/versions.json"
}
}
154 changes: 154 additions & 0 deletions src/appvar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use std::sync::RwLock;
use log::{debug, error, trace};
use crate::BeansError;
use lazy_static::lazy_static;

/// Default `appvar.json` to use.
pub const JSON_DATA_DEFAULT: &str = include_str!("appvar.json");
lazy_static! {
static ref JSON_DATA: RwLock<String> = RwLock::new(JSON_DATA_DEFAULT.to_string());
static ref AVD_INSTANCE: RwLock<Option<AppVarData>> = RwLock::new(None);
}

/// Going to be deprecated, use `get_appvar()` instead.
pub fn parse() -> AppVarData
{
trace!("======== IMPORTANT NOTICE ========\ncrate::appvar::parse() is going to be deprecated, use crate::appvar::get_appvar() instead!");
AppVarData::get()
}


/// Configuration for the compiled application.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AppVarData
{
#[serde(rename = "mod")]
pub mod_info: AppVarMod,
#[serde(rename = "remote")]
pub remote_info: AppVarRemote
}
impl AppVarData {
/// Parse `JSON_DATA` to AppVarData. Should only be called by `reset_appvar()`.
///
/// NOTE panics when `serde_json::from_str()` is Err, or when `JSON_DATA.read()` is Err.
/// REMARKS does not set `AVD_INSTANCE` to generated data, since this is only done by
/// `AppVarData::reset()`.
pub fn parse() -> Self {
debug!("[AppVarData::parse] trying to get JSON_DATA");
let x = JSON_DATA.read();
if let Ok(data) = x {
debug!("[AppVarData::parse] JSON_DATA= {:#?}", data);
return serde_json::from_str(&data).expect("Failed to deserialize JSON_DATA");
}
if let Err(e) = x {
panic!("[AppVarData::parse] Failed to read JSON_DATA {:#?}", e);
}
unreachable!();
}

/// Substitute values in the `source` string for what is defined in here.
pub fn sub(&self, source: String) -> String
{
source.clone()
.replace("$MOD_NAME_STYLIZED", &self.mod_info.name_stylized)
.replace("$MOD_NAME_SHORT", &self.mod_info.short_name)
.replace("$MOD_NAME", &self.mod_info.sourcemod_name)
.replace("$URL_BASE", &self.remote_info.base_url)
.replace("$URL_VERSIONS", &self.remote_info.versions_url)
}

/// Try and read the data from `AVD_INSTANCE` and return when some.
/// Otherwise, when it's none, we return `AppVarData::reset()`
///
/// NOTE this function panics when Err on `AVD_INSTANCE.read()`.
pub fn get() -> Self {
let avd_read = AVD_INSTANCE.read();
if let Ok(v) = avd_read {
let vc = v.clone();
if let Some(x) = vc {
debug!("[AppVarData::get] Instance exists in AVD_INSTANCE, so lets return that.");
return x;
}
}
else if let Err(e) = avd_read {
panic!("[AppVarData::get] Failed to read AVD_INSTANCE {:#?}", e);
}

Self::reset()
}

/// Set the content of `AVD_INSTANCE` to the result of `AppVarData::parse()`
///
/// NOTE this function panics when Err on `AVD_INSTANCE.write()`
pub fn reset() -> Self {
let instance = AppVarData::parse();

match AVD_INSTANCE.write() {
Ok(mut data) => {
*data = Some(instance.clone());
debug!("[reset_appvar] set content of AVD_INSTANCE to {:#?}", instance);
},
Err(e) => {
panic!("[reset_appvar] Failed to set AVD_INSTANCE! {:#?}", e);
}
}

instance
}


/// Serialize `data` into JSON, then set the content of `JSON_DATA` to the serialize content.
/// Once that is done, `AppVarData::reset()` will be called.
///
/// If `serde_json::to_string` fails, an error is printed in console and `sentry::capture_error`
/// is called.
pub fn set_json_data(data: AppVarData)
-> Result<(), BeansError>
{
debug!("[set_json_data] {:#?}", data);
match serde_json::to_string(&data) {
Ok(v) => {
if let Ok(mut ms) = JSON_DATA.write() {
*ms = v.to_string();
debug!("[set_json_data] successfully set data, calling reset_appvar()");
}
Self::reset();
Ok(())
},
Err(e) => {
error!("[appvar::set_json_data] Failed to serialize data to string! {:}", e);
debug!("{:#?}", e);
sentry::capture_error(&e);

Err(BeansError::AppVarDataSerializeFailure {
error: e,
data: data.clone()
})
}
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AppVarMod
{
/// name of the mod to use.
/// e.g; `open_fortress`
#[serde(rename = "sm_name")]
pub sourcemod_name: String,
/// two-letter abbreviation that is used in `versions.json` for the game.
/// e.g; `of`
pub short_name: String,
/// stylized name of the sourcemod.
/// e.g; `Open Fortress`
pub name_stylized: String
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AppVarRemote
{
/// base URL for the versioning.
/// e.g; `https://beans.adastral.net/`
pub base_url: String,
/// url where the version details are stored.
/// e.g; `https://beans.adastral.net/versions.json`
pub versions_url: String
}
Loading

0 comments on commit 6b95b81

Please sign in to comment.