Preserve order of mounts points and users

This commit is contained in:
Antoine Gersant 2024-10-09 23:48:22 -07:00
parent 497b3bb545
commit 08052c25a3
4 changed files with 64 additions and 55 deletions

View file

@ -1,5 +1,4 @@
use std::{ use std::{
collections::HashMap,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
time::Duration, time::Duration,
@ -24,45 +23,33 @@ pub struct Config {
pub reindex_every_n_seconds: Option<u64>, pub reindex_every_n_seconds: Option<u64>,
pub album_art_pattern: Option<Regex>, pub album_art_pattern: Option<Regex>,
pub ddns_url: Option<http::Uri>, pub ddns_url: Option<http::Uri>,
pub mount_dirs: HashMap<String, MountDir>, pub mount_dirs: Vec<MountDir>,
pub users: HashMap<String, User>, pub users: Vec<User>,
} }
impl TryFrom<storage::Config> for Config { impl TryFrom<storage::Config> for Config {
type Error = Error; type Error = Error;
fn try_from(c: storage::Config) -> Result<Self, Self::Error> { fn try_from(c: storage::Config) -> Result<Self, Self::Error> {
let mut users: HashMap<String, User> = HashMap::new(); let mut config = Config::default();
for user in c.users { config.set_mounts(c.mount_dirs)?;
let user = <storage::User as TryInto<User>>::try_into(user)?; config.set_users(c.users)?;
users.insert(user.name.clone(), user);
}
let mut mount_dirs: HashMap<String, MountDir> = HashMap::new(); config.reindex_every_n_seconds = c.reindex_every_n_seconds;
for mount_dir in c.mount_dirs {
let mount_dir = <storage::MountDir as TryInto<MountDir>>::try_into(mount_dir)?;
mount_dirs.insert(mount_dir.name.clone(), mount_dir);
}
let ddns_url = match c.ddns_url.map(http::Uri::try_from) { config.album_art_pattern = match c.album_art_pattern.as_deref().map(Regex::new) {
Some(Ok(u)) => Some(u),
Some(Err(_)) => return Err(Error::DDNSUpdateURLInvalid),
None => None,
};
let album_art_pattern = match c.album_art_pattern.as_deref().map(Regex::new) {
Some(Ok(u)) => Some(u), Some(Ok(u)) => Some(u),
Some(Err(_)) => return Err(Error::IndexAlbumArtPatternInvalid), Some(Err(_)) => return Err(Error::IndexAlbumArtPatternInvalid),
None => None, None => None,
}; };
Ok(Config { config.ddns_url = match c.ddns_url.map(http::Uri::try_from) {
reindex_every_n_seconds: c.reindex_every_n_seconds, Some(Ok(u)) => Some(u),
album_art_pattern, Some(Err(_)) => return Err(Error::DDNSUpdateURLInvalid),
ddns_url, None => None,
mount_dirs, };
users,
}) Ok(config)
} }
} }
@ -71,9 +58,9 @@ impl From<Config> for storage::Config {
Self { Self {
reindex_every_n_seconds: c.reindex_every_n_seconds, reindex_every_n_seconds: c.reindex_every_n_seconds,
album_art_pattern: c.album_art_pattern.map(|p| p.as_str().to_owned()), album_art_pattern: c.album_art_pattern.map(|p| p.as_str().to_owned()),
mount_dirs: c.mount_dirs.into_iter().map(|(_, d)| d.into()).collect(), mount_dirs: c.mount_dirs.into_iter().map(|d| d.into()).collect(),
ddns_url: c.ddns_url.map(|u| u.to_string()), ddns_url: c.ddns_url.map(|u| u.to_string()),
users: c.users.into_iter().map(|(_, u)| u.into()).collect(), users: c.users.into_iter().map(|u| u.into()).collect(),
} }
} }
} }
@ -181,13 +168,15 @@ impl Manager {
} }
pub async fn get_users(&self) -> Vec<User> { pub async fn get_users(&self) -> Vec<User> {
self.config.read().await.users.values().cloned().collect() self.config.read().await.users.iter().cloned().collect()
} }
pub async fn get_user(&self, username: &str) -> Result<User, Error> { pub async fn get_user(&self, username: &str) -> Result<User, Error> {
let config = self.config.read().await; let config = self.config.read().await;
let user = config.users.get(username); config
user.cloned().ok_or(Error::UserNotFound) .get_user(username)
.cloned()
.ok_or(Error::UserNotFound)
} }
pub async fn create_user( pub async fn create_user(
@ -230,7 +219,7 @@ impl Manager {
pub async fn get_mounts(&self) -> Vec<MountDir> { pub async fn get_mounts(&self) -> Vec<MountDir> {
let config = self.config.read().await; let config = self.config.read().await;
config.mount_dirs.values().cloned().collect() config.mount_dirs.iter().cloned().collect()
} }
pub async fn resolve_virtual_path<P: AsRef<Path>>( pub async fn resolve_virtual_path<P: AsRef<Path>>(

View file

@ -1,5 +1,4 @@
use std::{ use std::{
collections::HashMap,
ops::Deref, ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -40,18 +39,19 @@ impl From<MountDir> for storage::MountDir {
impl Config { impl Config {
pub fn set_mounts(&mut self, mount_dirs: Vec<storage::MountDir>) -> Result<(), Error> { pub fn set_mounts(&mut self, mount_dirs: Vec<storage::MountDir>) -> Result<(), Error> {
let mut new_mount_dirs = HashMap::new(); let mut new_mount_dirs = Vec::new();
for mount_dir in mount_dirs { for mount_dir in mount_dirs {
let mount_dir = <storage::MountDir as TryInto<MountDir>>::try_into(mount_dir)?; let mount_dir = <storage::MountDir as TryInto<MountDir>>::try_into(mount_dir)?;
new_mount_dirs.insert(mount_dir.name.clone(), mount_dir); new_mount_dirs.push(mount_dir);
} }
new_mount_dirs.dedup_by(|a, b| a.name == b.name);
self.mount_dirs = new_mount_dirs; self.mount_dirs = new_mount_dirs;
Ok(()) Ok(())
} }
pub fn resolve_virtual_path<P: AsRef<Path>>(&self, virtual_path: P) -> Result<PathBuf, Error> { pub fn resolve_virtual_path<P: AsRef<Path>>(&self, virtual_path: P) -> Result<PathBuf, Error> {
for (name, mount) in &self.mount_dirs { for mount in &self.mount_dirs {
if let Ok(p) = virtual_path.as_ref().strip_prefix(name) { if let Ok(p) = virtual_path.as_ref().strip_prefix(&mount.name) {
return if p.components().count() == 0 { return if p.components().count() == 0 {
Ok(mount.source.clone()) Ok(mount.source.clone())
} else { } else {

View file

@ -48,6 +48,17 @@ impl From<User> for storage::User {
} }
impl Config { impl Config {
pub fn set_users(&mut self, users: Vec<storage::User>) -> Result<(), Error> {
let mut new_users = Vec::new();
for user in users {
let user = <storage::User as TryInto<User>>::try_into(user)?;
new_users.push(user);
}
new_users.dedup_by(|a, b| a.name == b.name);
self.users = new_users;
Ok(())
}
pub fn create_user( pub fn create_user(
&mut self, &mut self,
username: &str, username: &str,
@ -58,25 +69,34 @@ impl Config {
return Err(Error::EmptyUsername); return Err(Error::EmptyUsername);
} }
if self.users.contains_key(username) { if self.exists(username) {
return Err(Error::DuplicateUsername); return Err(Error::DuplicateUsername);
} }
let password_hash = auth::hash_password(&password)?; let password_hash = auth::hash_password(&password)?;
self.users.insert( self.users.push(User {
username.to_owned(),
User {
name: username.to_owned(), name: username.to_owned(),
admin: Some(admin), admin: Some(admin),
initial_password: None, initial_password: None,
hashed_password: password_hash, hashed_password: password_hash,
}, });
);
Ok(()) Ok(())
} }
pub fn exists(&self, username: &str) -> bool {
self.users.iter().any(|u| u.name == username)
}
pub fn get_user(&self, username: &str) -> Option<&User> {
self.users.iter().find(|u| u.name == username)
}
pub fn get_user_mut(&mut self, username: &str) -> Option<&mut User> {
self.users.iter_mut().find(|u| u.name == username)
}
pub fn authenticate( pub fn authenticate(
&self, &self,
auth_token: &auth::Token, auth_token: &auth::Token,
@ -84,7 +104,7 @@ impl Config {
auth_secret: &auth::Secret, auth_secret: &auth::Secret,
) -> Result<auth::Authorization, Error> { ) -> Result<auth::Authorization, Error> {
let authorization = auth::decode_auth_token(auth_token, scope, auth_secret)?; let authorization = auth::decode_auth_token(auth_token, scope, auth_secret)?;
if self.users.contains_key(&authorization.username) { if self.exists(&authorization.username) {
Ok(authorization) Ok(authorization)
} else { } else {
Err(Error::IncorrectUsername) Err(Error::IncorrectUsername)
@ -97,7 +117,7 @@ impl Config {
password: &str, password: &str,
auth_secret: &auth::Secret, auth_secret: &auth::Secret,
) -> Result<auth::Token, Error> { ) -> Result<auth::Token, Error> {
let user = self.users.get(username).ok_or(Error::IncorrectUsername)?; let user = self.get_user(username).ok_or(Error::IncorrectUsername)?;
if auth::verify_password(&user.hashed_password, password) { if auth::verify_password(&user.hashed_password, password) {
let authorization = auth::Authorization { let authorization = auth::Authorization {
username: username.to_owned(), username: username.to_owned(),
@ -110,19 +130,19 @@ impl Config {
} }
pub fn set_is_admin(&mut self, username: &str, is_admin: bool) -> Result<(), Error> { pub fn set_is_admin(&mut self, username: &str, is_admin: bool) -> Result<(), Error> {
let user = self.users.get_mut(username).ok_or(Error::UserNotFound)?; let user = self.get_user_mut(username).ok_or(Error::UserNotFound)?;
user.admin = Some(is_admin); user.admin = Some(is_admin);
Ok(()) Ok(())
} }
pub fn set_password(&mut self, username: &str, password: &str) -> Result<(), Error> { pub fn set_password(&mut self, username: &str, password: &str) -> Result<(), Error> {
let user = self.users.get_mut(username).ok_or(Error::UserNotFound)?; let user = self.get_user_mut(username).ok_or(Error::UserNotFound)?;
user.hashed_password = auth::hash_password(password)?; user.hashed_password = auth::hash_password(password)?;
Ok(()) Ok(())
} }
pub fn delete_user(&mut self, username: &str) { pub fn delete_user(&mut self, username: &str) {
self.users.remove(username); self.users.retain(|u| u.name != username);
} }
} }

View file

@ -64,7 +64,7 @@ async fn put_settings_golden_path() {
let request = protocol::put_settings(dto::NewSettings { let request = protocol::put_settings(dto::NewSettings {
album_art_pattern: Some("test_pattern".to_owned()), album_art_pattern: Some("test_pattern".to_owned()),
reindex_every_n_seconds: Some(31), reindex_every_n_seconds: Some(31),
ddns_update_url: Some("ddns_url".to_owned()), ddns_update_url: Some("http://example.com/".to_owned()),
}); });
let response = service.fetch(&request).await; let response = service.fetch(&request).await;
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
@ -77,7 +77,7 @@ async fn put_settings_golden_path() {
&Settings { &Settings {
album_art_pattern: "test_pattern".to_owned(), album_art_pattern: "test_pattern".to_owned(),
reindex_every_n_seconds: 31, reindex_every_n_seconds: 31,
ddns_update_url: "ddns_url".to_owned(), ddns_update_url: "http://example.com/".to_owned(),
}, },
); );
} }