Masked email: x:MaskedEmail/changes and a state in /get (fork additions)
The fork's per-account change log answers /changes, collapsing a mask created and destroyed in the window. /changes on a registry type needs that type's get permission.
This commit is contained in:
@@ -196,15 +196,14 @@ fn change_key(account_id: u32, change_id: u64) -> ValueKey<ValueClass> {
|
||||
key(KIND_CHANGE, &rest)
|
||||
}
|
||||
|
||||
/// The account's changes after `since`, oldest first, and the latest change
|
||||
/// id (`since` when there are none).
|
||||
/// The account's changes after `since`, oldest first, each with its change
|
||||
/// id.
|
||||
pub async fn changes_since(
|
||||
data: &Store,
|
||||
account_id: u32,
|
||||
since: u64,
|
||||
) -> trc::Result<(Vec<(Id, Change)>, u64)> {
|
||||
) -> trc::Result<Vec<(u64, Id, Change)>> {
|
||||
let mut changes = Vec::new();
|
||||
let mut latest = since;
|
||||
data.iterate(
|
||||
IterateParams::new(
|
||||
change_key(account_id, since.saturating_add(1)),
|
||||
@@ -213,13 +212,13 @@ pub async fn changes_since(
|
||||
.ascending(),
|
||||
|key, value| {
|
||||
if key.len() >= 8 && value.len() == 9 {
|
||||
latest = latest.max(u64::from_be_bytes(key[key.len() - 8..].try_into().unwrap()));
|
||||
let change = match value[8] {
|
||||
0 => Change::Created,
|
||||
1 => Change::Updated,
|
||||
_ => Change::Destroyed,
|
||||
};
|
||||
changes.push((
|
||||
u64::from_be_bytes(key[key.len() - 8..].try_into().unwrap()),
|
||||
Id::new(u64::from_be_bytes(value[0..8].try_into().unwrap())),
|
||||
change,
|
||||
));
|
||||
@@ -229,7 +228,7 @@ pub async fn changes_since(
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
Ok((changes, latest))
|
||||
Ok(changes)
|
||||
}
|
||||
|
||||
/// The account's latest change id, 0 when there's none.
|
||||
|
||||
@@ -346,6 +346,55 @@ pub async fn sends_as(
|
||||
.is_some_and(|mask| mask.object.account_id.document_id() == account_id))
|
||||
}
|
||||
|
||||
/// Changes to an account's masks since a state, for `/changes`.
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct Changes {
|
||||
pub created: Vec<Id>,
|
||||
pub updated: Vec<Id>,
|
||||
pub destroyed: Vec<Id>,
|
||||
/// The state after the changes returned.
|
||||
pub new_state: u64,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
/// The changes since `since`, at most `max` masks' worth. A mask created and
|
||||
/// destroyed within the window isn't reported; one created and changed is
|
||||
/// reported as created.
|
||||
pub fn collapse(since: u64, entries: &[(u64, Id, Change)], max: usize) -> Changes {
|
||||
use ahash::AHashMap;
|
||||
let mut state: AHashMap<Id, (bool, bool)> = AHashMap::new(); // (created, destroyed)
|
||||
let mut order = Vec::new();
|
||||
let mut result = Changes {
|
||||
new_state: since,
|
||||
..Default::default()
|
||||
};
|
||||
for (change_id, id, change) in entries {
|
||||
if !state.contains_key(id) {
|
||||
if order.len() == max {
|
||||
result.has_more = true;
|
||||
break;
|
||||
}
|
||||
order.push(*id);
|
||||
}
|
||||
let entry = state.entry(*id).or_insert((false, false));
|
||||
match change {
|
||||
Change::Created => entry.0 = true,
|
||||
Change::Destroyed => entry.1 = true,
|
||||
Change::Updated => {}
|
||||
}
|
||||
result.new_state = *change_id;
|
||||
}
|
||||
for id in order {
|
||||
match state[&id] {
|
||||
(true, true) => {}
|
||||
(true, false) => result.created.push(id),
|
||||
(false, true) => result.destroyed.push(id),
|
||||
(false, false) => result.updated.push(id),
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// What an address is, as far as masks go.
|
||||
#[derive(Debug)]
|
||||
pub enum Lookup {
|
||||
@@ -406,6 +455,30 @@ pub fn with_header(address: &str, message: &[u8]) -> Vec<u8> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn changes_collapse() {
|
||||
let (a, b, c) = (Id::new(1), Id::new(2), Id::new(3));
|
||||
let entries = [
|
||||
(10, a, Change::Created),
|
||||
(11, a, Change::Updated),
|
||||
(12, b, Change::Updated),
|
||||
(13, c, Change::Created),
|
||||
(14, c, Change::Destroyed),
|
||||
(15, b, Change::Destroyed),
|
||||
];
|
||||
let all = collapse(9, &entries, 100);
|
||||
assert_eq!(all.created, vec![a]);
|
||||
assert_eq!(all.destroyed, vec![b]);
|
||||
assert!(all.updated.is_empty());
|
||||
assert_eq!(all.new_state, 15);
|
||||
assert!(!all.has_more);
|
||||
|
||||
let first = collapse(9, &entries, 1);
|
||||
assert_eq!(first.created, vec![a]);
|
||||
assert_eq!(first.new_state, 11);
|
||||
assert!(first.has_more);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_goes_first() {
|
||||
let out = with_header("[email protected]", b"Subject: hi\r\n\r\nbody");
|
||||
|
||||
Reference in New Issue
Block a user