Skip to main content

ProfileReader

Struct ProfileReader 

Source
pub struct ProfileReader { /* private fields */ }

Implementations§

Source§

impl ProfileReader

Source

pub fn cached_image( &self, id: &SourceMessageId, quality: ImageQuality, ) -> Result<CachedImage>

Resolve one exact local message through its advertised original hash and hardlink namespace. Ambiguous groups are rejected. Standard/Thumbnail require the exact resource basename/type/status/encoded-size join. A suffix alone is not a quality proof; Original also requires the XML content MD5.

Source§

impl ProfileReader

File/video hardlinks use one month directory and the observed zero second directory sentinel. Every candidate is content-MD5 verified; ambiguous bytes or unknown path layouts are refused rather than guessed.

Source§

impl ProfileReader

Source

pub fn peer_metadata( &self, peers: &[String], ) -> Result<BTreeMap<String, PeerMetadata>>

Read one page’s saved metadata without refreshing contacts or catalogs. Missing peers are omitted. A malformed extra buffer affects only its peer; an incompatible database schema remains an error.

Source§

impl ProfileReader

Source

pub fn thread_pinned( &self, threads: &[String], ) -> Result<BTreeMap<String, Option<bool>>>

Read the saved pin bit for ordinary contacts and active groups in one query. Incomplete identities and unsupported built-in peers stay unknown.

Source

pub fn thread_muted( &self, threads: &[String], ) -> Result<BTreeMap<String, Option<bool>>>

Derive notification preferences for a page without refreshing contacts. Direct contacts and groups use different official fields; incomplete identities and unexpected values remain unknown.

Source

pub fn group_avatar_members( &self, rooms: &[String], ) -> Result<BTreeMap<String, Vec<GroupMember>>>

Read avatar members for a page of groups with three batched queries. Validate membership against the same official room metadata used by group_members; an inconsistent room does not hide other valid rooms.

Source§

impl ProfileReader

Source

pub fn open( location: ProfileLocation, keys: KeyFile, options: ReadOptions, ) -> Result<Self>

Examples found in repository?
examples/contacts.rs (lines 11-18)
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    let args: Vec<_> = std::env::args().skip(1).collect();
6    if args.len() != 3 || args[0] != "--stopped-or-consistent-copy" {
7        return Err("usage: contacts --stopped-or-consistent-copy ACCOUNT_ROOT KEYS_FILE".into());
8    }
9    let location = ProfileLocation::new(&args[1])?;
10    let keys = KeyFile::load(&args[2])?;
11    let reader = ProfileReader::open(
12        location,
13        keys,
14        ReadOptions {
15            stopped_or_consistent_snapshot: true,
16            ..Default::default()
17        },
18    )?;
19    println!(
20        "{} contacts",
21        reader.contacts(&ListQuery::default())?.items.len()
22    );
23    Ok(())
24}
More examples
Hide additional examples
examples/apply_text_fixture.rs (lines 34-41)
4fn run() -> Result<()> {
5    let a = std::env::args().skip(1).collect::<Vec<_>>();
6    if a.len() != 3 {
7        return Err(ProfileError::InvalidQuery);
8    }
9    let location = ProfileLocation::new(&a[0])?;
10    let keys = KeyFile::load(&a[1])?;
11    let input: IncomingText =
12        serde_json::from_slice(&std::fs::read(&a[2])?).map_err(|_| ProfileError::InvalidQuery)?;
13    let options = WriteOptions {
14        exclusive_ownership_confirmed: true,
15        allow_plaintext: false,
16        durable_intent_id: "explicit-disposable-fixture-validation".into(),
17    };
18    let first = apply_incoming_text(&location, &keys, &input, &options)?;
19    let retry = apply_incoming_text(&location, &keys, &input, &options)?;
20    let read = mark_read_messages(
21        &location,
22        &keys,
23        &input.peer,
24        std::slice::from_ref(&first.source_id),
25        &options,
26    )?;
27    let read_retry = mark_read_messages(
28        &location,
29        &keys,
30        &input.peer,
31        std::slice::from_ref(&first.source_id),
32        &options,
33    )?;
34    let reader = ProfileReader::open(
35        location,
36        keys,
37        ReadOptions {
38            stopped_or_consistent_snapshot: true,
39            ..Default::default()
40        },
41    )?;
42    let page = reader.messages(&MessageQuery {
43        chat: Some(input.peer),
44        limit: Some(200),
45        ..Default::default()
46    })?;
47    let found = page
48        .items
49        .iter()
50        .find(|v| v.id == first.source_id)
51        .ok_or(ProfileError::InvalidMetadata)?;
52    let exact = found.content.text.as_deref() == Some(input.content.as_str())
53        && found.source.text.as_deref() == Some(input.source_xml.as_str())
54        && found.server_message_id.as_deref() == Some(input.server_message_id.as_str());
55    if !exact || retry.message_inserted || read_retry.removed_unread_rows != 0 {
56        return Err(ProfileError::InvalidMetadata);
57    }
58    println!(
59        "{}",
60        serde_json::json!({"newTextInserted":first.message_inserted,"retryInsertedDuplicate":retry.message_inserted,"exactBodySourceAndServerIdentity":exact,"removedUnreadRows":read.removed_unread_rows,"readRetryRemovedRows":read_retry.removed_unread_rows,"unreadBefore":read.unread_count_before,"unreadAfter":read.unread_count_after,"crossDatabaseAtomic":first.cross_database_atomic,"networkUsed":false,"officialRuntimeUsed":false})
61    );
62    Ok(())
63}
examples/audit_counts.rs (lines 14-22)
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
Source

