use crate::config::OS::{LINUX, MACOS, WINDOWS};
use crate::shell::run_shell_command_by_os;
use crate::{
default_cache_folder, format_one_arg, path_to_string, Command, ARCH_ARM7L,
ENV_PROCESSOR_ARCHITECTURE, REQUEST_TIMEOUT_SEC, UNAME_COMMAND,
};
use crate::{ARCH_ARM64, ARCH_X64, ARCH_X86, TTL_SEC};
use anyhow::anyhow;
use anyhow::Error;
use std::cell::RefCell;
use std::env;
use std::env::consts::OS;
use std::fs::read_to_string;
use std::path::Path;
use toml::Table;
#[cfg(windows)]
use winapi::um::sysinfoapi::{GetNativeSystemInfo, SYSTEM_INFO};
#[cfg(windows)]
use winapi::um::winnt::{
PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_ARM, PROCESSOR_ARCHITECTURE_ARM64,
PROCESSOR_ARCHITECTURE_IA64, PROCESSOR_ARCHITECTURE_INTEL,
};
thread_local!(static CACHE_PATH: RefCell<String> = RefCell::new(path_to_string(&default_cache_folder())));
pub const CONFIG_FILE: &str = "se-config.toml";
pub const ENV_PREFIX: &str = "SE_";
pub const VERSION_PREFIX: &str = "-version";
pub const PATH_PREFIX: &str = "-path";
pub const MIRROR_PREFIX: &str = "-mirror-url";
pub const CACHE_PATH_KEY: &str = "cache-path";
pub struct ManagerConfig {
pub cache_path: String,
pub fallback_driver_from_cache: bool,
pub browser_version: String,
pub driver_version: String,
pub browser_path: String,
pub driver_mirror_url: String,
pub browser_mirror_url: String,
pub os: String,
pub arch: String,
pub proxy: String,
pub timeout: u64,
pub ttl: u64,
pub offline: bool,
pub force_browser_download: bool,
pub avoid_browser_download: bool,
pub language_binding: String,
pub selenium_version: String,
pub avoid_stats: bool,
pub skip_driver_in_path: bool,
pub skip_browser_in_path: bool,
}
impl ManagerConfig {
pub fn default(browser_name: &str, driver_name: &str) -> ManagerConfig {
let cache_path = StringKey(vec![CACHE_PATH_KEY], &read_cache_path()).get_value();
let self_os = OS;
let self_arch = if WINDOWS.is(self_os) {
let mut _architecture = env::var(ENV_PROCESSOR_ARCHITECTURE).unwrap_or_default();
#[cfg(windows)]
{
if _architecture.is_empty() {
_architecture = get_win_os_architecture();
}
}
if _architecture.contains("32") {
ARCH_X86.to_string()
} else if _architecture.contains("ARM") {
ARCH_ARM64.to_string()
} else {
ARCH_X64.to_string()
}
} else {
let uname_a_command = Command::new_single(format_one_arg(UNAME_COMMAND, "a"));
if run_shell_command_by_os(self_os, uname_a_command)
.unwrap_or_default()
.to_ascii_lowercase()
.contains(ARCH_ARM64)
{
ARCH_ARM64.to_string()
} else {
let uname_m_command = Command::new_single(format_one_arg(UNAME_COMMAND, "m"));
run_shell_command_by_os(self_os, uname_m_command).unwrap_or_default()
}
};
let browser_version_label = concat(browser_name, VERSION_PREFIX);
let driver_version_label = concat(driver_name, VERSION_PREFIX);
let browser_path_label = concat(browser_name, PATH_PREFIX);
let driver_mirror_label = concat(driver_name, MIRROR_PREFIX);
let browser_mirror_label = concat(browser_name, MIRROR_PREFIX);
ManagerConfig {
cache_path,
fallback_driver_from_cache: true,
browser_version: StringKey(vec!["browser-version", &browser_version_label], "")
.get_value(),
driver_version: StringKey(vec!["driver-version", &driver_version_label], "")
.get_value(),
browser_path: StringKey(vec!["browser-path", &browser_path_label], "").get_value(),
driver_mirror_url: StringKey(vec!["driver-mirror-url", &driver_mirror_label], "")
.get_value(),
browser_mirror_url: StringKey(vec!["browser-mirror-url", &browser_mirror_label], "")
.get_value(),
os: StringKey(vec!["os"], self_os).get_value(),
arch: StringKey(vec!["arch"], self_arch.as_str()).get_value(),
proxy: StringKey(vec!["proxy"], "").get_value(),
timeout: IntegerKey("timeout", REQUEST_TIMEOUT_SEC).get_value(),
ttl: IntegerKey("ttl", TTL_SEC).get_value(),
offline: BooleanKey("offline", false).get_value(),
force_browser_download: BooleanKey("force-browser-download", false).get_value(),
avoid_browser_download: BooleanKey("avoid-browser-download", false).get_value(),
language_binding: StringKey(vec!["language-binding"], "").get_value(),
selenium_version: StringKey(vec!["selenium-version"], "").get_value(),
avoid_stats: BooleanKey("avoid-stats", false).get_value(),
skip_driver_in_path: BooleanKey("skip-driver-in-path", false).get_value(),
skip_browser_in_path: BooleanKey("skip-browser-in-path", false).get_value(),
}
}
}
#[allow(dead_code)]
#[allow(clippy::upper_case_acronyms)]
#[derive(Hash, Eq, PartialEq, Debug)]
pub enum OS {
WINDOWS,
MACOS,
LINUX,
}
impl OS {
pub fn to_str_vector(&self) -> Vec<&str> {
match self {
WINDOWS => vec!["windows", "win"],
MACOS => vec!["macos", "mac"],
LINUX => vec!["linux", "gnu/linux"],
}
}
pub fn is(&self, os: &str) -> bool {
self.to_str_vector()
.contains(&os.to_ascii_lowercase().as_str())
}
}
pub fn str_to_os(os: &str) -> Result<OS, Error> {
if WINDOWS.is(os) {
Ok(WINDOWS)
} else if MACOS.is(os) {
Ok(MACOS)
} else if LINUX.is(os) {
Ok(LINUX)
} else {
Err(anyhow!(format!("Invalid operating system: {os}")))
}
}
#[allow(dead_code)]
#[allow(clippy::upper_case_acronyms)]
pub enum ARCH {
X32,
X64,
ARM64,
ARMV7,
}
impl ARCH {
pub fn to_str_vector(&self) -> Vec<&str> {
match self {
ARCH::X32 => vec![ARCH_X86, "i386", "x32"],
ARCH::X64 => vec![ARCH_X64, "amd64", "x64", "i686", "ia64"],
ARCH::ARM64 => vec![ARCH_ARM64, "aarch64", "arm"],
ARCH::ARMV7 => vec![ARCH_ARM7L, "armv7l"],
}
}
pub fn is(&self, arch: &str) -> bool {
self.to_str_vector()
.contains(&arch.to_ascii_lowercase().as_str())
}
}
pub struct StringKey<'a>(pub Vec<&'a str>, pub &'a str);
impl StringKey<'_> {
pub fn get_value(&self) -> String {
let config = get_config().unwrap_or_default();
let keys = self.0.to_owned();
let default_value = self.1.to_owned();
let mut result;
for key in keys {
if config.contains_key(key) {
result = config[key].as_str().unwrap().to_string()
} else {
result = env::var(get_env_name(key)).unwrap_or_default()
}
if key.eq(CACHE_PATH_KEY) {
return check_cache_path(result, default_value);
}
if !result.is_empty() {
return result;
}
}
default_value
}
}
pub struct IntegerKey<'a>(pub &'a str, pub u64);
impl IntegerKey<'_> {
pub fn get_value(&self) -> u64 {
let config = get_config().unwrap_or_default();
let key = self.0;
if config.contains_key(key) {
config[key].as_integer().unwrap() as u64
} else {
env::var(get_env_name(key))
.unwrap_or_default()
.parse::<u64>()
.unwrap_or_else(|_| self.1.to_owned())
}
}
}
pub struct BooleanKey<'a>(pub &'a str, pub bool);
impl BooleanKey<'_> {
pub fn get_value(&self) -> bool {
let config = get_config().unwrap_or_default();
let key = self.0;
if config.contains_key(key) {
config[key].as_bool().unwrap()
} else {
env::var(get_env_name(key))
.unwrap_or_default()
.parse::<bool>()
.unwrap_or_else(|_| self.1.to_owned())
}
}
}
fn get_env_name(suffix: &str) -> String {
let suffix_uppercase: String = suffix.replace('-', "_").to_uppercase();
concat(ENV_PREFIX, suffix_uppercase.as_str())
}
fn get_config() -> Result<Table, Error> {
let cache_path = read_cache_path();
let config_path = Path::new(&cache_path).to_path_buf().join(CONFIG_FILE);
Ok(read_to_string(config_path)?.parse()?)
}
fn concat(prefix: &str, suffix: &str) -> String {
let mut version_label: String = prefix.to_owned();
version_label.push_str(suffix);
version_label
}
fn check_cache_path(value_in_config_or_env: String, default_value: String) -> String {
let return_value = if !value_in_config_or_env.is_empty() {
value_in_config_or_env
} else if !default_value.is_empty() {
default_value
} else {
read_cache_path()
};
write_cache_path(return_value.clone());
return_value
}
fn write_cache_path(cache_path: String) {
CACHE_PATH.with(|value| {
*value.borrow_mut() = cache_path;
});
}
fn read_cache_path() -> String {
let mut cache_path: String = path_to_string(&default_cache_folder());
CACHE_PATH.with(|value| {
let path: String = (&*value.borrow().to_string()).into();
if !path.is_empty() {
cache_path = path;
}
});
cache_path
}
#[cfg(windows)]
fn get_win_os_architecture() -> String {
unsafe {
let mut system_info: SYSTEM_INFO = std::mem::zeroed();
GetNativeSystemInfo(&mut system_info);
match system_info.u.s() {
si if si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 => "64-bit",
si if si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL => "32-bit",
si if si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM => "ARM",
si if si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64 => "ARM64",
si if si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64 => "Itanium-based",
_ => "Unknown",
}
.to_string()
}
}