Skip to content

Commit 71f87cc

Browse files
authored
Merge pull request #40 from ktwrd/develop
v1.7.3
2 parents d25358d + ed4083d commit 71f87cc

File tree

9 files changed

+234
-29
lines changed

9 files changed

+234
-29
lines changed

.github/workflows/compile.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
fail-fast: false
1616
matrix:
1717
include:
18-
- os: ubuntu-20.04
18+
- os: ubuntu-22.04
1919
filename: 'beans-rs'
2020
target: x86_64-unknown-linux-gnu
2121
- os: windows-latest
@@ -25,7 +25,7 @@ jobs:
2525
steps:
2626
- uses: actions/checkout@v4
2727
- name: Install Build Dependencies (ubuntu)
28-
if: ${{ matrix.os == 'ubuntu-20.04' }}
28+
if: ${{ matrix.os == 'ubuntu-22.04' }}
2929
run: |
3030
sudo apt-get update;
3131
sudo apt-get install -y \

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
fail-fast: false
1616
matrix:
1717
include:
18-
- os: ubuntu-20.04
18+
- os: ubuntu-22.04
1919
filename: 'beans-rs'
2020
target: x86_64-unknown-linux-gnu
2121

@@ -26,7 +26,7 @@ jobs:
2626
steps:
2727
- uses: actions/checkout@master
2828
- name: Install Build Dependencies (ubuntu)
29-
if: ${{ matrix.os == 'ubuntu-20.04' }}
29+
if: ${{ matrix.os == 'ubuntu-22.04' }}
3030
run: |
3131
sudo apt-get update;
3232
sudo apt-get install -y \

Cargo.toml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
[package]
22
name = "beans-rs"
3-
version = "1.7.2"
3+
version = "1.7.3"
44
edition = "2021"
55
authors = [
6-
"Kate Ward <[email protected]>"
6+
"Kate Ward <[email protected]>",
7+
"ToastXC <[email protected]>"
78
]
89
description = "Installer for Open Fortress"
910
repository = "https://github.com/ktwrd/beans-rs"
@@ -16,10 +17,10 @@ const_format = "0.2.34"
1617
futures = "0.3.31"
1718
futures-util = "0.3.31"
1819
indicatif = "0.17.11"
19-
rand = "0.9.0"
20+
rand = "0.9.1"
2021
serde = { version = "1.0.219", features = ["derive"] }
2122
serde_json = "1.0.140"
22-
sysinfo = "0.33.1"
23+
sysinfo = "0.35.1"
2324
tar = "0.4.44"
2425
tokio-util = { version= "0.7.14", features = ["io"] }
2526
zstd = "0.13.3"
@@ -32,7 +33,7 @@ log = "0.4.26"
3233
lazy_static = "1.5.0"
3334
thread-id = "5.0.0"
3435
colored = "3.0.0"
35-
sentry-log = "0.36.0"
36+
sentry-log = "0.38.0"
3637
chrono = "0.4.40"
3738

3839
fltk = { version = "1.5.4" }
@@ -46,10 +47,12 @@ fl2rust = "0.7.0"
4647
[target.'cfg(target_os = "windows")'.dependencies]
4748
winconsole = { version = "0.11.1", features = ["window"] }
4849
winreg = "0.55.0"
50+
widestring = "1.1.0"
51+
windows = { version = "0.60.0", features = ["Win32_System_IO", "Win32_Storage_FileSystem"] }
4952
dunce = "1.0.5"
5053

