Support plugin and archive symlinks

On Linux, and on Windows when the game is OpenMW or Oblivion Remastered.
This commit is contained in:
Oliver Hamlet
2025-08-03 14:26:17 +01:00
parent d6b0244203
commit 9f45282bee
5 changed files with 79 additions and 20 deletions
+70 -3
View File
@@ -625,14 +625,15 @@ fn find_archives(
data_path: &Path,
) -> std::io::Result<Vec<PathBuf>> {
let extension = archive_file_extension(game_type);
let allow_symlinks = allow_archive_symlinks(game_type);
let mut archive_paths = Vec::new();
for path in additional_data_paths {
let paths = find_archives_in_path(path, extension)?;
let paths = find_archives_in_path(path, extension, allow_symlinks)?;
archive_paths.extend(paths);
}
let paths = find_archives_in_path(data_path, extension)?;
let paths = find_archives_in_path(data_path, extension, allow_symlinks)?;
archive_paths.extend(paths);
Ok(archive_paths)
@@ -645,18 +646,29 @@ fn archive_file_extension(game_type: GameType) -> &'static str {
}
}
fn allow_archive_symlinks(game_type: GameType) -> bool {
// Assuming support for archive symlinks matches support for plugin symlinks.
!cfg!(windows) || matches!(game_type, GameType::OblivionRemastered | GameType::OpenMW)
}
fn find_archives_in_path(
parent_path: &Path,
archive_file_extension: &str,
allow_symlinks: bool,
) -> std::io::Result<Vec<PathBuf>> {
if !parent_path.exists() {
return Ok(Vec::new());
}
#[expect(
clippy::filetype_is_file,
reason = "Only files are supported except in specific cases"
)]
let paths = std::fs::read_dir(parent_path)?
.filter_map(Result::ok)
.filter(|e| {
e.file_type().map(|f| f.is_file()).unwrap_or(false)
e.file_type()
.is_ok_and(|f| f.is_file() || (allow_symlinks && f.is_symlink()))
&& iends_with_ascii(&e.file_name().to_string_lossy(), archive_file_extension)
})
.map(|e| e.path())
@@ -2044,6 +2056,61 @@ mod tests {
}
}
mod find_archives {
use tempfile::tempdir;
use super::*;
#[cfg(windows)]
pub(crate) fn symlink_file(original: &Path, link: &Path) {
std::os::windows::fs::symlink_file(original, link).unwrap();
}
#[cfg(unix)]
pub(crate) fn symlink_file(original: &Path, link: &Path) {
std::os::unix::fs::symlink(original, link).unwrap();
}
#[cfg(unix)]
#[parameterized_test(ALL_GAME_TYPES)]
fn should_find_symlinked_archives_on_linux(game_type: GameType) {
test_find_symlinked_archives(game_type, true);
}
#[cfg(windows)]
#[parameterized_test(ALL_GAME_TYPES)]
fn should_find_symlinked_archives_for_only_openmw_and_oblivion_remastered_on_windows(
game_type: GameType,
) {
test_find_symlinked_archives(
game_type,
matches!(game_type, GameType::OpenMW | GameType::OblivionRemastered),
);
}
fn test_find_symlinked_archives(game_type: GameType, should_include_symlinks: bool) {
let tmp_dir = tempdir().unwrap();
let extension = archive_file_extension(game_type);
let archive_path = tmp_dir.path().join(format!("archive{extension}"));
let symlink_path = tmp_dir.path().join(format!("archive.symlink{extension}"));
std::fs::File::create(&archive_path).unwrap();
symlink_file(&archive_path, &symlink_path);
let archives = find_archives(game_type, &[], tmp_dir.path()).unwrap();
assert!(archives.contains(&archive_path));
if should_include_symlinks {
assert!(archives.contains(&symlink_path));
} else {
assert!(!archives.contains(&symlink_path));
}
}
}
#[test]
fn to_plugin_sorting_data_should_filter_out_files_with_false_constraints() {
let game_type = GameType::Oblivion;
-1
View File
@@ -1,6 +1,5 @@
// Allow some lints that are denied at the workspace level.
#![allow(
clippy::filetype_is_file,
clippy::must_use_candidate,
clippy::missing_errors_doc,
clippy::wildcard_enum_match_arm