use std::{
env,
fs::OpenOptions,
io::{self, Read},
path::{Path, PathBuf},
str::FromStr,
};
use cfg_if::cfg_if;
use clap::ArgMatches;
use directories::ProjectDirs;
use serde::Deserialize;
pub fn get_default_config_path() -> Option<PathBuf> {
if let Ok(mut path) = env::current_dir() {
path.push("config.json");
if path.exists() {
return Some(path);
}
} else {
let relative_path = PathBuf::from("config.json");
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();
config_path.push("config.json");
if config_path.exists() {
return Some(config_path);
}
}
#[cfg(unix)]
if let Ok(base_directories) = xdg::BaseDirectories::with_prefix("shadowsocks-rust") {
if let Some(config_path) = base_directories.find_config_file("config.json") {
return Some(config_path);
}
}
#[cfg(unix)]
{
let global_config_path = Path::new("/etc/shadowsocks-rust/config.json");
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,
}
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;
}
Ok(config)
}
pub fn set_options(&mut self, matches: &ArgMatches) {
#[cfg(feature = "logging")]
{
let debug_level = matches.occurrences_of("VERBOSE");
if debug_level > 0 {
self.log.level = debug_level as u32;
}
if matches.is_present("LOG_WITHOUT_TIME") {
self.log.format.without_time = true;
}
if let Some(log_config) = matches.value_of("LOG_CONFIG") {
self.log.config_path = Some(log_config.into());
}
}
#[cfg(feature = "multi-threaded")]
if matches.is_present("SINGLE_THREADED") {
self.runtime.mode = RuntimeMode::SingleThread;
}
#[cfg(feature = "multi-threaded")]
match matches.value_of_t::<usize>("WORKER_THREADS") {
Ok(worker_count) => self.runtime.worker_count = Some(worker_count),
Err(ref err) if err.kind == clap::ErrorKind::ArgumentNotFound => {}
Err(err) => err.exit(),
}
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)]
pub enum RuntimeMode {
SingleThread,
#[cfg(feature = "multi-threaded")]
MultiThread,
}
impl Default for RuntimeMode {
fn default() -> RuntimeMode {
cfg_if! {
if #[cfg(feature = "multi-threaded")] {
RuntimeMode::MultiThread
} else {
RuntimeMode::SingleThread
}
}
}
}
#[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,
}
#[derive(Deserialize)]
struct SSConfig {
#[cfg(feature = "logging")]
log: Option<SSLogConfig>,
runtime: Option<SSRuntimeConfig>,
}
#[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>,
}