5154
[dependencies.sentry]
52-
version = "0.36.0"
55+
version = "0.38.0"
5356
default-features = false
5457
features = [
5558
"backtrace",

src/aria2.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,20 @@ pub async fn download_file(
6969
.replace("%OUT_FILENAME%", &output_filename)
7070
.replace("%USER_AGENT%", &user_agent)
7171
.replace("%URL%", &url);
72-
debug!("[aria2::download_file] using customized arguments: {}", repl);
72+
debug!(
73+
"[aria2::download_file] using customized arguments: {}",
74+
repl
75+
);
7376
cmd.arg(repl);
7477
}
7578
else
7679
{
7780
if let Some(extra) = crate::env_aria2c_extra_args()
7881
{
79-
debug!("[aria2::download_file] (prepend) extra arguments: {}", extra);
82+
debug!(
83+
"[aria2::download_file] (prepend) extra arguments: {}",
84+
extra
85+
);
8086
cmd.arg(extra);
8187
}
8288
cmd.args([

src/error.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,23 @@ pub enum BeansError
227227
reason: Aria2cExitCodeReason,
228228
cmd: String,
229229
backtrace: Backtrace
230+
},
231+
232+
#[error("Failed to read file attributes on {location} ({error:})")]
233+
ReadFileAttributesError
234+
{
235+
error: std::io::Error,
236+
location: String,
237+
backtrace: Backtrace
238+
},
239+
240+
#[error("Failed to set file attributes on {location} ({hresult:#010x}, {hresult_msg:})")]
241+
WindowsSetFileAttributeError
242+
{
243+
hresult: i32,
244+
hresult_msg: String,
245+
location: String,
246+
backtrace: Backtrace
230247
}
231248
}
232249
#[derive(Debug)]

src/extract.rs

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ use indicatif::{ProgressBar,
55
ProgressStyle};
66
use log::{debug,
77
error,
8-
info};
8+
info,
9+
warn};
910
use zstd::stream::read::Decoder as ZstdDecoder;
1011

11-
use crate::BeansError;
12+
use crate::{helper::join_path,
13+
BeansError};
1214

1315
fn unpack_tarball_getfile(
1416
tarball_location: String,
@@ -69,6 +71,7 @@ pub fn unpack_tarball(
6971
tarball = unpack_tarball_getfile(tarball_location.clone(), output_directory.clone())?;
7072
archive = tar::Archive::new(&tarball);
7173
archive.set_preserve_permissions(false);
74+
archive.set_preserve_ownerships(false);
7275

7376
let pb = ProgressBar::new(archive_entry_count);
7477
pb.set_style(ProgressStyle::with_template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({eta})")
@@ -98,6 +101,7 @@ pub fn unpack_tarball(
98101
{
99102
Ok(mut x) =>
100103
{
104+
x.set_preserve_permissions(false);
101105
pb.set_message("Extracting files");
102106
let mut filename = String::new();
103107

@@ -109,22 +113,66 @@ pub fn unpack_tarball(
109113
filename = String::from(s);
110114
}
111115
}
116+
117+
if filename.len() == 0
118+
{
119+
if let Ok(entry_path) = x.path()
120+
{
121+
if let Some(ep_str) = entry_path.to_str()
122+
{
123+
let ep = ep_str.to_string();
124+
filename = ep;
125+
}
126+
}
127+
}
128+
112129
if let Err(error) = x.unpack_in(&output_directory)
113130
{
114-
pb.finish_and_clear();
115131
debug!("error={:#?}", error);
116132
debug!("entry.path={:#?}", x.path());
117133
debug!("entry.link_name={:#?}", x.link_name());
118134
debug!("entry.size={:#?}", x.size());
119135
debug!("size={size:}");
120-
error!("[extract::unpack_tarball] Failed to unpack file {filename} ({error:})");
121-
return Err(BeansError::TarUnpackItemFailure {
122-
src_file: tarball_location,
123-
target_dir: output_directory,
124-
link_name: filename,
125-
error,
126-
backtrace: Backtrace::capture()
127-
});
136+
137+
let error_str = format!("{:#?}", error);
138+
if error_str.contains("io: Custom {")
139+
&& error_str.contains("error: TarError {")
140+
&& error_str.contains("kind: PermissionDenied")
141+
&& error_str.contains("io: Os {")
142+
&& error_str.contains("code: 5")
143+
{
144+
warn!("Failed to unpack file {filename} (Permission Denied, might be read-only)")
145+
}
146+
else
147+
{
148+
pb.finish_and_clear();
149+
error!(
150+
"[extract::unpack_tarball] Failed to unpack file {filename} ({error:})"
151+
);
152+
return Err(BeansError::TarUnpackItemFailure {
153+
src_file: tarball_location,
154+
target_dir: output_directory,
155+
link_name: filename,
156+
error,
157+
backtrace: Backtrace::capture()
158+
});
159+
}
160+
}
161+
162+
if let Ok(entry_path) = x.path()
163+
{
164+
if let Some(ep_str) = entry_path.to_str()
165+
{
166+
let ep = ep_str.to_string();
167+
let target_path = join_path(output_directory.clone(), ep);
168+
if crate::helper::file_exists(target_path.clone())
169+
{
170+
if let Err(e) = crate::helper::unmark_readonly(target_path.clone())
171+
{
172+
debug!("Failed to unmark read-only on file: {target_path:} {e:#?}");
173+
}
174+
}
175+
}
128176
}
129177
pb.inc(1);
130178
}

src/helper/linux.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,11 @@ fn find_steam_reg_path() -> Result<String, BeansError>
8585
error!("Couldn't find any of the locations in STEAM_POSSIBLE_DIR");
8686
Err(BeansError::SteamNotFound)
8787
}
88+
89+
pub fn unmark_readonly(location: String) -> Result<(), BeansError>
90+
{
91+
// does nothing since this function only
92+
// matters for windows
93+
// -kate, 13th mar 2025
94+
Ok(())
95+
}

src/helper/windows.rs

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
use std::backtrace::Backtrace;
1+
use std::{backtrace::Backtrace,
2+
ffi::OsStr,
3+
os::windows::fs::MetadataExt};
24

5+
use bitflags::bitflags;
6+
use log::debug;
7+
use widestring::U16String;
8+
use windows::{core::PCWSTR,
9+
Win32::Storage::FileSystem::*};
310
use winreg::{enums::HKEY_CURRENT_USER,
411
RegKey};
512

@@ -34,3 +41,117 @@ pub fn find_sourcemod_path() -> Result<String, BeansError>
3441
})
3542
}
3643
}
44+
45+
/// Unmark a file as readonly. For some reason, `gameinfo.txt` is always
46+
/// readonly, and trying to replace it fails. Unmarking it when this function
47+
/// when extracting should fix the issue.
48+
pub fn unmark_readonly(location: String) -> Result<(), BeansError>
49+
{
50+
if !crate::helper::file_exists(location.clone())
51+
{
52+
debug!("[windows::unmark_readonly] file does not exist: {location:}");
53+
return Ok(());
54+
}
55+
56+
let s = location.as_str();
57+
let previous = match get_windows_file_attributes(&s)
58+
{
59+
Ok(a) => a,
60+
Err(e) =>
61+
{
62+
return Err(BeansError::ReadFileAttributesError {
63+
error: e,
64+
location: s.to_string(),
65+
backtrace: Backtrace::capture()
66+
});
67+
}
68+
};
69+
70+
if !previous.contains(WindowsFileAttribute::ReadOnly)
71+
{
72+
return Ok(());
73+
}
74+
75+
let mut new_attr = previous - WindowsFileAttribute::ReadOnly;
76+
if new_attr.contains(WindowsFileAttribute::Archive)
77+
{
78+
new_attr -= WindowsFileAttribute::Archive;
79+
}
80+
81+
if new_attr.is_empty()
82+
{
83+
new_attr = WindowsFileAttribute::Normal;
84+
}
85+
86+
match set_file_attributes_win(&s, new_attr)
87+
{
88+
Ok(_) => Ok(()),
89+
Err(e) =>
90+
{
91+
let hr = e.code().0;
92+
debug!("file.name={s}");
93+
debug!("file.attr={new_attr:#?}");
94+
Err(BeansError::WindowsSetFileAttributeError {
95+
hresult: hr,
96+
hresult_msg: e.message(),
97+
location: s.to_string(),
98+
backtrace: Backtrace::capture()
99+
})
100+
}
101+
}
102+
}
103+
104+
fn set_file_attributes_win<P: AsRef<OsStr>>(
105+
location: P,
106+
attr: WindowsFileAttribute
107+
) -> windows::core::Result<()>
108+
{
109+
if let Some(location_str) = location.as_ref().to_str()
110+
{
111+
if crate::helper::file_exists(format!("{location_str}"))
112+
{
113+
let s = U16String::from_str(location_str);
114+
let mut a = attr.clone();
115+
if a.is_empty()
116+
{
117+
a = WindowsFileAttribute::Normal;
118+
}
119+
let win32_attr = FILE_FLAGS_AND_ATTRIBUTES(a.bits());
120+
unsafe {
121+
let win32_loc = PCWSTR(s.as_ptr());
122+
SetFileAttributesW(win32_loc, win32_attr)?;
123+
}
124+
}
125+
}
126+
Ok(())
127+
}
128+
129+
fn get_windows_file_attributes<P: AsRef<std::path::Path>>(
130+
location: &P
131+
) -> std::io::Result<WindowsFileAttribute>
132+
{
133+
let metadata = std::fs::metadata(location)?;
134+
let attr = metadata.file_attributes();
135+
if let Some(flags) = WindowsFileAttribute::from_bits(attr)
136+
{
137+
return Ok(flags);
138+
}
139+
else
140+
{
141+
return Ok(WindowsFileAttribute::empty());
142+
}
143+
}
144+
145+
bitflags! {
146+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147+
pub struct WindowsFileAttribute : u32 {
148+
const ReadOnly = 1;
149+
const Hidden = 2;
150+
const System = 4;
151+
const Archive = 32;
152+
const Normal = 128;
153+
const Temporary = 256;
154+
const Offline = 4096;
155+
const NonContentIndexed = 8192;
156+
}
157+
}

0 commit comments

Comments
 (0)