Update "accounts.toml" atomically (#4295)

This commit is contained in:
iequidoo
2023-04-04 16:09:30 -04:00
committed by iequidoo
parent c8988f5a55
commit 36bec9c295
2 changed files with 34 additions and 18 deletions

View File

@@ -12,6 +12,7 @@
### Fixes ### Fixes
- Fix python bindings README documentation on installing the bindings from source. - Fix python bindings README documentation on installing the bindings from source.
- Show a warning if quota list is empty #4261 - Show a warning if quota list is empty #4261
- Update "accounts.toml" atomically
## [1.112.6] - 2023-04-04 ## [1.112.6] - 2023-04-04

View File

@@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
use anyhow::{ensure, Context as _, Result}; use anyhow::{ensure, Context as _, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::fs; use tokio::fs;
use tokio::io::AsyncWriteExt;
use uuid::Uuid; use uuid::Uuid;
use crate::context::Context; use crate::context::Context;
@@ -301,7 +302,7 @@ pub const DB_NAME: &str = "dc.db";
/// Account manager configuration file. /// Account manager configuration file.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Config { struct Config {
file: PathBuf, file: PathBuf,
inner: InnerConfig, inner: InnerConfig,
} }
@@ -325,10 +326,8 @@ impl Config {
selected_account: 0, selected_account: 0,
next_id: 1, next_id: 1,
}; };
let cfg = Config { let file = dir.join(CONFIG_NAME);
file: dir.join(CONFIG_NAME), let mut cfg = Self { file, inner };
inner,
};
cfg.sync().await?; cfg.sync().await?;
@@ -336,10 +335,24 @@ impl Config {
} }
/// Sync the inmemory representation to disk. /// Sync the inmemory representation to disk.
async fn sync(&self) -> Result<()> { /// Takes a mutable reference because the saved file is a part of the `Config` state. This
fs::write(&self.file, toml::to_string_pretty(&self.inner)?) /// protects from parallel calls resulting to a wrong file contents.
async fn sync(&mut self) -> Result<()> {
let tmp_path = self.file.with_extension("toml.tmp");
let mut file = fs::File::create(&tmp_path)
.await .await
.context("failed to write config") .context("failed to create a tmp config")?;
file.write_all(toml::to_string_pretty(&self.inner)?.as_bytes())
.await
.context("failed to write a tmp config")?;
file.sync_data()
.await
.context("failed to sync a tmp config")?;
drop(file);
fs::rename(&tmp_path, &self.file)
.await
.context("failed to rename config")?;
Ok(())
} }
/// Read a configuration from the given file into memory. /// Read a configuration from the given file into memory.
@@ -359,7 +372,7 @@ impl Config {
} }
} }
let config = Self { file, inner }; let mut config = Self { file, inner };
if modified { if modified {
config.sync().await?; config.sync().await?;
} }
@@ -503,17 +516,19 @@ mod tests {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
let p: PathBuf = dir.path().join("accounts1"); let p: PathBuf = dir.path().join("accounts1");
let mut accounts1 = Accounts::new(p.clone()).await.unwrap(); {
accounts1.add_account().await.unwrap(); let mut accounts = Accounts::new(p.clone()).await.unwrap();
accounts.add_account().await.unwrap();
let accounts2 = Accounts::open(p).await.unwrap(); assert_eq!(accounts.accounts.len(), 1);
assert_eq!(accounts.config.get_selected_account(), 1);
}
{
let accounts = Accounts::open(p).await.unwrap();
assert_eq!(accounts1.accounts.len(), 1); assert_eq!(accounts.accounts.len(), 1);
assert_eq!(accounts1.config.get_selected_account(), 1); assert_eq!(accounts.config.get_selected_account(), 1);
}
assert_eq!(accounts1.dir, accounts2.dir);
assert_eq!(accounts1.config, accounts2.config,);
assert_eq!(accounts1.accounts.len(), accounts2.accounts.len());
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]