use std::fs;
use std::io::{self, Write};
use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
use std::path::{Path, PathBuf};
use log::error;
use crate::account::model::Uid;
use crate::support::file_ops::IgnoreKinds;
pub fn claim(
root: &Path,
min_uid: Uid,
max_uid: Uid,
read_only: bool,
) -> Option<Uid> {
if read_only {
read_only_claim(root, max_uid)
} else {
read_write_claim(root, max_uid)
}
.map(|u| min_uid.max(u))
}
fn read_only_claim(root: &Path, max_uid: Uid) -> Option<Uid> {
if let Some(last_claimed) = get_current_tokens(&token_dir(root))
.ok()
.and_then(|s| s.last().copied().and_then(Uid::of))
{
if last_claimed < max_uid {
last_claimed.next()
} else {
None
}
} else {
Some(Uid::MIN)
}
}
fn read_write_claim(root: &Path, max_uid: Uid) -> Option<Uid> {
let tdir = token_dir(root);
loop {
let current = match get_current_tokens(&tdir) {
Ok(c) if !c.is_empty() => c,
Ok(_) => {
if let Err(err) = init_token_dir(&tdir, max_uid) {
error!(
"Failed to initialised {}; \
all messages will be \\Recent: {}",
tdir.display(),
err
);
}
return Some(Uid::MIN);
},
Err(e) if io::ErrorKind::NotFound == e.kind() => {
if let Err(err) = init_token_dir(&tdir, max_uid) {
error!(
"Failed to initialised {}; \
all messages will be \\Recent: {}",
tdir.display(),
err
);
}
return Some(Uid::MIN);
},
Err(e) => {
error!(
"Failed to list {}; \
all messages will be \\Recent: {}",
tdir.display(),
e
);
return Some(Uid::MIN);
},
};
for superfluous in ¤t[..current.len() - 1] {
let _ = fs::remove_file(tdir.join(superfluous.to_string()));
}
let latest = current[current.len() - 1];
if latest >= max_uid.0.get() {
return None;
}
let latest_path = tdir.join(latest.to_string());
match nix::unistd::linkat(
None,
&latest_path,
None,
&tdir.join(max_uid.0.get().to_string()),
nix::unistd::LinkatFlags::NoSymlinkFollow,
) {
Ok(()) => {
let _ = fs::remove_file(&latest_path);
return Uid::of(latest).and_then(Uid::next);
},
Err(nix::errno::Errno::ENOENT) => continue,
Err(nix::errno::Errno::EEXIST) => return None,
Err(e) => {
error!(
"Failed to update recency token ({}); \
all messages will be \\Recent: {}",
latest_path.display(),
e
);
return Some(Uid::MIN);
},
}
}
}
fn token_dir(root: &Path) -> PathBuf {
root.join("recent")
}
fn get_current_tokens(token_dir: &Path) -> io::Result<Vec<u32>> {
let mut result = Vec::new();
for entry in fs::read_dir(token_dir)? {
let entry = entry?;
if let Some(val) = entry
.file_name()
.to_str()
.and_then(|n| n.parse::<u32>().ok())
{
result.push(val);
}
}
result.sort();
Ok(result)
}
fn init_token_dir(tdir: &Path, max_uid: Uid) -> io::Result<()> {
fs::DirBuilder::new()
.mode(0o700)
.create(tdir)
.ignore_already_exists()?;
let mut f = fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.mode(0o600)
.open(tdir.join(max_uid.0.get().to_string()))?;
let _ = writeln!(f, "This file is a token that tracks the \\Recent flag.");
Ok(())
}
#[cfg(test)]
mod test {
use tempfile::TempDir;
use super::*;
#[test]
fn test_recency_token() {
let root = TempDir::new().unwrap();
assert_eq!(
Some(Uid::u(5)),
claim(root.path(), Uid::u(5), Uid::u(10), true)
);
assert_eq!(
Some(Uid::u(2)),
claim(root.path(), Uid::u(2), Uid::u(7), true)
);
assert_eq!(
Some(Uid::u(1)),
claim(root.path(), Uid::u(1), Uid::u(10), false)
);
assert_eq!(None, claim(root.path(), Uid::u(1), Uid::u(9), true));
assert_eq!(None, claim(root.path(), Uid::u(1), Uid::u(10), true));
assert_eq!(None, claim(root.path(), Uid::u(1), Uid::u(9), false));
assert_eq!(None, claim(root.path(), Uid::u(1), Uid::u(10), false));
assert_eq!(
Some(Uid::u(11)),
claim(root.path(), Uid::u(1), Uid::u(20), true)
);
assert_eq!(
Some(Uid::u(11)),
claim(root.path(), Uid::u(1), Uid::u(20), true)
);
assert_eq!(
Some(Uid::u(11)),
claim(root.path(), Uid::u(1), Uid::u(20), false)
);
assert_eq!(None, claim(root.path(), Uid::u(1), Uid::u(20), true));
assert_eq!(None, claim(root.path(), Uid::u(1), Uid::u(20), false));
assert_eq!(
Some(Uid::u(21)),
claim(root.path(), Uid::u(1), Uid::u(30), true)
);
assert_eq!(
Some(Uid::u(21)),
claim(root.path(), Uid::u(1), Uid::u(30), false)
);
assert_eq!(
1,
fs::read_dir(root.path().join("recent"))
.unwrap()
.map(|v| v.unwrap())
.count()
);
}
}