#[cfg(feature = "local-online-config")]
use std::time::Duration;
use std::{
env,
fs::OpenOptions,
io::{self, Read},
path::{Path, PathBuf},
str::FromStr,
};
use clap::ArgMatches;
use directories::ProjectDirs;
use serde::Deserialize;
pub fn get_default_config_path(config_file: &str) -> Option<PathBuf> {
let config_files = vec![config_file, "config.json"];
if let Ok(mut path) = env::current_dir() {
for filename in &config_files {
path.push(filename);
if path.exists() {
return Some(path);
}
path.pop();
}
} else {
for filename in &config_files {
let relative_path = PathBuf::from(filename);
if relative_path.exists() {
return Some(relative_path);
}
}
}
if let Some(project_dirs) = ProjectDirs::from("org", "shadowsocks", "shadowsocks-rust") {
let mut config_path = project_dirs.config_dir().to_path_buf();
for filename in &config_files {
config_path.push(filename);
if config_path.exists() {
return Some(config_path);
}
config_path.pop();
}
}
#[cfg(unix)]
if let Ok(base_directories) = xdg::BaseDirectories::with_prefix("shadowsocks-rust") {
for filename in &config_files {
if let Some(config_path) = base_directories.find_config_file(filename) {
return Some(config_path);
}
}
}
#[cfg(unix)]
{
for filename in &config_files {
let path_str = "/etc/shadowsocks-rust/".to_owned() + filename;
let global_config_path = Path::new(&path_str);
if global_config_path.exists() {
return Some(global_config_path.to_path_buf());
}
}
}
None
}
#[derive(thiserror::Error, Debug)]
pub enum ConfigError {
#[error("{0}")]
IoError(#[from] io::Error),
#[error("{0}")]
JsonError(#[from] json5::Error),
#[error("Invalid value: {0}")]
InvalidValue(String),
}
#[derive(Debug, Clone, Default)]
pub struct Config {
#[cfg(feature = "logging")]
pub log: LogConfig,
pub runtime: RuntimeConfig,
#[cfg(feature = "local-online-config")]
pub online_config: Option<OnlineConfig>,
}
impl Config {
pub fn load_from_file<P: AsRef<Path>>(filename: &P) -> Result<Config, ConfigError> {
let filename = filename.as_ref();
let mut reader = OpenOptions::new().read(true).open(filename)?;
let mut content = String::new();
reader.read_to_string(&mut content)?;
Config::load_from_str(&content)
}
pub fn load_from_str(s: &str) -> Result<Config, ConfigError> {
let ssconfig = json5::from_str(s)?;
Config::load_from_ssconfig(ssconfig)
}
fn load_from_ssconfig(ssconfig: SSConfig) -> Result<Config, ConfigError> {
let mut config = Config::default();
#[cfg(feature = "logging")]
if let Some(log) = ssconfig.log {
let mut nlog = LogConfig::default();
if let Some(level) = log.level {
nlog.level = level;
}
if let Some(format) = log.format {
let mut nformat = LogFormatConfig::default();
if let Some(without_time) = format.without_time {
nformat.without_time = without_time;
}
nlog.format = nformat;
}
if let Some(config_path) = log.config_path {
nlog.config_path = Some(PathBuf::from(config_path));
}
config.log = nlog;
}
if let Some(runtime) = ssconfig.runtime {
let mut nruntime = RuntimeConfig::default();
#[cfg(feature = "multi-threaded")]
if let Some(worker_count) = runtime.worker_count {
nruntime.worker_count = Some(worker_count);
}
if let Some(mode) = runtime.mode {
match mode.parse::<RuntimeMode>() {
Ok(m) => nruntime.mode = m,
Err(..) => return Err(ConfigError::InvalidValue(mode)),
}
}
config.runtime = nruntime;
}
#[cfg(feature = "local-online-config")]
if let Some(online_config) = ssconfig.online_config {
config.online_config = Some(OnlineConfig {
config_url: online_config.config_url,
update_interval: online_config.update_interval.map(Duration::from_secs),
});
}
Ok(config)
}
pub fn set_options(&mut self, matches: &ArgMatches) {
#[cfg(feature = "logging")]
{
let debug_level = matches.get_count("VERBOSE");
if debug_level > 0 {
self.log.level = debug_level as u32;
}
if matches.get_flag("LOG_WITHOUT_TIME") {
self.log.format.without_time = true;
}
if let Some(log_config) = matches.get_one::<PathBuf>("LOG_CONFIG").cloned() {
self.log.config_path = Some(log_config);
}
}
#[cfg(feature = "multi-threaded")]
if matches.get_flag("SINGLE_THREADED") {
self.runtime.mode = RuntimeMode::SingleThread;
}
#[cfg(feature = "multi-threaded")]
if let Some(worker_count) = matches.get_one::<usize>("WORKER_THREADS") {
self.runtime.worker_count = Some(*worker_count);
}
let _ = matches;
}
}
#[cfg(feature = "logging")]
#[derive(Debug, Clone, Default)]
pub struct LogConfig {
pub level: u32,
pub format: LogFormatConfig,
pub config_path: Option<PathBuf>,
}
#[cfg(feature = "logging")]
#[derive(Debug, Clone, Default)]
pub struct LogFormatConfig {
pub without_time: bool,
}
#[derive(Debug, Clone, Copy, Default)]
pub enum RuntimeMode {
#[cfg_attr(not(feature = "multi-threaded"), default)]
SingleThread,
#[cfg(feature = "multi-threaded")]
#[cfg_attr(feature = "multi-threaded", default)]
MultiThread,
}
#[derive(Debug)]
pub struct RuntimeModeError;
impl FromStr for RuntimeMode {
type Err = RuntimeModeError;
fn from_str(s: &str) -> Result<RuntimeMode, Self::Err> {
match s {
"single_thread" => Ok(RuntimeMode::SingleThread),
#[cfg(feature = "multi-threaded")]
"multi_thread" => Ok(RuntimeMode::MultiThread),
_ => Err(RuntimeModeError),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RuntimeConfig {
#[cfg(feature = "multi-threaded")]
pub worker_count: Option<usize>,
pub mode: RuntimeMode,
}
#[cfg(feature = "local-online-config")]
#[derive(Debug, Clone)]
pub struct OnlineConfig {
pub config_url: String,
pub update_interval: Option<Duration>,
}
#[derive(Deserialize)]
struct SSConfig {
#[cfg(feature = "logging")]
log: Option<SSLogConfig>,
runtime: Option<SSRuntimeConfig>,
#[cfg(feature = "local-online-config")]
online_config: Option<SSOnlineConfig>,
}
#[cfg(feature = "logging")]
#[derive(Deserialize)]
struct SSLogConfig {
level: Option<u32>,
format: Option<SSLogFormat>,
config_path: Option<String>,
}
#[cfg(feature = "logging")]
#[derive(Deserialize)]
struct SSLogFormat {
without_time: Option<bool>,
}
#[derive(Deserialize)]
struct SSRuntimeConfig {
#[cfg(feature = "multi-threaded")]
worker_count: Option<usize>,
mode: Option<String>,
}
#[cfg(feature = "local-online-config")]
#[derive(Deserialize, Debug, Default)]
struct SSOnlineConfig {
config_url: String,
update_interval: Option<u64>,
}