mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
New userdata folder that holds voxygen settings and logs and server saves and settings, split up server settings file into parts that are persisted back to their files and parts that are read-only, misc fixes
This commit is contained in:
parent
26d59a62fc
commit
7c14a3f4a4
1
.gitignore
vendored
1
.gitignore
vendored
@ -30,6 +30,7 @@ run.sh
|
||||
maps
|
||||
screenshots
|
||||
todo.txt
|
||||
userdata
|
||||
|
||||
# Export data
|
||||
*.csv
|
||||
|
@ -44,6 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Overhauled representation of blocks to permit fluid and sprite coexistence
|
||||
- Overhauled sword
|
||||
- Reworked healing sceptre
|
||||
- Split out the sections of the server settings that can be edtited and saved by the server.
|
||||
- Revamped structure of where settings, logs, and game saves are stored so that almost everything is in one place.
|
||||
|
||||
### Removed
|
||||
|
||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4643,6 +4643,7 @@ dependencies = [
|
||||
"authc",
|
||||
"criterion",
|
||||
"crossbeam",
|
||||
"directories-next",
|
||||
"dot_vox",
|
||||
"enum-iterator",
|
||||
"hashbrown",
|
||||
|
@ -15,6 +15,7 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "spec
|
||||
roots = "0.0.6"
|
||||
specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control"], rev = "7a2e348ab2223818bad487695c66c43db88050a5" }
|
||||
vek = { version = "0.12.0", features = ["platform_intrinsics", "serde"] }
|
||||
directories-next = "1.0.1"
|
||||
dot_vox = "4.0"
|
||||
image = { version = "0.23.8", default-features = false, features = ["png"] }
|
||||
serde = { version = "1.0.110", features = ["derive", "rc"] }
|
||||
|
@ -1,6 +1,7 @@
|
||||
mod color;
|
||||
mod dir;
|
||||
mod option;
|
||||
pub mod userdata_dir;
|
||||
|
||||
pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash"));
|
||||
pub const GIT_TAG: &str = include_str!(concat!(env!("OUT_DIR"), "/gittag"));
|
||||
|
76
common/src/util/userdata_dir.rs
Normal file
76
common/src/util/userdata_dir.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
const VELOREN_USERDATA_ENV: &'static str = "VELOREN_USERDATA";
|
||||
|
||||
// TODO: consider expanding this to a general install strategy variable that is also used for
|
||||
// finding assets
|
||||
/// # `VELOREN_USERDATA_STRATEGY` environment variable
|
||||
/// Read during compilation
|
||||
/// Useful to set when compiling for distribution
|
||||
/// "system" => system specific project data directory
|
||||
/// "executable" => <executable dir>/userdata
|
||||
/// Note: case insensitive
|
||||
|
||||
/// Determines common user data directory used by veloren frontends
|
||||
/// The first specified in this list is used
|
||||
/// 1. The VELOREN_USERDATA environment variable
|
||||
/// 2. The VELOREN_USERDATA_STRATEGY environment variable
|
||||
/// 3. The CARGO_MANIFEST_DIR/userdata or CARGO_MANIFEST_DIR/../userdata depending on if a
|
||||
/// workspace if being used
|
||||
pub fn userdata_dir(workspace: bool, strategy: Option<&str>, manifest_dir: &str) -> PathBuf {
|
||||
// 1. The VELOREN_USERDATA environment variable
|
||||
std::env::var_os(VELOREN_USERDATA_ENV)
|
||||
.map(PathBuf::from)
|
||||
// 2. The VELOREN_USERDATA_STRATEGY environment variable
|
||||
.or_else(|| match strategy {
|
||||
// "system" => system specific project data directory
|
||||
Some(s) if s.eq_ignore_ascii_case("system") => Some(directories_next::ProjectDirs::from("net", "veloren", "userdata")
|
||||
.expect("System's $HOME directory path not found!")
|
||||
.data_dir()
|
||||
.to_owned()
|
||||
),
|
||||
// "executable" => <executable dir>/userdata
|
||||
Some(s) if s.eq_ignore_ascii_case("executable") => {
|
||||
let mut path = std::env::current_exe()
|
||||
.expect("Failed to retrieve executable directory!");
|
||||
path.pop();
|
||||
path.push("userdata");
|
||||
Some(path)
|
||||
},
|
||||
Some(_) => None, // TODO: panic? catch during compilation?
|
||||
_ => None,
|
||||
|
||||
})
|
||||
// 3. The CARGO_MANIFEST_DIR/userdata or CARGO_MANIFEST_DIR/../userdata depending on if a
|
||||
// workspace if being used
|
||||
.unwrap_or_else(|| {
|
||||
let mut path = PathBuf::from(manifest_dir);
|
||||
if workspace {
|
||||
path.pop();
|
||||
}
|
||||
path.push("userdata");
|
||||
path
|
||||
})
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! userdata_dir_workspace {
|
||||
() => {
|
||||
$crate::util::userdata_dir::userdata_dir(
|
||||
true,
|
||||
option_env!("VELOREN_USERDATA_STRATEGY"), env!("CARGO_MANIFEST_DIR")
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! userdata_dir_no_workspace {
|
||||
() => {
|
||||
$crate::util::userdata_dir::userdata_dir(
|
||||
false,
|
||||
option_env!("VELOREN_USERDATA_STRATEGY"), env!("CARGO_MANIFEST_DIR")
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
58
server-cli/src/logging.rs
Normal file
58
server-cli/src/logging.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use crate::tuilog::TuiLog;
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter, FmtSubscriber};
|
||||
#[cfg(feature = "tracy")]
|
||||
use tracing_subscriber::{layer::SubscriberExt, prelude::*};
|
||||
|
||||
const RUST_LOG_ENV: &str = "RUST_LOG";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref LOG: TuiLog<'static> = TuiLog::default();
|
||||
}
|
||||
|
||||
pub fn init(basic: bool) {
|
||||
// Init logging
|
||||
let base_exceptions = |env: EnvFilter| {
|
||||
env.add_directive("veloren_world::sim=info".parse().unwrap())
|
||||
.add_directive("veloren_world::civ=info".parse().unwrap())
|
||||
.add_directive("uvth=warn".parse().unwrap())
|
||||
.add_directive("tiny_http=warn".parse().unwrap())
|
||||
.add_directive("mio::sys::windows=debug".parse().unwrap())
|
||||
.add_directive(LevelFilter::INFO.into())
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "tracy"))]
|
||||
let filter = match std::env::var_os(RUST_LOG_ENV).map(|s| s.into_string()) {
|
||||
Some(Ok(env)) => {
|
||||
let mut filter = base_exceptions(EnvFilter::new(""));
|
||||
for s in env.split(',').into_iter() {
|
||||
match s.parse() {
|
||||
Ok(d) => filter = filter.add_directive(d),
|
||||
Err(err) => println!("WARN ignoring log directive: `{}`: {}", s, err),
|
||||
};
|
||||
}
|
||||
filter
|
||||
},
|
||||
_ => base_exceptions(EnvFilter::from_env(RUST_LOG_ENV)),
|
||||
};
|
||||
|
||||
#[cfg(feature = "tracy")]
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_tracy::TracyLayer::new().with_stackdepth(0))
|
||||
.init();
|
||||
|
||||
#[cfg(not(feature = "tracy"))]
|
||||
// TODO: when tracing gets per Layer filters re-enable this when the tracy feature is being
|
||||
// used (and do the same in voxygen)
|
||||
{
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_max_level(Level::ERROR)
|
||||
.with_env_filter(filter);
|
||||
|
||||
if basic {
|
||||
subscriber.init();
|
||||
} else {
|
||||
subscriber.with_writer(|| LOG.clone()).init();
|
||||
}
|
||||
}
|
||||
}
|
@ -2,39 +2,28 @@
|
||||
#![deny(clippy::clone_on_ref_ptr)]
|
||||
#![feature(bool_to_option)]
|
||||
|
||||
mod logging;
|
||||
mod shutdown_coordinator;
|
||||
mod tui_runner;
|
||||
mod tuilog;
|
||||
|
||||
#[macro_use] extern crate lazy_static;
|
||||
|
||||
use crate::{
|
||||
shutdown_coordinator::ShutdownCoordinator,
|
||||
tui_runner::{Message, Tui},
|
||||
tuilog::TuiLog,
|
||||
};
|
||||
use clap::{App, Arg};
|
||||
use common::clock::Clock;
|
||||
use server::{Event, Input, Server, ServerSettings};
|
||||
use server::{DataDir, Event, Input, Server, ServerSettings};
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use signal_hook::SIGUSR1;
|
||||
use tracing::{info, Level};
|
||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter, FmtSubscriber};
|
||||
#[cfg(feature = "tracy")]
|
||||
use tracing_subscriber::{layer::SubscriberExt, prelude::*};
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::{
|
||||
io,
|
||||
sync::{atomic::AtomicBool, mpsc, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
const TPS: u64 = 30;
|
||||
const RUST_LOG_ENV: &str = "RUST_LOG";
|
||||
|
||||
lazy_static! {
|
||||
static ref LOG: TuiLog<'static> = TuiLog::default();
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let matches = App::new("Veloren server cli")
|
||||
@ -67,50 +56,7 @@ fn main() -> io::Result<()> {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
let _ = signal_hook::flag::register(SIGUSR1, Arc::clone(&sigusr1_signal));
|
||||
|
||||
// Init logging
|
||||
let base_exceptions = |env: EnvFilter| {
|
||||
env.add_directive("veloren_world::sim=info".parse().unwrap())
|
||||
.add_directive("veloren_world::civ=info".parse().unwrap())
|
||||
.add_directive("uvth=warn".parse().unwrap())
|
||||
.add_directive("tiny_http=warn".parse().unwrap())
|
||||
.add_directive("mio::sys::windows=debug".parse().unwrap())
|
||||
.add_directive(LevelFilter::INFO.into())
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "tracy"))]
|
||||
let filter = match std::env::var_os(RUST_LOG_ENV).map(|s| s.into_string()) {
|
||||
Some(Ok(env)) => {
|
||||
let mut filter = base_exceptions(EnvFilter::new(""));
|
||||
for s in env.split(',').into_iter() {
|
||||
match s.parse() {
|
||||
Ok(d) => filter = filter.add_directive(d),
|
||||
Err(err) => println!("WARN ignoring log directive: `{}`: {}", s, err),
|
||||
};
|
||||
}
|
||||
filter
|
||||
},
|
||||
_ => base_exceptions(EnvFilter::from_env(RUST_LOG_ENV)),
|
||||
};
|
||||
|
||||
#[cfg(feature = "tracy")]
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_tracy::TracyLayer::new().with_stackdepth(0))
|
||||
.init();
|
||||
|
||||
#[cfg(not(feature = "tracy"))]
|
||||
// TODO: when tracing gets per Layer filters re-enable this when the tracy feature is being
|
||||
// used (and do the same in voxygen)
|
||||
{
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_max_level(Level::ERROR)
|
||||
.with_env_filter(filter);
|
||||
|
||||
if basic {
|
||||
subscriber.init();
|
||||
} else {
|
||||
subscriber.with_writer(|| LOG.clone()).init();
|
||||
}
|
||||
}
|
||||
logging::init(basic);
|
||||
|
||||
// Panic hook to ensure that console mode is set back correctly if in non-basic
|
||||
// mode
|
||||
@ -127,17 +73,25 @@ fn main() -> io::Result<()> {
|
||||
// Set up an fps clock
|
||||
let mut clock = Clock::start();
|
||||
|
||||
// Determine folder to save server data in
|
||||
let server_data_dir = DataDir::from({
|
||||
let mut path = common::userdata_dir_workspace!();
|
||||
path.push(server::DEFAULT_DATA_DIR_NAME);
|
||||
path
|
||||
});
|
||||
|
||||
// Load settings
|
||||
let mut settings = ServerSettings::load();
|
||||
// TODO: make settings file immutable so that this does not overwrite the
|
||||
// settings
|
||||
let mut settings = ServerSettings::load(server_data_dir.as_ref());
|
||||
|
||||
if no_auth {
|
||||
settings.auth_server_address = None;
|
||||
}
|
||||
|
||||
let server_port = &settings.gameserver_address.port();
|
||||
let metrics_port = &settings.metrics_address.port();
|
||||
// Create server
|
||||
let mut server = Server::new(settings).expect("Failed to create server instance!");
|
||||
let mut server =
|
||||
Server::new(settings, server_data_dir).expect("Failed to create server instance!");
|
||||
|
||||
info!(
|
||||
?server_port,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::LOG;
|
||||
use crate::logging::LOG;
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture},
|
||||
execute,
|
||||
|
@ -2,7 +2,7 @@
|
||||
//! To implement a new command, add an instance of `ChatCommand` to
|
||||
//! `CHAT_COMMANDS` and provide a handler function.
|
||||
|
||||
use crate::{client::Client, Server, StateExt};
|
||||
use crate::{client::Client, settings::EditableSetting, Server, StateExt};
|
||||
use chrono::{NaiveTime, Timelike};
|
||||
use common::{
|
||||
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
|
||||
@ -266,7 +266,7 @@ fn handle_motd(
|
||||
) {
|
||||
server.notify_client(
|
||||
client,
|
||||
ChatType::CommandError.server_msg(server.settings().server_description.clone()),
|
||||
ChatType::CommandError.server_msg((**server.server_description()).clone()),
|
||||
);
|
||||
}
|
||||
|
||||
@ -277,18 +277,21 @@ fn handle_set_motd(
|
||||
args: String,
|
||||
action: &ChatCommand,
|
||||
) {
|
||||
let data_dir = server.data_dir();
|
||||
match scan_fmt!(&args, &action.arg_fmt(), String) {
|
||||
Ok(msg) => {
|
||||
server
|
||||
.settings_mut()
|
||||
.edit(|s| s.server_description = msg.clone());
|
||||
.server_description_mut()
|
||||
.edit(data_dir.as_ref(), |d| **d = msg.clone());
|
||||
server.notify_client(
|
||||
client,
|
||||
ChatType::CommandError.server_msg(format!("Server description set to \"{}\"", msg)),
|
||||
);
|
||||
},
|
||||
Err(_) => {
|
||||
server.settings_mut().edit(|s| s.server_description.clear());
|
||||
server
|
||||
.server_description_mut()
|
||||
.edit(data_dir.as_ref(), |d| d.clear());
|
||||
server.notify_client(
|
||||
client,
|
||||
ChatType::CommandError.server_msg("Removed server description".to_string()),
|
||||
@ -1825,16 +1828,17 @@ fn handle_whitelist(
|
||||
if let Ok((whitelist_action, username)) = scan_fmt!(&args, &action.arg_fmt(), String, String) {
|
||||
if whitelist_action.eq_ignore_ascii_case("add") {
|
||||
server
|
||||
.settings_mut()
|
||||
.edit(|s| s.whitelist.push(username.clone()));
|
||||
.whitelist_mut()
|
||||
.edit(server.data_dir().as_ref(), |w| w.push(username.clone()));
|
||||
server.notify_client(
|
||||
client,
|
||||
ChatType::CommandInfo.server_msg(format!("\"{}\" added to whitelist", username)),
|
||||
);
|
||||
} else if whitelist_action.eq_ignore_ascii_case("remove") {
|
||||
server.settings_mut().edit(|s| {
|
||||
s.whitelist
|
||||
.retain(|x| !x.eq_ignore_ascii_case(&username.clone()))
|
||||
server
|
||||
.whitelist_mut()
|
||||
.edit(server.data_dir().as_ref(), |w| {
|
||||
w.retain(|x| !x.eq_ignore_ascii_case(&username.clone()))
|
||||
});
|
||||
server.notify_client(
|
||||
client,
|
||||
@ -1926,16 +1930,15 @@ fn handle_ban(
|
||||
.username_to_uuid(&target_alias);
|
||||
|
||||
if let Ok(uuid) = uuid_result {
|
||||
if server.settings().banlist.contains_key(&uuid) {
|
||||
if server.banlist().contains_key(&uuid) {
|
||||
server.notify_client(
|
||||
client,
|
||||
ChatType::CommandError
|
||||
.server_msg(format!("{} is already on the banlist", target_alias)),
|
||||
)
|
||||
} else {
|
||||
server.settings_mut().edit(|s| {
|
||||
s.banlist
|
||||
.insert(uuid, (target_alias.clone(), reason.clone()));
|
||||
server.banlist_mut().edit(server.data_dir().as_ref(), |b| {
|
||||
b.insert(uuid, (target_alias.clone(), reason.clone()));
|
||||
});
|
||||
server.notify_client(
|
||||
client,
|
||||
@ -1987,8 +1990,8 @@ fn handle_unban(
|
||||
.username_to_uuid(&username);
|
||||
|
||||
if let Ok(uuid) = uuid_result {
|
||||
server.settings_mut().edit(|s| {
|
||||
s.banlist.remove(&uuid);
|
||||
server.banlist_mut().edit(server.data_dir().as_ref(), |b| {
|
||||
b.remove(&uuid);
|
||||
});
|
||||
server.notify_client(
|
||||
client,
|
||||
|
16
server/src/data_dir.rs
Normal file
16
server/src/data_dir.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Used so that different server frontends can share the same server saves,
|
||||
/// etc.
|
||||
pub const DEFAULT_DATA_DIR_NAME: &'static str = "server";
|
||||
|
||||
/// Indicates where maps, saves, and server_config folders are to be stored
|
||||
pub struct DataDir {
|
||||
pub path: PathBuf,
|
||||
}
|
||||
impl<T: Into<PathBuf>> From<T> for DataDir {
|
||||
fn from(t: T) -> Self { Self { path: t.into() } }
|
||||
}
|
||||
impl AsRef<Path> for DataDir {
|
||||
fn as_ref(&self) -> &Path { &self.path }
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
|
||||
pub mod alias_validator;
|
||||
mod character_creator;
|
||||
mod data_dir;
|
||||
pub mod chunk_generator;
|
||||
pub mod client;
|
||||
pub mod cmd;
|
||||
@ -21,7 +22,7 @@ pub mod sys;
|
||||
#[cfg(not(feature = "worldgen"))] mod test_world;
|
||||
|
||||
// Reexports
|
||||
pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings};
|
||||
pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings, data_dir::{DataDir, DEFAULT_DATA_DIR_NAME}};
|
||||
|
||||
use crate::{
|
||||
alias_validator::AliasValidator,
|
||||
@ -29,6 +30,7 @@ use crate::{
|
||||
client::{Client, RegionSubscription},
|
||||
cmd::ChatCommandExt,
|
||||
login_provider::LoginProvider,
|
||||
settings::{Banlist, EditableSetting, ServerDescription, Whitelist},
|
||||
state_ext::StateExt,
|
||||
sys::sentinel::{DeletedEntities, TrackedComps},
|
||||
};
|
||||
@ -102,10 +104,15 @@ impl Server {
|
||||
/// Create a new `Server`
|
||||
#[allow(clippy::expect_fun_call)] // TODO: Pending review in #587
|
||||
#[allow(clippy::needless_update)] // TODO: Pending review in #587
|
||||
pub fn new(settings: ServerSettings) -> Result<Self, Error> {
|
||||
pub fn new(settings: ServerSettings, data_dir: DataDir) -> Result<Self, Error> {
|
||||
info!("Server is data dir is: {}", data_dir.path.display());
|
||||
|
||||
// persistence_db_dir is relative to data_dir
|
||||
let persistence_db_dir = data_dir.path.join(&settings.persistence_db_dir);
|
||||
|
||||
// Run pending DB migrations (if any)
|
||||
debug!("Running DB migrations...");
|
||||
if let Some(e) = persistence::run_migrations(&settings.persistence_db_dir).err() {
|
||||
if let Some(e) = persistence::run_migrations(&persistence_db_dir).err() {
|
||||
panic!("Migration error: {:?}", e);
|
||||
}
|
||||
|
||||
@ -116,6 +123,10 @@ impl Server {
|
||||
|
||||
let mut state = State::default();
|
||||
state.ecs_mut().insert(settings.clone());
|
||||
state.ecs_mut().insert(Whitelist::load(&data_dir.path));
|
||||
state.ecs_mut().insert(Banlist::load(&data_dir.path));
|
||||
state.ecs_mut().insert(ServerDescription::load(&data_dir.path));
|
||||
state.ecs_mut().insert(data_dir);
|
||||
state.ecs_mut().insert(EventBus::<ServerEvent>::default());
|
||||
state
|
||||
.ecs_mut()
|
||||
@ -128,10 +139,10 @@ impl Server {
|
||||
.insert(ChunkGenerator::new(chunk_gen_metrics));
|
||||
state
|
||||
.ecs_mut()
|
||||
.insert(CharacterUpdater::new(settings.persistence_db_dir.clone())?);
|
||||
.insert(CharacterUpdater::new(&persistence_db_dir)?);
|
||||
state
|
||||
.ecs_mut()
|
||||
.insert(CharacterLoader::new(settings.persistence_db_dir.clone())?);
|
||||
.insert(CharacterLoader::new(&persistence_db_dir)?);
|
||||
state
|
||||
.ecs_mut()
|
||||
.insert(comp::AdminList(settings.admins.clone()));
|
||||
@ -340,9 +351,10 @@ impl Server {
|
||||
|
||||
pub fn get_server_info(&self) -> ServerInfo {
|
||||
let settings = self.state.ecs().fetch::<ServerSettings>();
|
||||
let server_description = self.state.ecs().fetch::<ServerDescription>();
|
||||
ServerInfo {
|
||||
name: settings.server_name.clone(),
|
||||
description: settings.server_description.clone(),
|
||||
description: (**server_description).clone(),
|
||||
git_hash: common::util::GIT_HASH.to_string(),
|
||||
git_date: common::util::GIT_DATE.to_string(),
|
||||
auth_provider: settings.auth_server_address.clone(),
|
||||
@ -360,8 +372,38 @@ impl Server {
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the server's settings
|
||||
pub fn settings_mut(&mut self) -> impl DerefMut<Target = ServerSettings> + '_ {
|
||||
self.state.ecs_mut().fetch_mut::<ServerSettings>()
|
||||
pub fn settings_mut(&self) -> impl DerefMut<Target = ServerSettings> + '_ {
|
||||
self.state.ecs().fetch_mut::<ServerSettings>()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the server's whitelist
|
||||
pub fn whitelist_mut(&self) -> impl DerefMut<Target = Whitelist> + '_ {
|
||||
self.state.ecs().fetch_mut::<Whitelist>()
|
||||
}
|
||||
|
||||
/// Get a reference to the server's banlist
|
||||
pub fn banlist(&self) -> impl Deref<Target = Banlist> + '_ {
|
||||
self.state.ecs().fetch::<Banlist>()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the server's banlist
|
||||
pub fn banlist_mut(&self) -> impl DerefMut<Target = Banlist> + '_ {
|
||||
self.state.ecs().fetch_mut::<Banlist>()
|
||||
}
|
||||
|
||||
/// Get a reference to the server's description
|
||||
pub fn server_description(&self) -> impl Deref<Target = ServerDescription> + '_ {
|
||||
self.state.ecs().fetch::<ServerDescription>()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the server's description
|
||||
pub fn server_description_mut(&self) -> impl DerefMut<Target = ServerDescription> + '_ {
|
||||
self.state.ecs().fetch_mut::<ServerDescription>()
|
||||
}
|
||||
|
||||
/// Get path to the directory that the server info into
|
||||
pub fn data_dir(&self) -> impl Deref<Target = DataDir> + '_ {
|
||||
self.state.ecs().fetch::<DataDir>()
|
||||
}
|
||||
|
||||
/// Get a reference to the server's game state.
|
||||
|
@ -5,6 +5,7 @@ use crate::persistence::{
|
||||
};
|
||||
use common::character::{CharacterId, CharacterItem};
|
||||
use crossbeam::{channel, channel::TryIter};
|
||||
use std::path::Path;
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) type CharacterListResult = Result<Vec<CharacterItem>, Error>;
|
||||
@ -64,11 +65,11 @@ pub struct CharacterLoader {
|
||||
}
|
||||
|
||||
impl CharacterLoader {
|
||||
pub fn new(db_dir: String) -> diesel::QueryResult<Self> {
|
||||
pub fn new(db_dir: &Path) -> diesel::QueryResult<Self> {
|
||||
let (update_tx, internal_rx) = channel::unbounded::<CharacterLoaderRequest>();
|
||||
let (internal_tx, update_rx) = channel::unbounded::<CharacterLoaderResponse>();
|
||||
|
||||
let mut conn = establish_connection(&db_dir)?;
|
||||
let mut conn = establish_connection(db_dir)?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for request in internal_rx {
|
||||
|
@ -3,7 +3,7 @@ use common::{character::CharacterId, comp::item::ItemId};
|
||||
|
||||
use crate::persistence::{establish_connection, VelorenConnection};
|
||||
use crossbeam::channel;
|
||||
use std::sync::Arc;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use tracing::{error, trace};
|
||||
|
||||
pub type CharacterUpdateData = (comp::Stats, comp::Inventory, comp::Loadout);
|
||||
@ -19,11 +19,11 @@ pub struct CharacterUpdater {
|
||||
}
|
||||
|
||||
impl CharacterUpdater {
|
||||
pub fn new(db_dir: String) -> diesel::QueryResult<Self> {
|
||||
pub fn new(db_dir: &Path) -> diesel::QueryResult<Self> {
|
||||
let (update_tx, update_rx) =
|
||||
channel::unbounded::<Vec<(CharacterId, CharacterUpdateData)>>();
|
||||
|
||||
let mut conn = establish_connection(&db_dir)?;
|
||||
let mut conn = establish_connection(db_dir)?;
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
while let Ok(updates) = update_rx.recv() {
|
||||
|
@ -19,7 +19,10 @@ extern crate diesel;
|
||||
use common::comp;
|
||||
use diesel::{connection::SimpleConnection, prelude::*};
|
||||
use diesel_migrations::embed_migrations;
|
||||
use std::{env, fs, path::PathBuf};
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// A tuple of the components that are persisted to the DB for each character
|
||||
@ -45,9 +48,9 @@ impl std::io::Write for TracingOut {
|
||||
}
|
||||
|
||||
/// Runs any pending database migrations. This is executed during server startup
|
||||
pub fn run_migrations(db_dir: &str) -> Result<(), diesel_migrations::RunMigrationsError> {
|
||||
pub fn run_migrations(db_dir: &Path) -> Result<(), diesel_migrations::RunMigrationsError> {
|
||||
let db_dir = &apply_saves_dir_override(db_dir);
|
||||
let _ = fs::create_dir(format!("{}/", db_dir));
|
||||
let _ = fs::create_dir(format!("{}/", db_dir.display()));
|
||||
|
||||
embedded_migrations::run_with_output(
|
||||
&establish_connection(db_dir)
|
||||
@ -91,9 +94,9 @@ impl<'a> core::ops::Deref for VelorenTransaction<'a> {
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
pub fn establish_connection(db_dir: &str) -> QueryResult<VelorenConnection> {
|
||||
pub fn establish_connection(db_dir: &Path) -> QueryResult<VelorenConnection> {
|
||||
let db_dir = &apply_saves_dir_override(db_dir);
|
||||
let database_url = format!("{}/db.sqlite", db_dir);
|
||||
let database_url = format!("{}/db.sqlite", db_dir.display());
|
||||
|
||||
let connection = SqliteConnection::establish(&database_url)
|
||||
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url));
|
||||
@ -117,16 +120,13 @@ pub fn establish_connection(db_dir: &str) -> QueryResult<VelorenConnection> {
|
||||
Ok(VelorenConnection(connection))
|
||||
}
|
||||
|
||||
fn apply_saves_dir_override(db_dir: &str) -> String {
|
||||
fn apply_saves_dir_override(db_dir: &Path) -> PathBuf {
|
||||
if let Some(saves_dir) = env::var_os("VELOREN_SAVES_DIR") {
|
||||
let path = PathBuf::from(saves_dir.clone());
|
||||
let path = PathBuf::from(&saves_dir);
|
||||
if path.exists() || path.parent().map(|x| x.exists()).unwrap_or(false) {
|
||||
// Only allow paths with valid unicode characters
|
||||
if let Some(path) = path.to_str() {
|
||||
return path.to_owned();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
warn!(?saves_dir, "VELOREN_SAVES_DIR points to an invalid path.");
|
||||
}
|
||||
db_dir.to_string()
|
||||
db_dir.to_owned()
|
||||
}
|
||||
|
@ -1,12 +1,28 @@
|
||||
mod editable;
|
||||
|
||||
pub use editable::EditableSetting;
|
||||
|
||||
use authc::Uuid;
|
||||
use hashbrown::HashMap;
|
||||
use portpicker::pick_unused_port;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, io::prelude::*, net::SocketAddr, path::PathBuf, time::Duration};
|
||||
use std::{
|
||||
fs,
|
||||
net::SocketAddr,
|
||||
ops::{Deref, DerefMut},
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
use tracing::{error, warn};
|
||||
use world::sim::FileOpts;
|
||||
|
||||
const DEFAULT_WORLD_SEED: u32 = 59686;
|
||||
//const CONFIG_DIR_ENV: &'static str = "VELOREN_SERVER_CONFIG";
|
||||
const /*DEFAULT_*/CONFIG_DIR: &'static str = "server_config";
|
||||
const SETTINGS_FILENAME: &'static str = "settings.ron";
|
||||
const WHITELIST_FILENAME: &'static str = "whitelist.ron";
|
||||
const BANLIST_FILENAME: &'static str = "banlist.ron";
|
||||
const SERVER_DESCRIPTION_FILENAME: &'static str = "description.ron";
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
@ -18,14 +34,12 @@ pub struct ServerSettings {
|
||||
pub world_seed: u32,
|
||||
//pub pvp_enabled: bool,
|
||||
pub server_name: String,
|
||||
pub server_description: String,
|
||||
pub start_time: f64,
|
||||
pub admins: Vec<String>,
|
||||
pub whitelist: Vec<String>,
|
||||
pub banlist: HashMap<Uuid, (String, String)>,
|
||||
/// When set to None, loads the default map file (if available); otherwise,
|
||||
/// uses the value of the file options to decide how to proceed.
|
||||
pub map_file: Option<FileOpts>,
|
||||
/// Relative paths are relative to the server data dir
|
||||
pub persistence_db_dir: String,
|
||||
pub max_view_distance: Option<u32>,
|
||||
pub banned_words_files: Vec<PathBuf>,
|
||||
@ -42,15 +56,12 @@ impl Default for ServerSettings {
|
||||
metrics_address: SocketAddr::from(([0; 4], 14005)),
|
||||
auth_server_address: Some("https://auth.veloren.net".into()),
|
||||
world_seed: DEFAULT_WORLD_SEED,
|
||||
server_name: "Veloren Alpha".to_owned(),
|
||||
server_description: "This is the best Veloren server.".to_owned(),
|
||||
server_name: "Veloren Alpha".into(),
|
||||
max_players: 100,
|
||||
start_time: 9.0 * 3600.0,
|
||||
map_file: None,
|
||||
admins: Vec::new(),
|
||||
whitelist: Vec::new(),
|
||||
banlist: HashMap::new(),
|
||||
persistence_db_dir: "saves".to_owned(),
|
||||
persistence_db_dir: "saves".into(),
|
||||
max_view_distance: Some(30),
|
||||
banned_words_files: Vec::new(),
|
||||
max_player_group_size: 6,
|
||||
@ -62,41 +73,53 @@ impl Default for ServerSettings {
|
||||
}
|
||||
|
||||
impl ServerSettings {
|
||||
#[allow(clippy::single_match)] // TODO: Pending review in #587
|
||||
pub fn load() -> Self {
|
||||
let path = ServerSettings::get_settings_path();
|
||||
/// path: Directory that contains the server config directory
|
||||
pub fn load(path: &Path) -> Self {
|
||||
let path = Self::get_settings_path(path);
|
||||
|
||||
if let Ok(file) = fs::File::open(path) {
|
||||
if let Ok(file) = fs::File::open(&path) {
|
||||
match ron::de::from_reader(file) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
warn!(?e, "Failed to parse setting file! Fallback to default");
|
||||
Self::default()
|
||||
warn!(
|
||||
?e,
|
||||
"Failed to parse setting file! Falling back to default settings and \
|
||||
creating a template file for you to migrate your current settings file"
|
||||
);
|
||||
let default_settings = Self::default();
|
||||
let template_path = path.with_extension("template.ron");
|
||||
if let Err(e) = default_settings.save_to_file(&template_path) {
|
||||
error!(?e, "Failed to create template settings file")
|
||||
}
|
||||
default_settings
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let default_settings = Self::default();
|
||||
|
||||
match default_settings.save_to_file() {
|
||||
Err(e) => error!(?e, "Failed to create default setting file!"),
|
||||
_ => {},
|
||||
if let Err(e) = default_settings.save_to_file(&path) {
|
||||
error!(?e, "Failed to create default settings file!");
|
||||
}
|
||||
default_settings
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_to_file(&self) -> std::io::Result<()> {
|
||||
let path = ServerSettings::get_settings_path();
|
||||
let mut config_file = fs::File::create(path)?;
|
||||
|
||||
let s: &str = &ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default())
|
||||
fn save_to_file(&self, path: &Path) -> std::io::Result<()> {
|
||||
// Create dir if it doesn't exist
|
||||
if let Some(dir) = path.parent() {
|
||||
fs::create_dir_all(dir)?;
|
||||
}
|
||||
let ron = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default())
|
||||
.expect("Failed serialize settings.");
|
||||
config_file.write_all(s.as_bytes())?;
|
||||
|
||||
fs::write(path, ron.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn singleplayer(persistence_db_dir: String) -> Self {
|
||||
let load = Self::load();
|
||||
/// path: Directory that contains the server config directory
|
||||
pub fn singleplayer(path: &Path) -> Self {
|
||||
let load = Self::load(&path);
|
||||
Self {
|
||||
//BUG: theoretically another process can grab the port between here and server
|
||||
// creation, however the timewindow is quite small
|
||||
@ -116,24 +139,90 @@ impl ServerSettings {
|
||||
DEFAULT_WORLD_SEED
|
||||
},
|
||||
server_name: "Singleplayer".to_owned(),
|
||||
server_description: "Who needs friends anyway?".to_owned(),
|
||||
//server_description: "Who needs friends anyway?".to_owned(),
|
||||
max_players: 100,
|
||||
start_time: 9.0 * 3600.0,
|
||||
admins: vec!["singleplayer".to_string()], /* TODO: Let the player choose if they want
|
||||
* to use admin commands or not */
|
||||
persistence_db_dir,
|
||||
max_view_distance: None,
|
||||
client_timeout: Duration::from_secs(180),
|
||||
..load // Fill in remaining fields from server_settings.ron.
|
||||
}
|
||||
}
|
||||
|
||||
fn get_settings_path() -> PathBuf { PathBuf::from(r"server_settings.ron") }
|
||||
fn get_settings_path(path: &Path) -> PathBuf {
|
||||
let mut path = with_config_dir(path);
|
||||
path.push(SETTINGS_FILENAME);
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
pub fn edit<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
let r = f(self);
|
||||
self.save_to_file()
|
||||
.unwrap_or_else(|err| warn!("Failed to save settings: {:?}", err));
|
||||
r
|
||||
fn with_config_dir(path: &Path) -> PathBuf {
|
||||
let mut path = PathBuf::from(path);
|
||||
//if let Some(path) = std::env::var_os(CONFIG_DIR_ENV) {
|
||||
// let config_dir = PathBuf::from(path);
|
||||
// if config_dir.exists() {
|
||||
// return config_dir;
|
||||
// }
|
||||
// warn!(?path, "VELROREN_SERVER_CONFIG points to invalid path.");
|
||||
//}
|
||||
path.push(/* DEFAULT_ */ CONFIG_DIR);
|
||||
//PathBuf::from(DEFAULT_CONFIG_DIR)
|
||||
path
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(transparent)]
|
||||
pub struct Whitelist(Vec<String>);
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[serde(transparent)]
|
||||
pub struct Banlist(HashMap<Uuid, (String, String)>);
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ServerDescription(String);
|
||||
|
||||
impl Default for ServerDescription {
|
||||
fn default() -> Self { Self("This is the best Veloren server".into()) }
|
||||
}
|
||||
|
||||
impl EditableSetting for Whitelist {
|
||||
const FILENAME: &'static str = WHITELIST_FILENAME;
|
||||
}
|
||||
|
||||
impl EditableSetting for Banlist {
|
||||
const FILENAME: &'static str = BANLIST_FILENAME;
|
||||
}
|
||||
|
||||
impl EditableSetting for ServerDescription {
|
||||
const FILENAME: &'static str = SERVER_DESCRIPTION_FILENAME;
|
||||
}
|
||||
|
||||
impl Deref for Whitelist {
|
||||
type Target = Vec<String>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl DerefMut for Whitelist {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl Deref for Banlist {
|
||||
type Target = HashMap<Uuid, (String, String)>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl DerefMut for Banlist {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl Deref for ServerDescription {
|
||||
type Target = String;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl DerefMut for ServerDescription {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
89
server/src/settings/editable.rs
Normal file
89
server/src/settings/editable.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tracing::{error, warn};
|
||||
|
||||
pub trait EditableSetting: Serialize + DeserializeOwned + Default {
|
||||
const FILENAME: &'static str;
|
||||
|
||||
fn load(data_dir: &Path) -> Self {
|
||||
let path = Self::get_path(data_dir);
|
||||
|
||||
if let Ok(file) = fs::File::open(&path) {
|
||||
match ron::de::from_reader(file) {
|
||||
Ok(setting) => setting,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
?e,
|
||||
"Failed to parse setting file! Falling back to default and moving \
|
||||
existing file to a .invalid"
|
||||
);
|
||||
|
||||
// Rename existing file to .invalid.ron
|
||||
let mut new_path = path.with_extension("invalid.ron");
|
||||
|
||||
// If invalid path already exists append number
|
||||
for i in 1.. {
|
||||
if !new_path.exists() {
|
||||
break;
|
||||
}
|
||||
|
||||
warn!(
|
||||
?new_path,
|
||||
"Path to move invalid settings exists, appending number"
|
||||
);
|
||||
new_path = path.with_extension(format!("invalid{}.ron", i));
|
||||
}
|
||||
|
||||
warn!("Renaming invalid settings file to: {}", path.display());
|
||||
if let Err(e) = fs::rename(&path, &new_path) {
|
||||
warn!(?e, ?path, ?new_path, "Failed to rename settings file.");
|
||||
}
|
||||
|
||||
create_and_save_default(&path)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
create_and_save_default(&path)
|
||||
}
|
||||
}
|
||||
|
||||
fn edit<R>(&mut self, data_dir: &Path, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
let path = Self::get_path(data_dir);
|
||||
|
||||
let r = f(self);
|
||||
save_to_file(&*self, &path)
|
||||
.unwrap_or_else(|err| warn!("Failed to save setting: {:?}", err));
|
||||
r
|
||||
}
|
||||
|
||||
fn get_path(data_dir: &Path) -> PathBuf {
|
||||
let mut path = super::with_config_dir(data_dir);
|
||||
path.push(Self::FILENAME);
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
fn save_to_file<S: Serialize>(setting: &S, path: &Path) -> std::io::Result<()> {
|
||||
// Create dir if it doesn't exist
|
||||
if let Some(dir) = path.parent() {
|
||||
fs::create_dir_all(dir)?;
|
||||
}
|
||||
|
||||
let ron = ron::ser::to_string_pretty(setting, ron::ser::PrettyConfig::default())
|
||||
.expect("Failed serialize setting.");
|
||||
|
||||
fs::write(path, ron.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_and_save_default<S: EditableSetting>(path: &Path) -> S {
|
||||
let default = S::default();
|
||||
if let Err(e) = save_to_file(&default, path) {
|
||||
error!(?e, "Failed to create default setting file!");
|
||||
}
|
||||
default
|
||||
}
|
@ -6,6 +6,7 @@ use crate::{
|
||||
login_provider::LoginProvider,
|
||||
metrics::{NetworkRequestMetrics, PlayerMetrics},
|
||||
persistence::character_loader::CharacterLoader,
|
||||
settings::{Banlist, ServerDescription, Whitelist},
|
||||
ServerSettings,
|
||||
};
|
||||
use common::{
|
||||
@ -66,6 +67,9 @@ impl Sys {
|
||||
controllers: &mut WriteStorage<'_, Controller>,
|
||||
settings: &Read<'_, ServerSettings>,
|
||||
alias_validator: &ReadExpect<'_, AliasValidator>,
|
||||
whitelist: &Whitelist,
|
||||
banlist: &Banlist,
|
||||
server_description: &ServerDescription,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
loop {
|
||||
let msg = client.recv().await?;
|
||||
@ -96,11 +100,8 @@ impl Sys {
|
||||
view_distance,
|
||||
token_or_username,
|
||||
} => {
|
||||
let (username, uuid) = match login_provider.try_login(
|
||||
&token_or_username,
|
||||
&settings.whitelist,
|
||||
&settings.banlist,
|
||||
) {
|
||||
let (username, uuid) =
|
||||
match login_provider.try_login(&token_or_username, &whitelist, &banlist) {
|
||||
Err(err) => {
|
||||
client.error_state(RequestStateError::RegisterDenied(err));
|
||||
break Ok(());
|
||||
@ -206,10 +207,9 @@ impl Sys {
|
||||
});
|
||||
|
||||
// Give the player a welcome message
|
||||
if !settings.server_description.is_empty() {
|
||||
if !server_description.is_empty() {
|
||||
client.notify(
|
||||
ChatType::CommandInfo
|
||||
.server_msg(settings.server_description.clone()),
|
||||
ChatType::CommandInfo.server_msg(String::from(&**server_description)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -450,6 +450,11 @@ impl<'a> System<'a> for Sys {
|
||||
WriteStorage<'a, Controller>,
|
||||
Read<'a, ServerSettings>,
|
||||
ReadExpect<'a, AliasValidator>,
|
||||
(
|
||||
ReadExpect<'a, Whitelist>,
|
||||
ReadExpect<'a, Banlist>,
|
||||
ReadExpect<'a, ServerDescription>
|
||||
),
|
||||
);
|
||||
|
||||
#[allow(clippy::match_ref_pats)] // TODO: Pending review in #587
|
||||
@ -483,6 +488,11 @@ impl<'a> System<'a> for Sys {
|
||||
mut controllers,
|
||||
settings,
|
||||
alias_validator,
|
||||
(
|
||||
whitelist,
|
||||
banlist,
|
||||
server_description,
|
||||
),
|
||||
): Self::SystemData,
|
||||
) {
|
||||
span!(_guard, "run", "message::Sys::run");
|
||||
@ -543,6 +553,9 @@ impl<'a> System<'a> for Sys {
|
||||
&mut controllers,
|
||||
&settings,
|
||||
&alias_validator,
|
||||
&whitelist,
|
||||
&banlist,
|
||||
&server_description,
|
||||
);
|
||||
select!(
|
||||
_ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()),
|
||||
|
@ -53,6 +53,7 @@ chrono = "0.4.9"
|
||||
cpal = "0.11"
|
||||
crossbeam = "=0.7.2"
|
||||
deunicode = "1.0"
|
||||
# TODO: remove
|
||||
directories-next = "1.0.1"
|
||||
dot_vox = "4.0"
|
||||
enum-iterator = "0.6"
|
||||
|
@ -1,9 +1,8 @@
|
||||
use crate::hud;
|
||||
use crate::{hud, settings};
|
||||
use common::character::CharacterId;
|
||||
use directories_next::ProjectDirs;
|
||||
use hashbrown::HashMap;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{fs, io::prelude::*, path::PathBuf};
|
||||
use std::{fs, io::Write, path::PathBuf};
|
||||
use tracing::warn;
|
||||
|
||||
/// Represents a character in the profile.
|
||||
@ -177,13 +176,9 @@ impl Profile {
|
||||
warn!(?path, "VOXYGEN_CONFIG points to invalid path.");
|
||||
}
|
||||
|
||||
let proj_dirs = ProjectDirs::from("net", "veloren", "voxygen")
|
||||
.expect("System's $HOME directory path not found!");
|
||||
|
||||
proj_dirs
|
||||
.config_dir()
|
||||
.join("profile.ron")
|
||||
.with_extension("ron")
|
||||
let mut path = settings::voxygen_data_dir();
|
||||
path.push("profile.ron");
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
ui::ScaleMode,
|
||||
window::{FullScreenSettings, GameInput, KeyMouse},
|
||||
};
|
||||
use directories_next::{ProjectDirs, UserDirs};
|
||||
use directories_next::UserDirs;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{fs, io::prelude::*, path::PathBuf};
|
||||
@ -584,18 +584,18 @@ pub struct Log {
|
||||
}
|
||||
|
||||
impl Default for Log {
|
||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||
fn default() -> Self {
|
||||
let proj_dirs = ProjectDirs::from("net", "veloren", "voxygen")
|
||||
.expect("System's $HOME directory path not found!");
|
||||
|
||||
// Chooses a path to store the logs by the following order:
|
||||
// - The VOXYGEN_LOGS environment variable
|
||||
// - The ProjectsDirs data local directory
|
||||
// This only selects if there isn't already an entry in the settings file
|
||||
let logs_path = std::env::var_os("VOXYGEN_LOGS")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or(proj_dirs.data_local_dir().join("logs"));
|
||||
.unwrap_or_else(|| {
|
||||
let mut path = voxygen_data_dir();
|
||||
path.push("logs");
|
||||
path
|
||||
});
|
||||
|
||||
Self {
|
||||
log_to_file: true,
|
||||
@ -718,8 +718,6 @@ pub struct Settings {
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||
|
||||
fn default() -> Self {
|
||||
let user_dirs = UserDirs::new().expect("System's $HOME directory path not found!");
|
||||
|
||||
@ -730,10 +728,12 @@ impl Default for Settings {
|
||||
// This only selects if there isn't already an entry in the settings file
|
||||
let screenshots_path = std::env::var_os("VOXYGEN_SCREENSHOT")
|
||||
.map(PathBuf::from)
|
||||
.or(user_dirs.picture_dir().map(|dir| dir.join("veloren")))
|
||||
.or(std::env::current_exe()
|
||||
.or_else(|| user_dirs.picture_dir().map(|dir| dir.join("veloren")))
|
||||
.or_else(|| {
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|dir| dir.parent().map(PathBuf::from)))
|
||||
.and_then(|dir| dir.parent().map(PathBuf::from))
|
||||
})
|
||||
.expect("Couldn't choose a place to store the screenshots");
|
||||
|
||||
Settings {
|
||||
@ -766,7 +766,7 @@ impl Settings {
|
||||
let mut new_path = path.to_owned();
|
||||
new_path.pop();
|
||||
new_path.push("settings.invalid.ron");
|
||||
if let Err(e) = std::fs::rename(path.clone(), new_path.clone()) {
|
||||
if let Err(e) = std::fs::rename(&path, &new_path) {
|
||||
warn!(?e, ?path, ?new_path, "Failed to rename settings file.");
|
||||
}
|
||||
},
|
||||
@ -800,18 +800,23 @@ impl Settings {
|
||||
|
||||
pub fn get_settings_path() -> PathBuf {
|
||||
if let Some(path) = std::env::var_os("VOXYGEN_CONFIG") {
|
||||
let settings = PathBuf::from(path.clone()).join("settings.ron");
|
||||
let settings = PathBuf::from(&path).join("settings.ron");
|
||||
if settings.exists() || settings.parent().map(|x| x.exists()).unwrap_or(false) {
|
||||
return settings;
|
||||
}
|
||||
warn!(?path, "VOXYGEN_CONFIG points to invalid path.");
|
||||
}
|
||||
|
||||
let proj_dirs = ProjectDirs::from("net", "veloren", "voxygen")
|
||||
.expect("System's $HOME directory path not found!");
|
||||
proj_dirs
|
||||
.config_dir()
|
||||
.join("settings")
|
||||
.with_extension("ron")
|
||||
let mut path = voxygen_data_dir();
|
||||
path.push("settings.ron");
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
pub fn voxygen_data_dir() -> PathBuf {
|
||||
// Note: since voxygen is technically a lib we made need to lift this up to
|
||||
// run.rs
|
||||
let mut path = common::userdata_dir_workspace!();
|
||||
path.push("voxygen");
|
||||
path
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use client::Client;
|
||||
use common::clock::Clock;
|
||||
use crossbeam::channel::{bounded, unbounded, Receiver, Sender, TryRecvError};
|
||||
use server::{Error as ServerError, Event, Input, Server, ServerSettings};
|
||||
use server::{DataDir, Error as ServerError, Event, Input, Server, ServerSettings};
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@ -32,15 +32,15 @@ impl Singleplayer {
|
||||
pub fn new(client: Option<&Client>) -> (Self, ServerSettings) {
|
||||
let (sender, receiver) = unbounded();
|
||||
|
||||
// Determine folder to save server data in
|
||||
let server_data_dir = DataDir::from({
|
||||
let mut path = common::userdata_dir_workspace!();
|
||||
path.push("singleplayer");
|
||||
path
|
||||
});
|
||||
|
||||
// Create server
|
||||
let settings = ServerSettings::singleplayer(
|
||||
crate::settings::Settings::get_settings_path()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("saves")
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
let settings = ServerSettings::singleplayer(server_data_dir.as_ref());
|
||||
|
||||
let thread_pool = client.map(|c| c.thread_pool().clone());
|
||||
let settings2 = settings.clone();
|
||||
@ -52,7 +52,7 @@ impl Singleplayer {
|
||||
|
||||
let thread = thread::spawn(move || {
|
||||
let mut server = None;
|
||||
if let Err(e) = result_sender.send(match Server::new(settings2) {
|
||||
if let Err(e) = result_sender.send(match Server::new(settings2, server_data_dir) {
|
||||
Ok(s) => {
|
||||
server = Some(s);
|
||||
Ok(())
|
||||
|
Loading…
Reference in New Issue
Block a user