Watch config file changes

This commit is contained in:
Antoine Gersant 2024-10-11 21:20:16 -07:00
parent 142d400b8b
commit d555a2e5f0
6 changed files with 246 additions and 33 deletions

186
Cargo.lock generated
View file

@ -443,6 +443,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
@ -631,6 +640,27 @@ dependencies = [
"serde",
]
[[package]]
name = "file-id"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6584280525fb2059cba3db2c04abf947a1a29a45ddae89f3870f8281704fafc9"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "filetime"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.59.0",
]
[[package]]
name = "flate2"
version = "1.0.31"
@ -656,6 +686,15 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "futures"
version = "0.3.30"
@ -985,12 +1024,52 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "inotify"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags 1.3.2",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "kqueue"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
]
[[package]]
name = "lasso2"
version = "0.8.2"
@ -1024,12 +1103,33 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.6.0",
"libc",
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.22"
@ -1094,6 +1194,18 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "mio"
version = "1.0.1"
@ -1212,6 +1324,38 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.6.0",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio 0.8.11",
"walkdir",
"windows-sys 0.48.0",
]
[[package]]
name = "notify-debouncer-full"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f5dab59c348b9b50cf7f261960a20e389feb2713636399cd9082cd4b536154"
dependencies = [
"file-id",
"log",
"notify",
"parking_lot",
"walkdir",
]
[[package]]
name = "num-complex"
version = "0.4.6"
@ -1307,6 +1451,29 @@ dependencies = [
"zeroize",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
[[package]]
name = "password-hash"
version = "0.4.2"
@ -1413,6 +1580,8 @@ dependencies = [
"native_db",
"native_model",
"nohash-hasher",
"notify",
"notify-debouncer-full",
"num_cpus",
"opus_headers",
"pbkdf2",
@ -1588,6 +1757,15 @@ dependencies = [
"libc",
]
[[package]]
name = "redox_syscall"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "regex"
version = "1.10.6"
@ -1754,6 +1932,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sd-notify"
version = "0.4.2"
@ -2272,7 +2456,7 @@ dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"mio 1.0.1",
"pin-project-lite",
"socket2",
"tokio-macros",

View file

@ -32,6 +32,8 @@ mp4ameta = "0.11.0"
native_db = { git = "https://github.com/vincent-herlemont/native_db" }
native_model = "0.4.19"
nohash-hasher = "0.2.0"
notify = { version = "6.1.1", default-features = false }
notify-debouncer-full = { version = "0.3.1", default-features = false }
num_cpus = "1.14.0"
opus_headers = "0.1.2"
pbkdf2 = "0.11"

View file

@ -30,6 +30,8 @@ pub enum Error {
#[error("Filesystem error for `{0}`: `{1}`")]
Io(PathBuf, std::io::Error),
#[error(transparent)]
FileWatch(#[from] notify::Error),
#[error(transparent)]
Ape(#[from] ape::Error),
#[error("ID3 error in `{0}`: `{1}`")]
Id3(PathBuf, id3::Error),

View file

@ -4,6 +4,9 @@ use std::{
time::Duration,
};
use log::{error, info};
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use notify_debouncer_full::{DebounceEventResult, Debouncer, FileIdMap};
use regex::Regex;
use tokio::sync::RwLock;
@ -70,42 +73,64 @@ pub struct Manager {
config_file_path: PathBuf,
config: Arc<tokio::sync::RwLock<Config>>,
auth_secret: auth::Secret,
#[allow(dead_code)]
file_watcher: Arc<Debouncer<RecommendedWatcher, FileIdMap>>,
}
impl Manager {
pub async fn new(config_file_path: &Path, auth_secret: auth::Secret) -> Result<Self, Error> {
let config = {
if tokio::fs::try_exists(config_file_path)
.await
.map_err(|e| Error::Io(config_file_path.to_owned(), e))?
{
let config_content = tokio::fs::read_to_string(config_file_path)
.await
.map_err(|e| Error::Io(config_file_path.to_owned(), e))?;
let config = toml::de::from_str::<storage::Config>(&config_content)
.map_err(Error::ConfigDeserialization)?;
config.try_into()?
} else {
Config::default()
}
};
tokio::fs::File::create_new(config_file_path).await.ok();
let (sender, receiver) = std::sync::mpsc::channel::<DebounceEventResult>();
let mut debouncer =
notify_debouncer_full::new_debouncer(Duration::from_secs(1), None, sender)?;
debouncer
.watcher()
.watch(&config_file_path, RecursiveMode::NonRecursive)?;
let manager = Self {
config_file_path: config_file_path.to_owned(),
config: Arc::new(RwLock::new(config)),
config: Arc::new(RwLock::new(Config::default())),
auth_secret,
file_watcher: Arc::new(debouncer),
};
if !tokio::fs::try_exists(config_file_path)
.await
.map_err(|e| Error::Io(config_file_path.to_owned(), e))?
{
manager.save_config().await?;
}
tokio::task::spawn({
let manager = manager.clone();
async move {
loop {
match receiver.recv() {
Err(_) => break,
Ok(_) => {
if let Err(e) = manager.reload_config().await {
error!("Configuration error: {e}");
} else {
info!("Sucessfully applied configuration change");
}
}
}
}
}
});
manager.reload_config().await?;
Ok(manager)
}
async fn reload_config(&self) -> Result<(), Error> {
let config = Self::read_config(&self.config_file_path).await?;
self.apply_config(config).await
}
async fn read_config(config_file_path: &Path) -> Result<storage::Config, Error> {
let config_content = tokio::fs::read_to_string(config_file_path)
.await
.map_err(|e| Error::Io(config_file_path.to_owned(), e))?;
toml::de::from_str::<storage::Config>(&config_content).map_err(Error::ConfigDeserialization)
}
async fn save_config(&self) -> Result<(), Error> {
let serialized = toml::ser::to_string_pretty::<storage::Config>(
&self.config.read().await.clone().into(),
@ -117,13 +142,10 @@ impl Manager {
Ok(())
}
#[cfg(test)]
pub async fn apply(&self, config: storage::Config) -> Result<(), Error> {
self.mutate_fallible(|c| {
*c = config.try_into()?;
Ok(())
})
.await
pub async fn apply_config(&self, new_config: storage::Config) -> Result<(), Error> {
let mut config = self.config.write().await;
*config = new_config.try_into()?;
Ok(())
}
async fn mutate<F: FnOnce(&mut Config)>(&self, op: F) -> Result<(), Error> {
@ -138,8 +160,10 @@ impl Manager {
&self,
op: F,
) -> Result<(), Error> {
let mut config = self.config.write().await;
op(&mut config)?;
{
let mut config = self.config.write().await;
op(&mut config)?;
}
self.save_config().await?;
Ok(())
}

View file

@ -56,7 +56,7 @@ impl ContextBuilder {
.unwrap();
let playlist_manager = playlist::Manager::new(ndb_manager.clone());
config_manager.apply(self.config).await.unwrap();
config_manager.apply_config(self.config).await.unwrap();
Context {
index_manager,

View file

@ -92,6 +92,7 @@ impl From<app::Error> for APIError {
app::Error::ThreadJoining(_) => APIError::Internal,
app::Error::Io(p, e) => APIError::Io(p, e),
app::Error::FileWatch(_) => APIError::Internal,
app::Error::Ape(_) => APIError::Internal,
app::Error::Id3(p, e) => APIError::ThumbnailId3Decoding(p, e),
app::Error::Metaflac(p, e) => APIError::ThumbnailFlacDecoding(p, e),