Skip to main content

ProfileLocation

Struct ProfileLocation 

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

Implementations§

Source§

impl ProfileLocation

Source

pub fn new(path: impl AsRef<Path>) -> Result<Self>

Select one account root containing db_storage, or db_storage itself. A whole WeChat home with multiple accounts is never resolved implicitly.

Examples found in repository?
examples/contacts.rs (line 9)
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 (line 9)
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 (line 8)
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 root(&self) -> &Path

Source

pub fn account_directory(&self) -> &str

Examples found in repository?
examples/audit_counts.rs (line 12)
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}

Trait Implementations§

Source§

impl Clone for ProfileLocation

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ProfileLocation

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.