441 lines
12 KiB
Rust
441 lines
12 KiB
Rust
use core::ops::Deref;
|
|
use diesel;
|
|
use diesel::prelude::*;
|
|
use regex::Regex;
|
|
use serde_json;
|
|
use std::fs;
|
|
use std::io::Read;
|
|
use std::path;
|
|
use toml;
|
|
|
|
use db::DB;
|
|
use db::ConnectionSource;
|
|
use db::{ddns_config, misc_settings, mount_points, users};
|
|
use ddns::DDNSConfig;
|
|
use errors::*;
|
|
use user::*;
|
|
use vfs::MountPoint;
|
|
|
|
#[derive(Debug, Queryable)]
|
|
pub struct MiscSettings {
|
|
id: i32,
|
|
pub auth_secret: String,
|
|
pub index_sleep_duration_seconds: i32,
|
|
pub index_album_art_pattern: String,
|
|
pub prefix_url: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
pub struct ConfigUser {
|
|
pub name: String,
|
|
pub password: String,
|
|
pub admin: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
pub struct Config {
|
|
pub album_art_pattern: Option<String>,
|
|
pub reindex_every_n_seconds: Option<i32>,
|
|
pub mount_dirs: Option<Vec<MountPoint>>,
|
|
pub prefix_url: Option<String>,
|
|
pub users: Option<Vec<ConfigUser>>,
|
|
pub ydns: Option<DDNSConfig>,
|
|
}
|
|
|
|
impl Config {
|
|
fn clean_paths(&mut self) -> Result<()> {
|
|
if let Some(ref mut mount_dirs) = self.mount_dirs {
|
|
for mount_dir in mount_dirs {
|
|
match clean_path_string(&mount_dir.source).to_str() {
|
|
Some(p) => mount_dir.source = p.to_owned(),
|
|
_ => bail!("Bad mount directory path"),
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn parse_json(content: &str) -> Result<Config> {
|
|
let mut config = serde_json::from_str::<Config>(content)?;
|
|
config.clean_paths()?;
|
|
Ok(config)
|
|
}
|
|
|
|
pub fn parse_toml_file(path: &path::Path) -> Result<Config> {
|
|
info!("Config file path: {}", path.to_string_lossy());
|
|
let mut config_file = fs::File::open(path)?;
|
|
let mut config_file_content = String::new();
|
|
config_file.read_to_string(&mut config_file_content)?;
|
|
let mut config = toml::de::from_str::<Config>(&config_file_content)?;
|
|
config.clean_paths()?;
|
|
Ok(config)
|
|
}
|
|
|
|
pub fn read<T>(db: &T) -> Result<Config>
|
|
where T: ConnectionSource
|
|
{
|
|
use self::misc_settings::dsl::*;
|
|
use self::mount_points::dsl::*;
|
|
use self::ddns_config::dsl::*;
|
|
|
|
let connection = db.get_connection();
|
|
|
|
let mut config = Config {
|
|
album_art_pattern: None,
|
|
reindex_every_n_seconds: None,
|
|
mount_dirs: None,
|
|
prefix_url: None,
|
|
users: None,
|
|
ydns: None,
|
|
};
|
|
|
|
let (art_pattern, sleep_duration, url) = misc_settings
|
|
.select((index_album_art_pattern, index_sleep_duration_seconds, prefix_url))
|
|
.get_result(connection.deref())?;
|
|
config.album_art_pattern = Some(art_pattern);
|
|
config.reindex_every_n_seconds = Some(sleep_duration);
|
|
config.prefix_url = if url != "" { Some(url) } else { None };
|
|
|
|
let mount_dirs = mount_points
|
|
.select((source, name))
|
|
.get_results(connection.deref())?;
|
|
config.mount_dirs = Some(mount_dirs);
|
|
|
|
let found_users: Vec<(String, i32)> = users::table
|
|
.select((users::columns::name, users::columns::admin))
|
|
.get_results(connection.deref())?;
|
|
config.users = Some(found_users
|
|
.into_iter()
|
|
.map(|(n, a)| {
|
|
ConfigUser {
|
|
name: n,
|
|
password: "".to_owned(),
|
|
admin: a != 0,
|
|
}
|
|
})
|
|
.collect::<_>());
|
|
|
|
let ydns = ddns_config
|
|
.select((host, username, password))
|
|
.get_result(connection.deref())?;
|
|
config.ydns = Some(ydns);
|
|
|
|
Ok(config)
|
|
}
|
|
|
|
fn reset<T>(db: &T) -> Result<()>
|
|
where T: ConnectionSource
|
|
{
|
|
use self::ddns_config::dsl::*;
|
|
let connection = db.get_connection();
|
|
|
|
diesel::delete(mount_points::table)
|
|
.execute(connection.deref())?;
|
|
diesel::delete(users::table).execute(connection.deref())?;
|
|
diesel::update(ddns_config)
|
|
.set((host.eq(""), username.eq(""), password.eq("")))
|
|
.execute(connection.deref())?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn overwrite<T>(db: &T, new_config: &Config) -> Result<()>
|
|
where T: ConnectionSource
|
|
{
|
|
reset(db)?;
|
|
amend(db, new_config)
|
|
}
|
|
|
|
pub fn amend<T>(db: &T, new_config: &Config) -> Result<()>
|
|
where T: ConnectionSource
|
|
{
|
|
let connection = db.get_connection();
|
|
|
|
if let Some(ref mount_dirs) = new_config.mount_dirs {
|
|
diesel::delete(mount_points::table)
|
|
.execute(connection.deref())?;
|
|
diesel::insert_into(mount_points::table)
|
|
.values(mount_dirs)
|
|
.execute(connection.deref())?;
|
|
}
|
|
|
|
if let Some(ref config_users) = new_config.users {
|
|
let old_usernames: Vec<String> = users::table
|
|
.select(users::name)
|
|
.get_results(connection.deref())?;
|
|
|
|
// Delete users that are not in new list
|
|
// Delete users that have a new password
|
|
let delete_usernames: Vec<String> = old_usernames
|
|
.into_iter()
|
|
.filter(|old_name| match config_users.iter().find(|u| &u.name == old_name) {
|
|
None => true,
|
|
Some(new_user) => !new_user.password.is_empty(),
|
|
})
|
|
.collect::<_>();
|
|
diesel::delete(users::table.filter(users::name.eq_any(&delete_usernames)))
|
|
.execute(connection.deref())?;
|
|
|
|
// Insert users that have a new password
|
|
let insert_users: Vec<&ConfigUser> = config_users
|
|
.iter()
|
|
.filter(|u| !u.password.is_empty())
|
|
.collect::<_>();
|
|
for ref config_user in insert_users {
|
|
let new_user = User::new(&config_user.name, &config_user.password, config_user.admin);
|
|
diesel::insert_into(users::table)
|
|
.values(&new_user)
|
|
.execute(connection.deref())?;
|
|
}
|
|
|
|
// Grant admin rights
|
|
for ref user in config_users {
|
|
diesel::update(users::table.filter(users::name.eq(&user.name)))
|
|
.set(users::admin.eq(user.admin as i32))
|
|
.execute(connection.deref())?;
|
|
}
|
|
}
|
|
|
|
if let Some(sleep_duration) = new_config.reindex_every_n_seconds {
|
|
diesel::update(misc_settings::table)
|
|
.set(misc_settings::index_sleep_duration_seconds.eq(sleep_duration as i32))
|
|
.execute(connection.deref())?;
|
|
}
|
|
|
|
if let Some(ref album_art_pattern) = new_config.album_art_pattern {
|
|
diesel::update(misc_settings::table)
|
|
.set(misc_settings::index_album_art_pattern.eq(album_art_pattern))
|
|
.execute(connection.deref())?;
|
|
}
|
|
|
|
if let Some(ref ydns) = new_config.ydns {
|
|
use self::ddns_config::dsl::*;
|
|
diesel::update(ddns_config)
|
|
.set((host.eq(ydns.host.clone()),
|
|
username.eq(ydns.username.clone()),
|
|
password.eq(ydns.password.clone())))
|
|
.execute(connection.deref())?;
|
|
}
|
|
|
|
if let Some(ref prefix_url) = new_config.prefix_url {
|
|
diesel::update(misc_settings::table)
|
|
.set(misc_settings::prefix_url.eq(prefix_url))
|
|
.execute(connection.deref())?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn clean_path_string(path_string: &str) -> path::PathBuf {
|
|
let separator_regex = Regex::new(r"\\|/").unwrap();
|
|
let mut correct_separator = String::new();
|
|
correct_separator.push(path::MAIN_SEPARATOR);
|
|
let path_string = separator_regex.replace_all(path_string, correct_separator.as_str());
|
|
path::Path::new(path_string.deref()).iter().collect()
|
|
}
|
|
|
|
fn _get_test_db(name: &str) -> DB {
|
|
let mut db_path = path::PathBuf::new();
|
|
db_path.push("test");
|
|
db_path.push(name);
|
|
if db_path.exists() {
|
|
fs::remove_file(&db_path).unwrap();
|
|
}
|
|
|
|
let db = DB::new(&db_path).unwrap();
|
|
db
|
|
}
|
|
|
|
#[test]
|
|
fn test_amend() {
|
|
let db = _get_test_db("amend.sqlite");
|
|
|
|
let initial_config = Config {
|
|
album_art_pattern: Some("file\\.png".into()),
|
|
reindex_every_n_seconds: Some(123),
|
|
prefix_url: None,
|
|
mount_dirs: Some(vec![MountPoint {
|
|
source: "C:\\Music".into(),
|
|
name: "root".into(),
|
|
}]),
|
|
users: Some(vec![ConfigUser {
|
|
name: "Teddy🐻".into(),
|
|
password: "Tasty🍖".into(),
|
|
admin: false,
|
|
}]),
|
|
ydns: None,
|
|
};
|
|
|
|
let new_config = Config {
|
|
album_art_pattern: Some("🖼️\\.jpg".into()),
|
|
reindex_every_n_seconds: None,
|
|
prefix_url: Some("polaris".into()),
|
|
mount_dirs: Some(vec![MountPoint {
|
|
source: "/home/music".into(),
|
|
name: "🎵📁".into(),
|
|
}]),
|
|
users: Some(vec![ConfigUser {
|
|
name: "Kermit🐸".into(),
|
|
password: "🐞🐞".into(),
|
|
admin: false,
|
|
}]),
|
|
ydns: Some(DDNSConfig {
|
|
host: "🐸🐸🐸.ydns.eu".into(),
|
|
username: "kfr🐸g".into(),
|
|
password: "tasty🐞".into(),
|
|
}),
|
|
};
|
|
|
|
let mut expected_config = new_config.clone();
|
|
expected_config.reindex_every_n_seconds = initial_config.reindex_every_n_seconds;
|
|
if let Some(ref mut users) = expected_config.users {
|
|
users[0].password = "".into();
|
|
}
|
|
|
|
amend(&db, &initial_config).unwrap();
|
|
amend(&db, &new_config).unwrap();
|
|
let db_config = read(&db).unwrap();
|
|
assert_eq!(db_config, expected_config);
|
|
}
|
|
|
|
#[test]
|
|
fn test_amend_preserve_password_hashes() {
|
|
use self::users::dsl::*;
|
|
|
|
let db = _get_test_db("amend_preserve_password_hashes.sqlite");
|
|
let initial_hash: Vec<u8>;
|
|
let new_hash: Vec<u8>;
|
|
|
|
let initial_config = Config {
|
|
album_art_pattern: None,
|
|
reindex_every_n_seconds: None,
|
|
prefix_url: None,
|
|
mount_dirs: None,
|
|
users: Some(vec![ConfigUser {
|
|
name: "Teddy🐻".into(),
|
|
password: "Tasty🍖".into(),
|
|
admin: false,
|
|
}]),
|
|
ydns: None,
|
|
};
|
|
amend(&db, &initial_config).unwrap();
|
|
|
|
{
|
|
let connection = db.get_connection();
|
|
initial_hash = users
|
|
.select(password_hash)
|
|
.filter(name.eq("Teddy🐻"))
|
|
.get_result(connection.deref())
|
|
.unwrap();
|
|
}
|
|
|
|
let new_config = Config {
|
|
album_art_pattern: None,
|
|
reindex_every_n_seconds: None,
|
|
prefix_url: None,
|
|
mount_dirs: None,
|
|
users: Some(vec![ConfigUser {
|
|
name: "Kermit🐸".into(),
|
|
password: "tasty🐞".into(),
|
|
admin: false,
|
|
},
|
|
ConfigUser {
|
|
name: "Teddy🐻".into(),
|
|
password: "".into(),
|
|
admin: false,
|
|
}]),
|
|
ydns: None,
|
|
};
|
|
amend(&db, &new_config).unwrap();
|
|
|
|
{
|
|
let connection = db.get_connection();
|
|
new_hash = users
|
|
.select(password_hash)
|
|
.filter(name.eq("Teddy🐻"))
|
|
.get_result(connection.deref())
|
|
.unwrap();
|
|
}
|
|
|
|
assert_eq!(new_hash, initial_hash);
|
|
}
|
|
|
|
|
|
#[test]
|
|
fn test_toggle_admin() {
|
|
use self::users::dsl::*;
|
|
|
|
let db = _get_test_db("amend_toggle_admin.sqlite");
|
|
|
|
let initial_config = Config {
|
|
album_art_pattern: None,
|
|
reindex_every_n_seconds: None,
|
|
prefix_url: None,
|
|
mount_dirs: None,
|
|
users: Some(vec![ConfigUser {
|
|
name: "Teddy🐻".into(),
|
|
password: "Tasty🍖".into(),
|
|
admin: true,
|
|
}]),
|
|
ydns: None,
|
|
};
|
|
amend(&db, &initial_config).unwrap();
|
|
|
|
{
|
|
let connection = db.get_connection();
|
|
let is_admin: i32 = users
|
|
.select(admin)
|
|
.get_result(connection.deref())
|
|
.unwrap();
|
|
assert_eq!(is_admin, 1);
|
|
}
|
|
|
|
let new_config = Config {
|
|
album_art_pattern: None,
|
|
reindex_every_n_seconds: None,
|
|
prefix_url: None,
|
|
mount_dirs: None,
|
|
users: Some(vec![ConfigUser {
|
|
name: "Teddy🐻".into(),
|
|
password: "".into(),
|
|
admin: false,
|
|
}]),
|
|
ydns: None,
|
|
};
|
|
amend(&db, &new_config).unwrap();
|
|
|
|
{
|
|
let connection = db.get_connection();
|
|
let is_admin: i32 = users
|
|
.select(admin)
|
|
.get_result(connection.deref())
|
|
.unwrap();
|
|
assert_eq!(is_admin, 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_clean_path_string() {
|
|
let mut correct_path = path::PathBuf::new();
|
|
if cfg!(target_os = "windows") {
|
|
correct_path.push("C:\\");
|
|
} else {
|
|
correct_path.push("/usr");
|
|
}
|
|
correct_path.push("some");
|
|
correct_path.push("path");
|
|
if cfg!(target_os = "windows") {
|
|
assert_eq!(correct_path, clean_path_string(r#"C:/some/path"#));
|
|
assert_eq!(correct_path, clean_path_string(r#"C:\some\path"#));
|
|
assert_eq!(correct_path, clean_path_string(r#"C:\some\path\"#));
|
|
assert_eq!(correct_path, clean_path_string(r#"C:\some\path\\\\"#));
|
|
assert_eq!(correct_path, clean_path_string(r#"C:\some/path//"#));
|
|
} else {
|
|
assert_eq!(correct_path, clean_path_string(r#"/usr/some/path"#));
|
|
assert_eq!(correct_path, clean_path_string(r#"/usr\some\path"#));
|
|
assert_eq!(correct_path, clean_path_string(r#"/usr\some\path\"#));
|
|
assert_eq!(correct_path, clean_path_string(r#"/usr\some\path\\\\"#));
|
|
assert_eq!(correct_path, clean_path_string(r#"/usr\some/path//"#));
|
|
}
|
|
}
|