Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
53 changes: 48 additions & 5 deletions deltachat-jsonrpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,11 +383,6 @@ impl CommandApi {
Ok(BlobObject::create_and_deduplicate(&ctx, file, file)?.to_abs_path())
}

async fn draft_self_report(&self, account_id: u32) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
Ok(ctx.draft_self_report().await?.to_u32())
}

/// Sets the given configuration key.
async fn set_config(&self, account_id: u32, key: String, value: Option<String>) -> Result<()> {
let ctx = self.get_context(account_id).await?;
Expand Down Expand Up @@ -886,6 +881,54 @@ impl CommandApi {
Ok(chat_id.to_u32())
}

/// Like `secure_join()`, but allows to pass a source and a UI-path.
/// You only need this if your UI has an option to send statistics
/// to Delta Chat's developers.
///
/// **source**: The source where the QR code came from. One of:
/// ```rust
/// enum SecurejoinSource {
/// /// Because of some problem, it is unknown where the QR code came from.
/// Unknown = 0,
/// /// The user opened a link somewhere outside Delta Chat
/// ExternalLink = 1,
/// /// The user clicked on a link in a message inside Delta Chat
/// InternalLink = 2,
/// /// The user clicked "Paste from Clipboard" in the QR scan activity
/// Clipboard = 3,
/// /// The user clicked "Load QR code as image" in the QR scan activity
/// ImageLoaded = 4,
/// /// The user scanned a QR code
/// Scan = 5,
/// }
/// ```
///
/// **uipath**: Which UI path did the user use to arrive at the QR code screen.
/// If the SecurejoinSource was ExternalLink or InternalLink,
/// you can just pass 0 here, because the QR code screen wasn't even opened.
/// ```rust
/// enum SecurejoinUIPath {
/// /// The UI path is unknown, or the user didn't open the QR code screen at all.
/// Unknown = 0,
/// /// The user directly clicked on the QR icon in the main screen
/// QrIcon = 1,
/// /// The user first clicked on the `+` button in the main screen,
/// /// and then on "New Contact"
/// NewContact = 2,
/// }
/// ```
async fn secure_join_with_ux_info(
&self,
account_id: u32,
qr: String,
source: Option<u32>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is None different from Some(Unknown) for both parameters?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at all, I first thought that making them an Option will make them optional parameters in the JsonRPC, but this didn't actually work. I'll just make them u32.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OTOH, the Option does do a good job at communicating that it's ok to pass nothing here, while this is less clear when it's just a u32

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this Unknown is defined in the code somewhere, it will also do this good job. If Option isn't needed technically, i'm for removing it.

uipath: Option<u32>,
) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
let chat_id = securejoin::join_securejoin_with_ux_info(&ctx, &qr, source, uipath).await?;
Ok(chat_id.to_u32())
}

