Write config changes to disk
This commit is contained in:
parent
fb18cb3c4f
commit
5ec0b5f7a5
7 changed files with 86 additions and 36 deletions
|
@ -87,6 +87,8 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("Could not deserialize configuration: `{0}`")]
|
#[error("Could not deserialize configuration: `{0}`")]
|
||||||
ConfigDeserialization(toml::de::Error),
|
ConfigDeserialization(toml::de::Error),
|
||||||
|
#[error("Could not serialize configuration: `{0}`")]
|
||||||
|
ConfigSerialization(toml::ser::Error),
|
||||||
#[error("Could not deserialize collection")]
|
#[error("Could not deserialize collection")]
|
||||||
IndexDeserializationError,
|
IndexDeserializationError,
|
||||||
#[error("Could not serialize collection")]
|
#[error("Could not serialize collection")]
|
||||||
|
|
|
@ -114,8 +114,32 @@ impl Manager {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub async fn apply(&self, config: storage::Config) -> Result<(), Error> {
|
pub async fn apply(&self, config: storage::Config) -> Result<(), Error> {
|
||||||
*self.config.write().await = config.try_into()?;
|
self.mutate_fallible(|c| {
|
||||||
// TODO persistence
|
*c = config.try_into()?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn mutate<F: FnOnce(&mut Config)>(&self, op: F) -> Result<(), Error> {
|
||||||
|
self.mutate_fallible(|c| {
|
||||||
|
op(c);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn mutate_fallible<F: FnOnce(&mut Config) -> Result<(), Error>>(
|
||||||
|
&self,
|
||||||
|
op: F,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut config = self.config.write().await;
|
||||||
|
op(&mut config)?;
|
||||||
|
let serialized = toml::ser::to_string_pretty::<storage::Config>(&config.clone().into())
|
||||||
|
.map_err(Error::ConfigSerialization)?;
|
||||||
|
tokio::fs::write(&self.config_file_path, serialized.as_bytes())
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::Io(self.config_file_path.clone(), e))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,10 +149,11 @@ impl Manager {
|
||||||
Duration::from_secs(seconds)
|
Duration::from_secs(seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_index_sleep_duration(&self, duration: Duration) {
|
pub async fn set_index_sleep_duration(&self, duration: Duration) -> Result<(), Error> {
|
||||||
let mut config = self.config.write().await;
|
self.mutate(|c| {
|
||||||
config.reindex_every_n_seconds = Some(duration.as_secs());
|
c.reindex_every_n_seconds = Some(duration.as_secs());
|
||||||
// TODO persistence
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_index_album_art_pattern(&self) -> Regex {
|
pub async fn get_index_album_art_pattern(&self) -> Regex {
|
||||||
|
@ -137,20 +162,22 @@ impl Manager {
|
||||||
pattern.unwrap_or_else(|| Regex::new("Folder.(jpeg|jpg|png)").unwrap())
|
pattern.unwrap_or_else(|| Regex::new("Folder.(jpeg|jpg|png)").unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_index_album_art_pattern(&self, regex: Regex) {
|
pub async fn set_index_album_art_pattern(&self, regex: Regex) -> Result<(), Error> {
|
||||||
let mut config = self.config.write().await;
|
self.mutate(|c| {
|
||||||
config.album_art_pattern = Some(regex);
|
c.album_art_pattern = Some(regex);
|
||||||
// TODO persistence
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_ddns_update_url(&self) -> Option<http::Uri> {
|
pub async fn get_ddns_update_url(&self) -> Option<http::Uri> {
|
||||||
self.config.read().await.ddns_url.clone()
|
self.config.read().await.ddns_url.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_ddns_update_url(&self, url: http::Uri) {
|
pub async fn set_ddns_update_url(&self, url: http::Uri) -> Result<(), Error> {
|
||||||
let mut config = self.config.write().await;
|
self.mutate(|c| {
|
||||||
config.ddns_url = Some(url);
|
c.ddns_url = Some(url);
|
||||||
// TODO persistence
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_users(&self) -> Vec<User> {
|
pub async fn get_users(&self) -> Vec<User> {
|
||||||
|
@ -169,9 +196,8 @@ impl Manager {
|
||||||
password: &str,
|
password: &str,
|
||||||
admin: bool,
|
admin: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut config = self.config.write().await;
|
self.mutate_fallible(|c| c.create_user(username, password, admin))
|
||||||
config.create_user(username, password, admin)
|
.await
|
||||||
// TODO persistence
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login(&self, username: &str, password: &str) -> Result<auth::Token, Error> {
|
pub async fn login(&self, username: &str, password: &str) -> Result<auth::Token, Error> {
|
||||||
|
@ -180,15 +206,13 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_is_admin(&self, username: &str, is_admin: bool) -> Result<(), Error> {
|
pub async fn set_is_admin(&self, username: &str, is_admin: bool) -> Result<(), Error> {
|
||||||
let mut config = self.config.write().await;
|
self.mutate_fallible(|c| c.set_is_admin(username, is_admin))
|
||||||
config.set_is_admin(username, is_admin)
|
.await
|
||||||
// TODO persistence
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_password(&self, username: &str, password: &str) -> Result<(), Error> {
|
pub async fn set_password(&self, username: &str, password: &str) -> Result<(), Error> {
|
||||||
let mut config = self.config.write().await;
|
self.mutate_fallible(|c| c.set_password(username, password))
|
||||||
config.set_password(username, password)
|
.await
|
||||||
// TODO persistence
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn authenticate(
|
pub async fn authenticate(
|
||||||
|
@ -200,10 +224,8 @@ impl Manager {
|
||||||
config.authenticate(auth_token, scope, &self.auth_secret)
|
config.authenticate(auth_token, scope, &self.auth_secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_user(&self, username: &str) {
|
pub async fn delete_user(&self, username: &str) -> Result<(), Error> {
|
||||||
let mut config = self.config.write().await;
|
self.mutate(|c| c.delete_user(username)).await
|
||||||
config.delete_user(username);
|
|
||||||
// TODO persistence
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_mounts(&self) -> Vec<MountDir> {
|
pub async fn get_mounts(&self) -> Vec<MountDir> {
|
||||||
|
@ -220,15 +242,25 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_mounts(&self, mount_dirs: Vec<storage::MountDir>) -> Result<(), Error> {
|
pub async fn set_mounts(&self, mount_dirs: Vec<storage::MountDir>) -> Result<(), Error> {
|
||||||
self.config.write().await.set_mounts(mount_dirs)
|
self.mutate_fallible(|c| c.set_mounts(mount_dirs)).await
|
||||||
// TODO persistence
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::app::test;
|
||||||
|
use crate::test_name;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn blank_config_is_valid() {
|
||||||
|
let config_path = PathBuf::from_iter(["test-data", "blank.toml"]);
|
||||||
|
Manager::new(&config_path, auth::Secret([0; 32]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_read_config() {
|
async fn can_read_config() {
|
||||||
let config_path = PathBuf::from_iter(["test-data", "config.toml"]);
|
let config_path = PathBuf::from_iter(["test-data", "config.toml"]);
|
||||||
|
@ -257,4 +289,18 @@ mod test {
|
||||||
);
|
);
|
||||||
assert!(config.users[0].hashed_password.is_some());
|
assert!(config.users[0].hashed_password.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_write_config() {
|
||||||
|
let ctx = test::ContextBuilder::new(test_name!()).build().await;
|
||||||
|
ctx.config_manager
|
||||||
|
.create_user("Walter", "example_password", false)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let manager = Manager::new(&ctx.config_manager.config_file_path, auth::Secret([0; 32]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(manager.get_user("Walter").await.is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,10 @@ pub struct Config {
|
||||||
pub reindex_every_n_seconds: Option<u64>,
|
pub reindex_every_n_seconds: Option<u64>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub album_art_pattern: Option<String>,
|
pub album_art_pattern: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub mount_dirs: Vec<MountDir>,
|
pub mount_dirs: Vec<MountDir>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub ddns_url: Option<String>,
|
pub ddns_url: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub users: Vec<User>,
|
pub users: Vec<User>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,7 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(ctx.config_manager.get_user(TEST_USERNAME).await.is_ok());
|
assert!(ctx.config_manager.get_user(TEST_USERNAME).await.is_ok());
|
||||||
|
|
||||||
ctx.config_manager.delete_user(TEST_USERNAME).await;
|
ctx.config_manager.delete_user(TEST_USERNAME).await.unwrap();
|
||||||
assert!(ctx.config_manager.get_user(TEST_USERNAME).await.is_err());
|
assert!(ctx.config_manager.get_user(TEST_USERNAME).await.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,20 +126,20 @@ async fn put_settings(
|
||||||
let Ok(regex) = Regex::new(&pattern) else {
|
let Ok(regex) = Regex::new(&pattern) else {
|
||||||
return Err(APIError::InvalidAlbumArtPattern);
|
return Err(APIError::InvalidAlbumArtPattern);
|
||||||
};
|
};
|
||||||
config_manager.set_index_album_art_pattern(regex).await;
|
config_manager.set_index_album_art_pattern(regex).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(seconds) = new_settings.reindex_every_n_seconds {
|
if let Some(seconds) = new_settings.reindex_every_n_seconds {
|
||||||
config_manager
|
config_manager
|
||||||
.set_index_sleep_duration(Duration::from_secs(seconds as u64))
|
.set_index_sleep_duration(Duration::from_secs(seconds as u64))
|
||||||
.await;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(url_string) = new_settings.ddns_update_url {
|
if let Some(url_string) = new_settings.ddns_update_url {
|
||||||
let Ok(uri) = http::Uri::try_from(url_string) else {
|
let Ok(uri) = http::Uri::try_from(url_string) else {
|
||||||
return Err(APIError::InvalidDDNSURL);
|
return Err(APIError::InvalidDDNSURL);
|
||||||
};
|
};
|
||||||
config_manager.set_ddns_update_url(uri).await;
|
config_manager.set_ddns_update_url(uri).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -239,7 +239,7 @@ async fn delete_user(
|
||||||
return Err(APIError::DeletingOwnAccount);
|
return Err(APIError::DeletingOwnAccount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config_manager.delete_user(&name).await;
|
config_manager.delete_user(&name).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,7 @@ impl From<app::Error> for APIError {
|
||||||
app::Error::IndexAlbumArtPatternInvalid => APIError::InvalidAlbumArtPattern,
|
app::Error::IndexAlbumArtPatternInvalid => APIError::InvalidAlbumArtPattern,
|
||||||
|
|
||||||
app::Error::ConfigDeserialization(_) => APIError::Internal,
|
app::Error::ConfigDeserialization(_) => APIError::Internal,
|
||||||
|
app::Error::ConfigSerialization(_) => APIError::Internal,
|
||||||
app::Error::IndexDeserializationError => APIError::Internal,
|
app::Error::IndexDeserializationError => APIError::Internal,
|
||||||
app::Error::IndexSerializationError => APIError::Internal,
|
app::Error::IndexSerializationError => APIError::Internal,
|
||||||
|
|
||||||
|
|
1
test-data/blank.toml
Normal file
1
test-data/blank.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
Loading…
Add table
Reference in a new issue