pub fn generation(&self) -> &str

Source

pub fn schemas(&self) -> &[SchemaReport]

Source

pub fn coverage(&self) -> Coverage

Examples found in repository?
examples/audit_counts.rs (line 104)
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
Source

pub fn inventory(&self) -> &[DatabaseInventory]

Examples found in repository?
examples/audit_counts.rs (line 104)
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
Source

pub fn require_writable(&self) -> Result<()>

Source

pub fn contacts(&self, q: &ListQuery) -> Result<Page<Contact>>

Examples found in repository?
examples/contacts.rs (line 21)
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    let args: Vec<_> = std::env::args().skip(1).collect();
6    if args.len() != 3 || args[0] != "--stopped-or-consistent-copy" {
7        return Err("usage: contacts --stopped-or-consistent-copy ACCOUNT_ROOT KEYS_FILE".into());
8    }
9    let location = ProfileLocation::new(&args[1])?;
10    let keys = KeyFile::load(&args[2])?;
11    let reader = ProfileReader::open(
12        location,
13        keys,
14        ReadOptions {
15            stopped_or_consistent_snapshot: true,
16            ..Default::default()
17        },
18    )?;
19    println!(
20        "{} contacts",
21        reader.contacts(&ListQuery::default())?.items.len()
22    );
23    Ok(())
24}
More examples
Hide additional examples
examples/audit_counts.rs (line 30)
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
Source

pub fn thread_exists(&self, username: &str) -> Result<bool>

Source

pub fn threads(&self, q: &ListQuery) -> Result<Page<Thread>>

Examples found in repository?
examples/audit_counts.rs (line 48)
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
Source

pub fn labels(&self) -> Result<BTreeMap<String, String>>

Source

pub fn message_references( &self, chat: Option<&str>, max_items: usize, ) -> Result<Vec<MessageReference>>

Capture complete history membership in one pass per shard. This reads only IDs/timestamps; callers resolve delivered content with messages_by_ids against this same snapshot. No state is cached across snapshots, and incompatible message tables remain errors.

Source

pub fn latest_messages( &self, chats: &[String], own_username: Option<&str>, ) -> Result<BTreeMap<String, Message>>

Read the latest saved message for each requested conversation in one snapshot. One compound query per shard selects one indexed row per conversation; no point lookups or older-body decoding are needed.

Source

pub fn messages(&self, q: &MessageQuery) -> Result<Page<Message>>