async fn leave_group(&self, account_id: u32, chat_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
remove_contact_from_chat(&ctx, ChatId::new(chat_id), ContactId::SELF).await
Expand Down
5 changes: 5 additions & 0 deletions deltachat-time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ impl SystemTimeTools {
pub fn shift(duration: Duration) {
*SYSTEM_TIME_SHIFT.write().unwrap() += duration;
}

/// Simulates the system clock being rewound by `duration`.
pub fn shift_back(duration: Duration) {
*SYSTEM_TIME_SHIFT.write().unwrap() -= duration;
}
}

#[cfg(test)]
Expand Down
27 changes: 24 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use tokio::fs;

use crate::blob::BlobObject;
use crate::configure::EnteredLoginParam;
use crate::constants;
use crate::context::Context;
use crate::events::EventType;
use crate::log::{LogExt, info};
Expand All @@ -23,6 +22,7 @@ use crate::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::provider::{Provider, get_provider_by_id};
use crate::sync::{self, Sync::*, SyncData};
use crate::tools::get_abs_path;
use crate::{constants, statistics};

/// The available configuration keys.
#[derive(
Expand Down Expand Up @@ -431,9 +431,25 @@ pub enum Config {
/// used for signatures, encryption to self and included in `Autocrypt` header.
KeyId,

/// This key is sent to the self_reporting bot so that the bot can recognize the user
/// Send statistics to Delta Chat's developers.
/// Can be exposed to the user as a setting.
StatsSending,

/// Last time statistics were sent to Delta Chat's developers
StatsLastSent,

/// This key is sent to the statistics bot so that the bot can recognize the user
/// without storing the email address
SelfReportingId,
StatsId,

/// The last message id that was already included in the previously sent statistics,
/// or that already existed before the user opted in.
/// Only messages with an id larger than this
/// will be counted in the next statistics.
StatsLastCountedMsgId,

/// The last contact id that already existed when statistics-sending was enabled.
StatsLastOldContactId,

/// MsgId of webxdc map integration.
WebxdcIntegration,
Expand Down Expand Up @@ -827,6 +843,11 @@ impl Context {
.await?;
}
}
Config::StatsSending => {
self.sql.set_raw_config(key.as_ref(), value).await?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe do this as the last step so as if StatsSending is set, LastCountedMsgId and LastOldContactId are guaranteed to be correct, even if the program crashes in between?

Also if StatsSending is already set, this updates LastCountedMsgId, but it mustn't.

statistics::set_last_counted_msg_id(self).await?;
statistics::set_last_old_contact_id(self).await?;
}
_ => {
self.sql.set_raw_config(key.as_ref(), value).await?;
}
Expand Down
176 changes: 23 additions & 153 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,30 @@ use std::time::Duration;

use anyhow::{Context as _, Result, bail, ensure};
use async_channel::{self as channel, Receiver, Sender};
use pgp::types::PublicKeyTrait;
use ratelimit::Ratelimit;
use tokio::sync::{Mutex, Notify, RwLock};

use crate::chat::{ChatId, ProtectionStatus, get_chat_cnt};
use crate::chatlist_events;
use crate::chat::{ChatId, get_chat_cnt};
use crate::config::Config;
use crate::constants::{
self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_CHAT_ID_TRASH, DC_VERSION_STR,
};
use crate::contact::{Contact, ContactId, import_vcard, mark_contact_id_as_verified};
use crate::constants::{self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
use crate::contact::{Contact, ContactId};
use crate::debug_logging::DebugLogging;
use crate::download::DownloadState;
use crate::events::{Event, EventEmitter, EventType, Events};
use crate::imap::{FolderMeaning, Imap, ServerMetadata};
use crate::key::{load_self_secret_key, self_fingerprint};
use crate::key::self_fingerprint;
use crate::log::{info, warn};
use crate::logged_debug_assert;
use crate::login_param::{ConfiguredLoginParam, EnteredLoginParam};
use crate::message::{self, Message, MessageState, MsgId};
use crate::param::{Param, Params};
use crate::message::{self, MessageState, MsgId};
use crate::peer_channels::Iroh;
use crate::push::PushSubscriber;
use crate::quota::QuotaInfo;
use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
use crate::sql::Sql;
use crate::stock_str::StockStrings;
use crate::timesmearing::SmearedTimestamp;
use crate::tools::{self, create_id, duration_to_str, time, time_elapsed};
use crate::tools::{self, duration_to_str, time, time_elapsed};
use crate::{chatlist_events, statistics};

/// Builder for the [`Context`].
///
Expand Down Expand Up @@ -1081,154 +1076,29 @@ impl Context {
.await?
.unwrap_or_default(),
);
res.insert(
"stats_id",
self.get_config(Config::StatsId)
.await?
.unwrap_or_else(|| "<unset>".to_string()),
);
res.insert(
"stats_sending",
statistics::should_send_statistics(self).await?.to_string(),
);
res.insert(
"stats_last_sent",
self.get_config_i64(Config::StatsLastSent)
.await?
.to_string(),
);

let elapsed = time_elapsed(&self.creation_time);
res.insert("uptime", duration_to_str(elapsed));

Ok(res)
}

async fn get_self_report(&self) -> Result<String> {
#[derive(Default)]
struct ChatNumbers {
protected: u32,
opportunistic_dc: u32,
opportunistic_mua: u32,
unencrypted_dc: u32,
unencrypted_mua: u32,
}

let mut res = String::new();
res += &format!("core_version {}\n", get_version_str());

let num_msgs: u32 = self
.sql
.query_get_value(
"SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id!=?",
(DC_CHAT_ID_TRASH,),
)
.await?
.unwrap_or_default();
res += &format!("num_msgs {num_msgs}\n");

let num_chats: u32 = self
.sql
.query_get_value("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked!=1", ())
.await?
.unwrap_or_default();
res += &format!("num_chats {num_chats}\n");

let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len();
res += &format!("db_size_bytes {db_size}\n");

let secret_key = &load_self_secret_key(self).await?.primary_key;
let key_created = secret_key.public_key().created_at().timestamp();
res += &format!("key_created {key_created}\n");

// how many of the chats active in the last months are:
// - protected
// - opportunistic-encrypted and the contact uses Delta Chat
// - opportunistic-encrypted and the contact uses a classical MUA
// - unencrypted and the contact uses Delta Chat
// - unencrypted and the contact uses a classical MUA
let three_months_ago = time().saturating_sub(3600 * 24 * 30 * 3);
let chats = self
.sql
.query_map(
"SELECT c.protected, m.param, m.msgrmsg
FROM chats c
JOIN msgs m
ON c.id=m.chat_id
AND m.id=(
SELECT id
FROM msgs
WHERE chat_id=c.id
AND hidden=0
AND download_state=?
AND to_id!=?
ORDER BY timestamp DESC, id DESC LIMIT 1)
WHERE c.id>9
AND (c.blocked=0 OR c.blocked=2)
AND IFNULL(m.timestamp,c.created_timestamp) > ?
GROUP BY c.id",
(DownloadState::Done, ContactId::INFO, three_months_ago),
|row| {
let protected: ProtectionStatus = row.get(0)?;
let message_param: Params =
row.get::<_, String>(1)?.parse().unwrap_or_default();
let is_dc_message: bool = row.get(2)?;
Ok((protected, message_param, is_dc_message))
},
|rows| {
let mut chats = ChatNumbers::default();
for row in rows {
let (protected, message_param, is_dc_message) = row?;
let encrypted = message_param
.get_bool(Param::GuaranteeE2ee)
.unwrap_or(false);

if protected == ProtectionStatus::Protected {
chats.protected += 1;
} else if encrypted {
if is_dc_message {
chats.opportunistic_dc += 1;
} else {
chats.opportunistic_mua += 1;
}
} else if is_dc_message {
chats.unencrypted_dc += 1;
} else {
chats.unencrypted_mua += 1;
}
}
Ok(chats)
},
)
.await?;
res += &format!("chats_protected {}\n", chats.protected);
res += &format!("chats_opportunistic_dc {}\n", chats.opportunistic_dc);
res += &format!("chats_opportunistic_mua {}\n", chats.opportunistic_mua);
res += &format!("chats_unencrypted_dc {}\n", chats.unencrypted_dc);
res += &format!("chats_unencrypted_mua {}\n", chats.unencrypted_mua);

let self_reporting_id = match self.get_config(Config::SelfReportingId).await? {
Some(id) => id,
None => {
let id = create_id();
self.set_config(Config::SelfReportingId, Some(&id)).await?;
id
}
};
res += &format!("self_reporting_id {self_reporting_id}");

Ok(res)
}

/// Drafts a message with statistics about the usage of Delta Chat.
/// The user can inspect the message if they want, and then hit "Send".
///
/// On the other end, a bot will receive the message and make it available
/// to Delta Chat's developers.
pub async fn draft_self_report(&self) -> Result<ChatId> {
const SELF_REPORTING_BOT_VCARD: &str = include_str!("../assets/self-reporting-bot.vcf");
let contact_id: ContactId = *import_vcard(self, SELF_REPORTING_BOT_VCARD)
.await?
.first()
.context("Self reporting bot vCard does not contain a contact")?;
mark_contact_id_as_verified(self, contact_id, ContactId::SELF).await?;

let chat_id = ChatId::create_for_contact(self, contact_id).await?;
chat_id
.set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id))
.await?;

let mut msg = Message::new_text(self.get_self_report().await?);

chat_id.set_draft(self, Some(&mut msg)).await?;

Ok(chat_id)
}

/// Get a list of fresh, unmuted messages in unblocked chats.
///
/// The list starts with the most recent message
Expand Down
Loading
Loading