Examples found in repository?
examples/apply_text_fixture.rs (lines 42-46)
4fn run() -> Result<()> {
5    let a = std::env::args().skip(1).collect::<Vec<_>>();
6    if a.len() != 3 {
7        return Err(ProfileError::InvalidQuery);
8    }
9    let location = ProfileLocation::new(&a[0])?;
10    let keys = KeyFile::load(&a[1])?;
11    let input: IncomingText =
12        serde_json::from_slice(&std::fs::read(&a[2])?).map_err(|_| ProfileError::InvalidQuery)?;
13    let options = WriteOptions {
14        exclusive_ownership_confirmed: true,
15        allow_plaintext: false,
16        durable_intent_id: "explicit-disposable-fixture-validation".into(),
17    };
18    let first = apply_incoming_text(&location, &keys, &input, &options)?;
19    let retry = apply_incoming_text(&location, &keys, &input, &options)?;
20    let read = mark_read_messages(
21        &location,
22        &keys,
23        &input.peer,
24        std::slice::from_ref(&first.source_id),
25        &options,
26    )?;
27    let read_retry = mark_read_messages(
28        &location,
29        &keys,
30        &input.peer,
31        std::slice::from_ref(&first.source_id),
32        &options,
33    )?;
34    let reader = ProfileReader::open(
35        location,
36        keys,
37        ReadOptions {
38            stopped_or_consistent_snapshot: true,
39            ..Default::default()
40        },
41    )?;
42    let page = reader.messages(&MessageQuery {
43        chat: Some(input.peer),
44        limit: Some(200),
45        ..Default::default()
46    })?;
47    let found = page
48        .items
49        .iter()
50        .find(|v| v.id == first.source_id)
51        .ok_or(ProfileError::InvalidMetadata)?;
52    let exact = found.content.text.as_deref() == Some(input.content.as_str())
53        && found.source.text.as_deref() == Some(input.source_xml.as_str())
54        && found.server_message_id.as_deref() == Some(input.server_message_id.as_str());
55    if !exact || retry.message_inserted || read_retry.removed_unread_rows != 0 {
56        return Err(ProfileError::InvalidMetadata);
57    }
58    println!(
59        "{}",
60        serde_json::json!({"newTextInserted":first.message_inserted,"retryInsertedDuplicate":retry.message_inserted,"exactBodySourceAndServerIdentity":exact,"removedUnreadRows":read.removed_unread_rows,"readRetryRemovedRows":read_retry.removed_unread_rows,"unreadBefore":read.unread_count_before,"unreadAfter":read.unread_count_after,"crossDatabaseAtomic":first.cross_database_atomic,"networkUsed":false,"officialRuntimeUsed":false})
61    );
62    Ok(())
63}
More examples
Hide additional examples
examples/audit_counts.rs (line 91)
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
Source

pub fn message( &self, id: &SourceMessageId, own_username: Option<&str>, ) -> Result<Message>

Read one source-qualified observation without paging through history. Resolve sender numbers only through this database’s own Name2Id map.

Source

pub fn messages_by_ids( &self, ids: &[SourceMessageId], own_username: Option<&str>, ) -> Result<Vec<Message>>

Resolve at most one page without decoding unrelated history. Missing rows are omitted; unreadable databases and incompatible records remain errors. Source-qualified IDs preserve each shard’s own sender namespace.

Source

pub fn messages_by_server_id( &self, chat: &str, server_id: &str, own_username: Option<&str>, ) -> Result<Vec<Message>>

Resolve an unsigned server ID in one conversation only. Return up to two observations so callers can reject ambiguous IDs across shards.

Source

pub fn visit_messages_by_server_ids<E>( &self, targets: &[(String, String)], own_username: Option<&str>, visit: impl FnMut(Message) -> Result<(), E>, ) -> Result<(), E>
where E: From<ProfileError> + From<Error>,

Read quoted originals for one page from this captured snapshot. Group by conversation/shard, decode only matching rows, and retain at most two observations per identity just like the single-reference lookup. The visitor consumes each row before the next, avoiding a page of large bodies.

Source

pub fn groups(&self, q: &ListQuery) -> Result<Page<Group>>

Examples found in repository?
examples/audit_counts.rs (line 62)
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
Source

pub fn group_mention_permissions( &self, room: &str, own: Option<&str>, ) -> Result<GroupMentionPermissions>

Source

pub fn group_members( &self, room: &str, q: &ListQuery, ) -> Result<Page<GroupMember>>

Examples found in repository?
examples/audit_counts.rs (line 69)
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
Source

pub fn avatar(&self, username: &str) -> Result<CachedBytes>

Examples found in repository?
examples/audit_counts.rs (line 32)
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
Source

pub fn cached_voice( &self, database: &str, chat: &str, local_id: &str, server_id: Option<&str>, ) -> Result<CachedBytes>

Read one exact cached voice record. The supplied media-shard namespace is explicit; its Name2Id is not interchangeable with the message shard.

Source

pub fn decode_dat(&self, bytes: &[u8]) -> Result<(Vec<u8>, DatLayout)>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.