mirror of
https://github.com/chatmail/core.git
synced 2026-07-03 14:04:56 +03:00
Compare commits
32 Commits
refactor/t
...
flub-loggi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c3cfc53c2 | ||
|
|
de28ed68c9 | ||
|
|
ecf1b4a9f7 | ||
|
|
ac48ada198 | ||
|
|
98f55bd8f5 | ||
|
|
780cd9d864 | ||
|
|
4312c03a0b | ||
|
|
124f684036 | ||
|
|
0770042f30 | ||
|
|
f8736895cd | ||
|
|
dacde72456 | ||
|
|
7e66af05ff | ||
|
|
f0486eb820 | ||
|
|
e06ac87c0d | ||
|
|
0c19fcd79f | ||
|
|
02fe3d1b99 | ||
|
|
95b90a59dc | ||
|
|
fe7852b64e | ||
|
|
f87cb4231c | ||
|
|
430d4e5f6e | ||
|
|
6f6791c1b5 | ||
|
|
bc11ae7245 | ||
|
|
de228bdb4b | ||
|
|
25fb199ba0 | ||
|
|
d0795f5770 | ||
|
|
fbb8c8e80c | ||
|
|
76610f1e72 | ||
|
|
618d74cd67 | ||
|
|
59700cb477 | ||
|
|
fc1a136448 | ||
|
|
42ef43bdf6 | ||
|
|
f53b3c2e7b |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -90,7 +90,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "async-imap"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/async-email/async-imap#1327f678cf5515842fc309636459372e6ab40db2"
|
||||
source = "git+https://github.com/async-email/async-imap#377d40837028b454c6365ff13e3f35cc341a908e"
|
||||
dependencies = [
|
||||
"async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-std 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
environment:
|
||||
RUST_BACKTRACE: full
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
|
||||
|
||||
@@ -4387,6 +4387,16 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_EVENT_SECUREJOIN_JOINER_PROGRESS 2061
|
||||
|
||||
|
||||
/**
|
||||
* This event is sent out to the inviter when a joiner successfully joined a group.
|
||||
*
|
||||
* @param data1 (int) chat_id
|
||||
* @param data2 (int) contact_id
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_SECUREJOIN_MEMBER_ADDED 2062
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,7 @@ use num_traits::{FromPrimitive, ToPrimitive};
|
||||
|
||||
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::context::{Context, ContextBuilder};
|
||||
use deltachat::dc_tools::{
|
||||
as_path, dc_strdup, to_opt_string_lossy, to_string_lossy, OsStrExt, StrExt,
|
||||
};
|
||||
@@ -175,6 +175,15 @@ impl ContextWrapper {
|
||||
contact_id as uintptr_t,
|
||||
progress as uintptr_t,
|
||||
),
|
||||
Event::SecurejoinMemberAdded {
|
||||
chat_id,
|
||||
contact_id,
|
||||
} => ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
chat_id as uintptr_t,
|
||||
contact_id as uintptr_t,
|
||||
),
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
@@ -239,22 +248,15 @@ pub unsafe extern "C" fn dc_open(
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
let rust_cb = move |_ctx: &Context, evt: Event| ffi_context.translate_cb(evt);
|
||||
|
||||
let ctx = if blobdir.is_null() || *blobdir == 0 {
|
||||
Context::new(
|
||||
Box::new(rust_cb),
|
||||
ffi_context.os_name.clone(),
|
||||
as_path(dbfile).to_path_buf(),
|
||||
)
|
||||
} else {
|
||||
Context::with_blobdir(
|
||||
Box::new(rust_cb),
|
||||
ffi_context.os_name.clone(),
|
||||
as_path(dbfile).to_path_buf(),
|
||||
as_path(blobdir).to_path_buf(),
|
||||
)
|
||||
};
|
||||
match ctx {
|
||||
let mut builder = ContextBuilder::new(
|
||||
Box::new(rust_cb),
|
||||
ffi_context.os_name.clone(),
|
||||
as_path(dbfile).to_path_buf(),
|
||||
);
|
||||
if !blobdir.is_null() && *blobdir != 0 {
|
||||
builder = builder.blobdir(as_path(blobdir).to_path_buf());
|
||||
}
|
||||
match builder.create() {
|
||||
Ok(ctx) => {
|
||||
let mut inner_guard = ffi_context.inner.write().unwrap();
|
||||
*inner_guard = Some(ctx);
|
||||
|
||||
@@ -132,10 +132,8 @@ fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
|
||||
real_spec = rs.unwrap();
|
||||
}
|
||||
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
|
||||
if suffix == "eml" {
|
||||
if dc_poke_eml_file(context, &real_spec).is_ok() {
|
||||
read_cnt += 1
|
||||
}
|
||||
if suffix == "eml" && dc_poke_eml_file(context, &real_spec).is_ok() {
|
||||
read_cnt += 1
|
||||
}
|
||||
} else {
|
||||
/* import a directory */
|
||||
@@ -588,7 +586,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
let members = chat::get_chat_contacts(context, sel_chat.id);
|
||||
let subtitle = if sel_chat.is_device_talk() {
|
||||
"device-talk".to_string()
|
||||
} else if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
|
||||
} else if sel_chat.get_type() == Chattype::Single && !members.is_empty() {
|
||||
let contact = Contact::get_by_id(context, members[0])?;
|
||||
contact.get_addr().to_string()
|
||||
} else {
|
||||
@@ -862,11 +860,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
"archive" | "unarchive" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
||||
let chat_id = arg1.parse()?;
|
||||
chat::archive(
|
||||
context,
|
||||
chat_id,
|
||||
if arg0 == "archive" { true } else { false },
|
||||
)?;
|
||||
chat::archive(context, chat_id, arg0 == "archive")?;
|
||||
}
|
||||
"delchat" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
||||
|
||||
@@ -235,7 +235,7 @@ impl Completer for DcHelper {
|
||||
}
|
||||
}
|
||||
|
||||
const IMEX_COMMANDS: [&'static str; 12] = [
|
||||
const IMEX_COMMANDS: [&str; 12] = [
|
||||
"initiate-key-transfer",
|
||||
"get-setupcodebegin",
|
||||
"continue-key-transfer",
|
||||
@@ -250,7 +250,7 @@ const IMEX_COMMANDS: [&'static str; 12] = [
|
||||
"stop",
|
||||
];
|
||||
|
||||
const DB_COMMANDS: [&'static str; 11] = [
|
||||
const DB_COMMANDS: [&str; 11] = [
|
||||
"info",
|
||||
"open",
|
||||
"close",
|
||||
@@ -264,7 +264,7 @@ const DB_COMMANDS: [&'static str; 11] = [
|
||||
"housekeeping",
|
||||
];
|
||||
|
||||
const CHAT_COMMANDS: [&'static str; 24] = [
|
||||
const CHAT_COMMANDS: [&str; 24] = [
|
||||
"listchats",
|
||||
"listarchived",
|
||||
"chat",
|
||||
@@ -290,7 +290,7 @@ const CHAT_COMMANDS: [&'static str; 24] = [
|
||||
"unarchive",
|
||||
"delchat",
|
||||
];
|
||||
const MESSAGE_COMMANDS: [&'static str; 8] = [
|
||||
const MESSAGE_COMMANDS: [&str; 8] = [
|
||||
"listmsgs",
|
||||
"msginfo",
|
||||
"listfresh",
|
||||
@@ -300,7 +300,7 @@ const MESSAGE_COMMANDS: [&'static str; 8] = [
|
||||
"unstar",
|
||||
"delmsg",
|
||||
];
|
||||
const CONTACT_COMMANDS: [&'static str; 6] = [
|
||||
const CONTACT_COMMANDS: [&str; 6] = [
|
||||
"listcontacts",
|
||||
"listverified",
|
||||
"addcontact",
|
||||
@@ -308,7 +308,7 @@ const CONTACT_COMMANDS: [&'static str; 6] = [
|
||||
"delcontact",
|
||||
"cleanupcontacts",
|
||||
];
|
||||
const MISC_COMMANDS: [&'static str; 9] = [
|
||||
const MISC_COMMANDS: [&str; 9] = [
|
||||
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
|
||||
];
|
||||
|
||||
@@ -334,8 +334,8 @@ impl Hinter for DcHelper {
|
||||
}
|
||||
}
|
||||
|
||||
static COLORED_PROMPT: &'static str = "\x1b[1;32m> \x1b[0m";
|
||||
static PROMPT: &'static str = "> ";
|
||||
static COLORED_PROMPT: &str = "\x1b[1;32m> \x1b[0m";
|
||||
static PROMPT: &str = "> ";
|
||||
|
||||
impl Highlighter for DcHelper {
|
||||
fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
|
||||
@@ -366,11 +366,12 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
||||
println!("Error: Bad arguments, expected [db-name].");
|
||||
return Err(format_err!("No db-name specified"));
|
||||
}
|
||||
let context = Context::new(
|
||||
let context = ContextBuilder::new(
|
||||
Box::new(receive_event),
|
||||
"CLI".into(),
|
||||
Path::new(&args[1]).to_path_buf(),
|
||||
)?;
|
||||
)
|
||||
.create()?;
|
||||
|
||||
println!("Delta Chat Core is awaiting your commands.");
|
||||
|
||||
|
||||
@@ -39,8 +39,9 @@ fn main() {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
println!("creating database {:?}", dbfile);
|
||||
let ctx =
|
||||
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
||||
let ctx = ContextBuilder::new(Box::new(cb), "FakeOs".into(), dbfile)
|
||||
.create()
|
||||
.expect("Failed to create context");
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
let info = ctx.get_info();
|
||||
let duration = time::Duration::from_millis(4000);
|
||||
@@ -113,7 +114,7 @@ fn main() {
|
||||
|
||||
println!("stopping threads");
|
||||
|
||||
*running.clone().write().unwrap() = false;
|
||||
*running.write().unwrap() = false;
|
||||
deltachat::job::interrupt_inbox_idle(&ctx, true);
|
||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ DC_EVENT_IMEX_PROGRESS = 2051
|
||||
DC_EVENT_IMEX_FILE_WRITTEN = 2052
|
||||
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
||||
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
||||
DC_EVENT_SECUREJOIN_MEMBER_ADDED = 2062
|
||||
DC_EVENT_FILE_COPIED = 2055
|
||||
DC_EVENT_IS_OFFLINE = 2081
|
||||
DC_EVENT_GET_STRING = 2091
|
||||
@@ -150,7 +151,8 @@ DC_STR_MSGLOCATIONENABLED = 64
|
||||
DC_STR_MSGLOCATIONDISABLED = 65
|
||||
DC_STR_LOCATION = 66
|
||||
DC_STR_STICKER = 67
|
||||
DC_STR_COUNT = 67
|
||||
DC_STR_DEVICE_MESSAGES = 68
|
||||
DC_STR_COUNT = 68
|
||||
# end const generated
|
||||
|
||||
|
||||
|
||||
@@ -461,6 +461,20 @@ class TestOnlineAccount:
|
||||
assert ev[2] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
def test_move_works_on_self_sent(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account(mvbox=True)
|
||||
ac1.set_config("bcc_self", "1")
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
chat.send_text("message1")
|
||||
chat.send_text("message2")
|
||||
chat.send_text("message3")
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
def test_forward_messages(self, acfactory):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
@@ -768,6 +782,7 @@ class TestOnlineAccount:
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
ac2._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
wait_securejoin_inviter_progress(ac1, 1000)
|
||||
ac1._evlogger.get_matching("DC_EVENT_SECUREJOIN_MEMBER_ADDED")
|
||||
|
||||
def test_qr_verified_group_and_chatting(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
@@ -53,7 +53,7 @@ deps =
|
||||
sphinx==2.2.0
|
||||
breathe
|
||||
commands =
|
||||
sphinx-build -w toxdoc-warnings.log -b html . _build/html
|
||||
sphinx-build -Q -w toxdoc-warnings.log -b html . _build/html
|
||||
|
||||
|
||||
[testenv:lintdoc]
|
||||
|
||||
@@ -45,9 +45,8 @@ pub fn dc_is_configured(context: &Context) -> bool {
|
||||
/*******************************************************************************
|
||||
* Configure JOB
|
||||
******************************************************************************/
|
||||
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
|
||||
#[allow(non_snake_case, unused_must_use)]
|
||||
pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
pub fn JobConfigureImap(context: &Context) {
|
||||
if !context.sql.is_open() {
|
||||
error!(context, "Cannot configure, database not opened.",);
|
||||
progress!(context, 0);
|
||||
@@ -354,19 +353,14 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
}
|
||||
16 => {
|
||||
progress!(context, 900);
|
||||
let flags: libc::c_int = if context.get_config_bool(Config::MvboxWatch)
|
||||
|| context.get_config_bool(Config::MvboxMove)
|
||||
{
|
||||
DC_CREATE_MVBOX as i32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let create_mvbox = context.get_config_bool(Config::MvboxWatch)
|
||||
|| context.get_config_bool(Config::MvboxMove);
|
||||
context
|
||||
.inbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.configure_folders(context, flags);
|
||||
.configure_folders(context, create_mvbox);
|
||||
true
|
||||
}
|
||||
17 => {
|
||||
@@ -606,7 +600,7 @@ pub fn read_autoconf_file(context: &Context, url: &str) -> Option<String> {
|
||||
mod tests {
|
||||
|
||||
use crate::config::*;
|
||||
use crate::configure::dc_job_do_DC_JOB_CONFIGURE_IMAP;
|
||||
use crate::configure::JobConfigureImap;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
@@ -616,6 +610,6 @@ mod tests {
|
||||
.set_config(Config::Addr, Some("probably@unexistant.addr"))
|
||||
.unwrap();
|
||||
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
|
||||
dc_job_do_DC_JOB_CONFIGURE_IMAP(&t.ctx);
|
||||
JobConfigureImap(&t.ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,6 @@ lazy_static! {
|
||||
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
|
||||
pub enum MoveState {
|
||||
Undefined = 0,
|
||||
Pending = 1,
|
||||
Stay = 2,
|
||||
Moving = 3,
|
||||
}
|
||||
|
||||
impl Default for MoveState {
|
||||
fn default() -> Self {
|
||||
MoveState::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
// some defaults
|
||||
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
||||
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
||||
@@ -134,8 +119,6 @@ pub const DC_CONTACT_ID_INFO: u32 = 2;
|
||||
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
|
||||
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
pub const DC_CREATE_MVBOX: usize = 1;
|
||||
|
||||
// Flags for empty server job
|
||||
|
||||
pub const DC_EMPTY_MVBOX: u32 = 0x01;
|
||||
|
||||
@@ -585,7 +585,12 @@ impl Contact {
|
||||
|
||||
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
||||
|
||||
if peerstate.is_some() && peerstate.as_ref().and_then(|p| p.peek_key(0)).is_some() {
|
||||
if peerstate.is_some()
|
||||
&& peerstate
|
||||
.as_ref()
|
||||
.and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified))
|
||||
.is_some()
|
||||
{
|
||||
let peerstate = peerstate.as_ref().unwrap();
|
||||
let p =
|
||||
context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
|
||||
@@ -605,11 +610,11 @@ impl Contact {
|
||||
.map(|k| k.formatted_fingerprint())
|
||||
.unwrap_or_default();
|
||||
let fingerprint_other_verified = peerstate
|
||||
.peek_key(2)
|
||||
.peek_key(PeerstateVerifiedStatus::BidirectVerified)
|
||||
.map(|k| k.formatted_fingerprint())
|
||||
.unwrap_or_default();
|
||||
let fingerprint_other_unverified = peerstate
|
||||
.peek_key(0)
|
||||
.peek_key(PeerstateVerifiedStatus::Unverified)
|
||||
.map(|k| k.formatted_fingerprint())
|
||||
.unwrap_or_default();
|
||||
if peerstate.addr.is_some() && &loginparam.addr < peerstate.addr.as_ref().unwrap() {
|
||||
|
||||
152
src/context.rs
152
src/context.rs
@@ -15,6 +15,7 @@ use crate::imap::*;
|
||||
use crate::job::*;
|
||||
use crate::job_thread::JobThread;
|
||||
use crate::key::*;
|
||||
use crate::log;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::lot::Lot;
|
||||
use crate::message::{self, Message, MsgId};
|
||||
@@ -62,6 +63,7 @@ pub struct Context {
|
||||
/// Mutex to avoid generating the key for the user more than once.
|
||||
pub generating_key_mutex: Mutex<()>,
|
||||
pub translated_stockstrings: RwLock<HashMap<usize, String>>,
|
||||
pub logger: RwLock<log::Logger>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -94,30 +96,92 @@ pub fn get_info() -> HashMap<&'static str, String> {
|
||||
res
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Creates new context.
|
||||
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> {
|
||||
let mut blob_fname = OsString::new();
|
||||
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
||||
/// Builder for [Context].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat::context::ContextBuilder;
|
||||
/// let dir = tempfile::tempdir().unwrap();
|
||||
/// let dbfile = dir.path().join("my-context.db");
|
||||
/// let ctx = ContextBuilder::new(Box::new(|_, _| 0), "AppName".into(), dbfile)
|
||||
/// .blobdir(dir.path().join("my-context-blobs"))
|
||||
/// .logdir(dir.path().join("my-context-logs"))
|
||||
/// .create()
|
||||
/// .unwrap();
|
||||
/// assert_eq!(ctx.get_blobdir(), dir.path().join("my-context-blobs"));
|
||||
/// ```
|
||||
#[derive(DebugStub)]
|
||||
pub struct ContextBuilder {
|
||||
#[debug_stub = "Callback"]
|
||||
cb: Box<ContextCallback>,
|
||||
os_name: String,
|
||||
dbfile: PathBuf,
|
||||
blobdir: PathBuf,
|
||||
logdir: PathBuf,
|
||||
}
|
||||
|
||||
impl ContextBuilder {
|
||||
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Self {
|
||||
let db_fname = OsString::from(dbfile.file_name().unwrap_or(&OsString::from("dc-context")));
|
||||
let mut blob_fname = db_fname.clone();
|
||||
blob_fname.push("-blobs");
|
||||
let blobdir = dbfile.with_file_name(blob_fname);
|
||||
if !blobdir.exists() {
|
||||
std::fs::create_dir_all(&blobdir)?;
|
||||
let mut log_fname = db_fname.clone();
|
||||
log_fname.push("-logs");
|
||||
ContextBuilder {
|
||||
cb,
|
||||
os_name,
|
||||
blobdir: dbfile.with_file_name(blob_fname),
|
||||
logdir: dbfile.with_file_name(log_fname),
|
||||
dbfile,
|
||||
}
|
||||
Context::with_blobdir(cb, os_name, dbfile, blobdir)
|
||||
}
|
||||
|
||||
pub fn with_blobdir(
|
||||
pub fn blobdir(mut self, path: PathBuf) -> Self {
|
||||
self.blobdir = path;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn logdir(mut self, path: PathBuf) -> Self {
|
||||
self.logdir = path;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn create(self) -> Result<Context> {
|
||||
if !self.blobdir.exists() {
|
||||
std::fs::create_dir_all(&self.blobdir)?;
|
||||
}
|
||||
if !self.logdir.exists() {
|
||||
std::fs::create_dir_all(&self.logdir)?;
|
||||
}
|
||||
Context::new(
|
||||
self.cb,
|
||||
self.os_name,
|
||||
self.dbfile,
|
||||
self.blobdir,
|
||||
self.logdir,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
fn new(
|
||||
cb: Box<ContextCallback>,
|
||||
os_name: String,
|
||||
dbfile: PathBuf,
|
||||
blobdir: PathBuf,
|
||||
logdir: PathBuf,
|
||||
) -> Result<Context> {
|
||||
ensure!(
|
||||
blobdir.is_dir(),
|
||||
"Blobdir does not exist: {}",
|
||||
blobdir.display()
|
||||
);
|
||||
ensure!(
|
||||
logdir.is_dir(),
|
||||
"Logdir does not exist: {}",
|
||||
logdir.display()
|
||||
);
|
||||
let ctx = Context {
|
||||
blobdir,
|
||||
dbfile,
|
||||
@@ -150,6 +214,7 @@ impl Context {
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||
generating_key_mutex: Mutex::new(()),
|
||||
translated_stockstrings: RwLock::new(HashMap::new()),
|
||||
logger: RwLock::new(log::Logger::new(logdir)?),
|
||||
};
|
||||
|
||||
ensure!(
|
||||
@@ -435,10 +500,9 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.is_inbox(folder) && !self.is_sentbox(folder) {
|
||||
if self.is_mvbox(folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(msg) = Message::load_from_db(self, msg_id) {
|
||||
if msg.is_setupmessage() {
|
||||
// do not move setup messages;
|
||||
@@ -446,10 +510,6 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.is_mvbox(folder) {
|
||||
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
|
||||
// 1 = dc message, 2 = reply to dc message
|
||||
if 0 != msg.is_dc_message {
|
||||
job_add(
|
||||
@@ -459,7 +519,6 @@ impl Context {
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
message::update_msg_move_state(self, &msg.rfc724_mid, MoveState::Moving);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,12 +554,25 @@ pub struct BobStatus {
|
||||
pub qr_scan: Option<Lot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PerformJobsNeeded {
|
||||
Not,
|
||||
AtOnce,
|
||||
AvoidDos,
|
||||
}
|
||||
|
||||
impl Default for PerformJobsNeeded {
|
||||
fn default() -> Self {
|
||||
Self::Not
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SmtpState {
|
||||
pub idle: bool,
|
||||
pub suspended: bool,
|
||||
pub doing_jobs: bool,
|
||||
pub perform_jobs_needed: i32,
|
||||
pub perform_jobs_needed: PerformJobsNeeded,
|
||||
pub probe_network: bool,
|
||||
}
|
||||
|
||||
@@ -519,7 +591,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
std::fs::write(&dbfile, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| 0), "FakeOs".into(), dbfile);
|
||||
let res = ContextBuilder::new(Box::new(|_, _| 0), "FakeOs".into(), dbfile).create();
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -534,7 +606,9 @@ mod tests {
|
||||
fn test_blobdir_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile)
|
||||
.create()
|
||||
.unwrap();
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
assert!(blobdir.is_dir());
|
||||
}
|
||||
@@ -545,7 +619,7 @@ mod tests {
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
std::fs::write(&blobdir, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile);
|
||||
let res = ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).create();
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -555,7 +629,9 @@ mod tests {
|
||||
let subdir = tmp.path().join("subdir");
|
||||
let dbfile = subdir.join("db.sqlite");
|
||||
let dbfile2 = dbfile.clone();
|
||||
Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile)
|
||||
.create()
|
||||
.unwrap();
|
||||
assert!(subdir.is_dir());
|
||||
assert!(dbfile2.is_file());
|
||||
}
|
||||
@@ -565,7 +641,9 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = PathBuf::new();
|
||||
let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir);
|
||||
let res = ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile)
|
||||
.blobdir(blobdir)
|
||||
.create();
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -574,10 +652,36 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("blobs");
|
||||
let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir);
|
||||
ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile)
|
||||
.blobdir(blobdir.clone())
|
||||
.create()
|
||||
.unwrap();
|
||||
assert!(blobdir.is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_empty_logdir() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let logdir = PathBuf::new();
|
||||
let res = ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile)
|
||||
.logdir(logdir)
|
||||
.create();
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_logdir_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let logdir = tmp.path().join("logs");
|
||||
ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile)
|
||||
.logdir(logdir.clone())
|
||||
.create()
|
||||
.unwrap();
|
||||
assert!(logdir.is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_crashes_on_context_deref() {
|
||||
let t = dummy_context();
|
||||
|
||||
@@ -1640,7 +1640,11 @@ fn check_verified_properties(
|
||||
info!(context, "{} has verified {}.", contact.get_addr(), to_addr,);
|
||||
let fp = peerstate.gossip_key_fingerprint.clone();
|
||||
if let Some(fp) = fp {
|
||||
peerstate.set_verified(0, &fp, 2);
|
||||
peerstate.set_verified(
|
||||
DC_PS_GOSSIP_KEY,
|
||||
&fp,
|
||||
PeerstateVerifiedStatus::BidirectVerified,
|
||||
);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
is_verified = true;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ impl EncryptHelper {
|
||||
&mut self,
|
||||
factory: &mut MimeFactory,
|
||||
e2ee_guaranteed: bool,
|
||||
min_verified: libc::c_int,
|
||||
min_verified: PeerstateVerifiedStatus,
|
||||
do_gossip: bool,
|
||||
mut in_out_message: *mut Mailmime,
|
||||
imffields_unprotected: *mut mailimf_fields,
|
||||
@@ -118,10 +118,10 @@ impl EncryptHelper {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(key) = peerstate.peek_key(min_verified as usize) {
|
||||
if let Some(key) = peerstate.peek_key(min_verified) {
|
||||
keyring.add_owned(key.clone());
|
||||
if do_gossip {
|
||||
if let Some(header) = peerstate.render_gossip_header(min_verified as usize) {
|
||||
if let Some(header) = peerstate.render_gossip_header(min_verified) {
|
||||
gossip_headers.push(header.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,4 +243,11 @@ pub enum Event {
|
||||
/// @return 0
|
||||
#[strum(props(id = "2061"))]
|
||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||
|
||||
/// This event is sent out to the inviter when a joiner successfully joined a group.
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) contact_id
|
||||
/// @return 0
|
||||
#[strum(props(id = "2062"))]
|
||||
SecurejoinMemberAdded { chat_id: u32, contact_id: u32 },
|
||||
}
|
||||
|
||||
93
src/imap.rs
93
src/imap.rs
@@ -12,7 +12,7 @@ use async_imap::{
|
||||
types::{Fetch, Flag, Mailbox, Name, NameAttribute},
|
||||
};
|
||||
use async_std::prelude::*;
|
||||
use async_std::sync::{Arc, Mutex, RwLock};
|
||||
use async_std::sync::{Mutex, RwLock};
|
||||
use async_std::task;
|
||||
|
||||
use crate::constants::*;
|
||||
@@ -23,7 +23,7 @@ use crate::events::Event;
|
||||
use crate::imap_client::*;
|
||||
use crate::job::{job_add, Action};
|
||||
use crate::login_param::{CertificateChecks, LoginParam};
|
||||
use crate::message::{self, update_msg_move_state, update_server_uid};
|
||||
use crate::message::{self, update_server_uid};
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::param::Params;
|
||||
use crate::stock::StockMessage;
|
||||
@@ -45,11 +45,10 @@ const SELECT_ALL: &str = "1:*";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Imap {
|
||||
config: Arc<RwLock<ImapConfig>>,
|
||||
|
||||
session: Arc<Mutex<Option<Session>>>,
|
||||
connected: Arc<Mutex<bool>>,
|
||||
interrupt: Arc<Mutex<Option<stop_token::StopSource>>>,
|
||||
config: RwLock<ImapConfig>,
|
||||
session: Mutex<Option<Session>>,
|
||||
connected: Mutex<bool>,
|
||||
interrupt: Mutex<Option<stop_token::StopSource>>,
|
||||
skip_next_idle_wait: AtomicBool,
|
||||
should_reconnect: AtomicBool,
|
||||
}
|
||||
@@ -118,10 +117,10 @@ impl Default for ImapConfig {
|
||||
impl Imap {
|
||||
pub fn new() -> Self {
|
||||
Imap {
|
||||
session: Arc::new(Mutex::new(None)),
|
||||
config: Arc::new(RwLock::new(ImapConfig::default())),
|
||||
interrupt: Arc::new(Mutex::new(None)),
|
||||
connected: Arc::new(Mutex::new(false)),
|
||||
session: Mutex::new(None),
|
||||
config: RwLock::new(ImapConfig::default()),
|
||||
interrupt: Mutex::new(None),
|
||||
connected: Mutex::new(false),
|
||||
skip_next_idle_wait: AtomicBool::new(false),
|
||||
should_reconnect: AtomicBool::new(false),
|
||||
}
|
||||
@@ -306,7 +305,7 @@ impl Imap {
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
self.configure_folders(context, 0x1);
|
||||
self.configure_folders(context, true);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -887,37 +886,43 @@ impl Imap {
|
||||
let interval = async_std::stream::interval(Duration::from_secs(60));
|
||||
let mut interrupt_interval = interrupt.stop_token().stop_stream(interval);
|
||||
*self.interrupt.lock().await = Some(interrupt);
|
||||
if self.skip_next_idle_wait.load(Ordering::SeqCst) {
|
||||
// interrupt_idle has happened before we
|
||||
// provided self.interrupt
|
||||
self.skip_next_idle_wait.store(false, Ordering::SeqCst);
|
||||
info!(context, "fake-idle wait was skipped");
|
||||
} else {
|
||||
// loop until we are interrupted or if we fetched something
|
||||
while let Some(_) = interrupt_interval.next().await {
|
||||
// try to connect with proper login params
|
||||
// (setup_handle_if_needed might not know about them if we
|
||||
// never successfully connected)
|
||||
if let Err(err) = self.connect_configured(context) {
|
||||
warn!(context, "fake_idle: could not connect: {}", err);
|
||||
continue;
|
||||
}
|
||||
if self.config.read().await.can_idle {
|
||||
// we only fake-idled because network was gone during IDLE, probably
|
||||
break;
|
||||
}
|
||||
info!(context, "fake_idle is connected");
|
||||
// we are connected, let's see if fetching messages results
|
||||
// in anything. If so, we behave as if IDLE had data but
|
||||
// will have already fetched the messages so perform_*_fetch
|
||||
// will not find any new.
|
||||
|
||||
// loop until we are interrupted or if we fetched something
|
||||
while let Some(_) = interrupt_interval.next().await {
|
||||
// try to connect with proper login params
|
||||
// (setup_handle_if_needed might not know about them if we
|
||||
// never successfully connected)
|
||||
if let Err(err) = self.connect_configured(context) {
|
||||
warn!(context, "fake_idle: could not connect: {}", err);
|
||||
continue;
|
||||
}
|
||||
if self.config.read().await.can_idle {
|
||||
// we only fake-idled because network was gone during IDLE, probably
|
||||
break;
|
||||
}
|
||||
info!(context, "fake_idle is connected");
|
||||
// we are connected, let's see if fetching messages results
|
||||
// in anything. If so, we behave as if IDLE had data but
|
||||
// will have already fetched the messages so perform_*_fetch
|
||||
// will not find any new.
|
||||
|
||||
if let Some(ref watch_folder) = watch_folder {
|
||||
match self.fetch_from_single_folder(context, watch_folder).await {
|
||||
Ok(res) => {
|
||||
info!(context, "fetch_from_single_folder returned {:?}", res);
|
||||
if res {
|
||||
break;
|
||||
if let Some(ref watch_folder) = watch_folder {
|
||||
match self.fetch_from_single_folder(context, watch_folder).await {
|
||||
Ok(res) => {
|
||||
info!(context, "fetch_from_single_folder returned {:?}", res);
|
||||
if res {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, "could not fetch from folder: {}", err);
|
||||
self.trigger_reconnect()
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, "could not fetch from folder: {}", err);
|
||||
self.trigger_reconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1201,7 +1206,7 @@ impl Imap {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn configure_folders(&self, context: &Context, flags: libc::c_int) {
|
||||
pub fn configure_folders(&self, context: &Context, create_mvbox: bool) {
|
||||
task::block_on(async move {
|
||||
if !self.is_connected().await {
|
||||
return;
|
||||
@@ -1232,7 +1237,7 @@ impl Imap {
|
||||
.find(|folder| folder.name() == "DeltaChat" || folder.name() == fallback_folder)
|
||||
.map(|n| n.name().to_string());
|
||||
|
||||
if mvbox_folder.is_none() && 0 != (flags as usize & DC_CREATE_MVBOX) {
|
||||
if mvbox_folder.is_none() && create_mvbox {
|
||||
info!(context, "Creating MVBOX-folder \"DeltaChat\"...",);
|
||||
|
||||
match session.create("DeltaChat").await {
|
||||
@@ -1408,6 +1413,7 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
|
||||
{
|
||||
if old_server_folder.is_empty() && old_server_uid == 0 {
|
||||
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
|
||||
context.do_heuristics_moves(server_folder.as_ref(), msg_id);
|
||||
job_add(
|
||||
context,
|
||||
Action::MarkseenMsgOnImap,
|
||||
@@ -1417,7 +1423,6 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
|
||||
);
|
||||
} else if old_server_folder != server_folder {
|
||||
info!(context, "[move] detected moved message {}", rfc724_mid,);
|
||||
update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
|
||||
if old_server_folder != server_folder || old_server_uid != server_uid {
|
||||
|
||||
@@ -72,11 +72,12 @@ impl Client {
|
||||
pub async fn secure<S: AsRef<str>>(
|
||||
self,
|
||||
domain: S,
|
||||
_certificate_checks: CertificateChecks,
|
||||
certificate_checks: CertificateChecks,
|
||||
) -> ImapResult<Client> {
|
||||
match self {
|
||||
Client::Insecure(client) => {
|
||||
let tls = async_tls::TlsConnector::new();
|
||||
let tls_config = dc_build_tls_config(certificate_checks);
|
||||
let tls: async_tls::TlsConnector = Arc::new(tls_config).into();
|
||||
|
||||
let client_sec = client.secure(domain, &tls).await?;
|
||||
|
||||
|
||||
@@ -367,7 +367,7 @@ pub fn normalize_setup_code(s: &str) -> String {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
|
||||
pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
|
||||
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
|
||||
let param = job.param.get(Param::Arg).unwrap_or_default();
|
||||
|
||||
427
src/job.rs
427
src/job.rs
@@ -1,4 +1,3 @@
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
@@ -9,13 +8,12 @@ use crate::chat;
|
||||
use crate::config::Config;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::context::{Context, PerformJobsNeeded};
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::imap::*;
|
||||
use crate::imex::*;
|
||||
use crate::job_thread::JobThread;
|
||||
use crate::location;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::MsgId;
|
||||
@@ -24,6 +22,9 @@ use crate::mimefactory::{vec_contains_lowercase, Loaded, MimeFactory};
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
|
||||
// results in ~3 weeks for the last backoff timespan
|
||||
const JOB_RETRIES: u32 = 17;
|
||||
|
||||
/// Thread IDs
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(i32)]
|
||||
@@ -33,14 +34,6 @@ enum Thread {
|
||||
Smtp = 5000,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum ThreadExecutor {
|
||||
Inbox,
|
||||
Mvbox,
|
||||
Sentbox,
|
||||
Smtp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
enum TryAgain {
|
||||
Dont,
|
||||
@@ -117,7 +110,7 @@ pub struct Job {
|
||||
pub foreign_id: u32,
|
||||
pub desired_timestamp: i64,
|
||||
pub added_timestamp: i64,
|
||||
pub tries: i32,
|
||||
pub tries: u32,
|
||||
pub param: Params,
|
||||
try_again: TryAgain,
|
||||
pub pending_error: Option<String>,
|
||||
@@ -147,7 +140,7 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_SEND(&mut self, context: &Context) {
|
||||
fn SendMsgToSmtp(&mut self, context: &Context) {
|
||||
/* connect to SMTP server, if not yet done */
|
||||
if !context.smtp.lock().unwrap().is_connected() {
|
||||
let loginparam = LoginParam::from_database(context, "configured_");
|
||||
@@ -219,9 +212,8 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context, thread: ThreadExecutor) {
|
||||
let t = get_mailbox_for_executor(context, thread);
|
||||
let imap_inbox = &t.read().unwrap().imap;
|
||||
fn MoveMsg(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
if context
|
||||
@@ -230,7 +222,7 @@ impl Job {
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
imap_inbox.configure_folders(context, 0x1i32);
|
||||
imap_inbox.configure_folders(context, true);
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
@@ -265,9 +257,8 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context, thread: ThreadExecutor) {
|
||||
let t = get_mailbox_for_executor(context, thread);
|
||||
let imap_inbox = &t.read().unwrap().imap;
|
||||
fn DeleteMsgOnImap(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
if let Ok(mut msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
if !msg.rfc724_mid.is_empty() {
|
||||
@@ -295,26 +286,24 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_EMPTY_SERVER(&mut self, context: &Context, thread: ThreadExecutor) {
|
||||
let t = get_mailbox_for_executor(context, thread);
|
||||
let imap_inbox = &t.read().unwrap().imap;
|
||||
|
||||
if thread == ThreadExecutor::Mvbox && DC_EMPTY_MVBOX > 0 {
|
||||
fn EmptyServer(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
if self.foreign_id & DC_EMPTY_MVBOX > 0 {
|
||||
if let Some(mvbox_folder) = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder")
|
||||
{
|
||||
imap_inbox.empty_folder(context, &mvbox_folder);
|
||||
}
|
||||
} else if thread == ThreadExecutor::Inbox && DC_EMPTY_INBOX > 0 {
|
||||
}
|
||||
if self.foreign_id & DC_EMPTY_INBOX > 0 {
|
||||
imap_inbox.empty_folder(context, "INBOX");
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context, thread: ThreadExecutor) {
|
||||
let t = get_mailbox_for_executor(context, thread);
|
||||
let imap_inbox = &t.read().unwrap().imap;
|
||||
fn MarkseenMsgOnImap(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
@@ -341,17 +330,14 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context, thread: ThreadExecutor) {
|
||||
fn MarkseenMdnOnImap(&mut self, context: &Context) {
|
||||
let folder = self
|
||||
.param
|
||||
.get(Param::ServerFolder)
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
||||
|
||||
let t = get_mailbox_for_executor(context, thread);
|
||||
let imap_inbox = &t.read().unwrap().imap;
|
||||
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
if imap_inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater {
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
return;
|
||||
@@ -363,7 +349,7 @@ impl Job {
|
||||
.unwrap_or_default()
|
||||
< 3
|
||||
{
|
||||
imap_inbox.configure_folders(context, 0x1i32);
|
||||
imap_inbox.configure_folders(context, true);
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
@@ -494,7 +480,7 @@ pub fn perform_smtp_jobs(context: &Context) {
|
||||
|
||||
let probe_smtp_network = state.probe_network;
|
||||
state.probe_network = false;
|
||||
state.perform_jobs_needed = 0;
|
||||
state.perform_jobs_needed = PerformJobsNeeded::Not;
|
||||
|
||||
if state.suspended {
|
||||
info!(context, "SMTP-jobs suspended.",);
|
||||
@@ -505,7 +491,7 @@ pub fn perform_smtp_jobs(context: &Context) {
|
||||
};
|
||||
|
||||
info!(context, "SMTP-jobs started...",);
|
||||
job_perform(context, ThreadExecutor::Smtp, probe_smtp_network);
|
||||
job_perform(context, Thread::Smtp, probe_smtp_network);
|
||||
info!(context, "SMTP-jobs ended.");
|
||||
|
||||
{
|
||||
@@ -522,24 +508,27 @@ pub fn perform_smtp_idle(context: &Context) {
|
||||
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
if state.perform_jobs_needed == 1 {
|
||||
info!(
|
||||
context,
|
||||
"SMTP-idle will not be started because of waiting jobs.",
|
||||
);
|
||||
} else {
|
||||
let dur = get_next_wakeup_time(context, Thread::Smtp);
|
||||
|
||||
loop {
|
||||
let res = cvar.wait_timeout(state, dur).unwrap();
|
||||
state = res.0;
|
||||
|
||||
if state.idle || res.1.timed_out() {
|
||||
// We received the notification and the value has been updated, we can leave.
|
||||
break;
|
||||
}
|
||||
match state.perform_jobs_needed {
|
||||
PerformJobsNeeded::AtOnce => {
|
||||
info!(
|
||||
context,
|
||||
"SMTP-idle will not be started because of waiting jobs.",
|
||||
);
|
||||
}
|
||||
PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => {
|
||||
let dur = get_next_wakeup_time(context, Thread::Smtp);
|
||||
|
||||
loop {
|
||||
let res = cvar.wait_timeout(state, dur).unwrap();
|
||||
state = res.0;
|
||||
|
||||
if state.idle || res.1.timed_out() {
|
||||
// We received the notification and the value has been updated, we can leave.
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.idle = false;
|
||||
}
|
||||
state.idle = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,111 +707,32 @@ pub fn perform_inbox_jobs(context: &Context) {
|
||||
*context.probe_imap_network.write().unwrap() = false;
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = false;
|
||||
|
||||
job_perform(context, ThreadExecutor::Inbox, probe_imap_network);
|
||||
job_perform(context, Thread::Imap, probe_imap_network);
|
||||
info!(context, "dc_perform_inbox_jobs ended.",);
|
||||
}
|
||||
|
||||
pub fn perform_mvbox_jobs(context: &Context) {
|
||||
info!(context, "dc_perform_mbox_jobs starting.",);
|
||||
|
||||
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap();
|
||||
*context.probe_imap_network.write().unwrap() = false;
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = false;
|
||||
|
||||
job_perform(context, ThreadExecutor::Mvbox, probe_imap_network);
|
||||
info!(context, "dc_perform_mbox_jobs ended.",);
|
||||
info!(context, "dc_perform_mbox_jobs EMPTY (for now).",);
|
||||
}
|
||||
|
||||
pub fn perform_sentbox_jobs(context: &Context) {
|
||||
info!(context, "dc_perform_sentbox_jobs starting.",);
|
||||
|
||||
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap();
|
||||
*context.probe_imap_network.write().unwrap() = false;
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = false;
|
||||
|
||||
job_perform(context, ThreadExecutor::Sentbox, probe_imap_network);
|
||||
info!(context, "dc_perform_sentbox_jobs ended.",);
|
||||
info!(context, "dc_perform_sentbox_jobs EMPTY (for now).",);
|
||||
}
|
||||
|
||||
fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
|
||||
let folder = get_folder_for_executor(context, thread);
|
||||
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
let jobs: Vec<Job> = load_jobs(context, thread, probe_network);
|
||||
|
||||
let fallback = if thread == ThreadExecutor::Inbox || thread == ThreadExecutor::Smtp {
|
||||
// only select non empty ones on the inbox thread or smtp
|
||||
"OR (j.foreign_id IS NULL)) "
|
||||
} else {
|
||||
") "
|
||||
};
|
||||
|
||||
let query_start = concat!(
|
||||
"SELECT ",
|
||||
"j.id AS id, ",
|
||||
"j.action AS action, ",
|
||||
"j.foreign_id AS foreign_id, ",
|
||||
"j.param AS param, ",
|
||||
"j.added_timestamp AS added_timestamp, ",
|
||||
"j.desired_timestamp AS desired_timestamp, ",
|
||||
"j.tries AS tries ",
|
||||
"FROM jobs j ",
|
||||
"LEFT JOIN msgs m ON j.foreign_id=m.id ",
|
||||
)
|
||||
.to_string();
|
||||
let query = if !probe_network {
|
||||
// processing for first-try and after backoff-timeouts:
|
||||
// process jobs in the order they were added.
|
||||
query_start
|
||||
+ "WHERE j.thread=? AND j.desired_timestamp<=? AND "
|
||||
+ "((j.foreign_id IS NOT NULL AND m.server_folder=?) "
|
||||
+ fallback
|
||||
+ "ORDER BY j.action DESC, j.added_timestamp;"
|
||||
} else {
|
||||
// processing after call to dc_maybe_network():
|
||||
// process _all_ pending jobs that failed before
|
||||
// in the order of their backoff-times.
|
||||
query_start
|
||||
+ "WHERE j.thread=? AND j.tries>0 AND "
|
||||
+ "((j.foreign_id IS NOT NULL AND m.server_folder=?) "
|
||||
+ fallback
|
||||
+ "ORDER BY j.desired_timestamp, j.action DESC;"
|
||||
};
|
||||
|
||||
let params_no_probe = params![thread as i64, time(), folder];
|
||||
let params_probe = params![thread as i64, folder];
|
||||
let params: &[&dyn rusqlite::ToSql] = if !probe_network {
|
||||
params_no_probe
|
||||
} else {
|
||||
params_probe
|
||||
};
|
||||
let jobs: Result<Vec<Job>, _> = context
|
||||
.sql
|
||||
.query_map(
|
||||
query,
|
||||
params,
|
||||
|row| {
|
||||
let job = Job {
|
||||
job_id: row.get(0)?,
|
||||
action: row.get(1)?,
|
||||
foreign_id: row.get(2)?,
|
||||
desired_timestamp: row.get(5)?,
|
||||
added_timestamp: row.get(4)?,
|
||||
tries: row.get(6)?,
|
||||
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
|
||||
try_again: TryAgain::Dont,
|
||||
pending_error: None,
|
||||
};
|
||||
|
||||
Ok(job)
|
||||
},
|
||||
|jobs| jobs.collect::<Result<Vec<Job>, _>>().map_err(Into::into),
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!(context, "query failed: {:?}", err);
|
||||
});
|
||||
|
||||
for mut job in jobs.unwrap_or_default() {
|
||||
for mut job in jobs {
|
||||
info!(
|
||||
context,
|
||||
"{:?}-job #{}, action {} started...", thread, job.job_id, job.action,
|
||||
"{}-job #{}, action {} started...",
|
||||
if thread == Thread::Imap {
|
||||
"INBOX"
|
||||
} else {
|
||||
"SMTP"
|
||||
},
|
||||
job.job_id,
|
||||
job.action,
|
||||
);
|
||||
|
||||
// some configuration jobs are "exclusive":
|
||||
@@ -846,34 +756,31 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
|
||||
suspend_smtp_thread(context, true);
|
||||
}
|
||||
|
||||
let mut tries = 0;
|
||||
while tries <= 1 {
|
||||
for _tries in 0..2 {
|
||||
// this can be modified by a job using dc_job_try_again_later()
|
||||
job.try_again = TryAgain::Dont;
|
||||
|
||||
match job.action {
|
||||
Action::Unknown => {
|
||||
warn!(context, "Unknown job id found");
|
||||
info!(context, "Unknown job id found");
|
||||
}
|
||||
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
|
||||
Action::EmptyServer => job.do_DC_JOB_EMPTY_SERVER(context, thread),
|
||||
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context, thread),
|
||||
Action::MarkseenMsgOnImap => job.do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context, thread),
|
||||
Action::MarkseenMdnOnImap => job.do_DC_JOB_MARKSEEN_MDN_ON_IMAP(context, thread),
|
||||
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context, thread),
|
||||
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
||||
Action::ConfigureImap => dc_job_do_DC_JOB_CONFIGURE_IMAP(context),
|
||||
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
|
||||
Action::SendMsgToSmtp => job.SendMsgToSmtp(context),
|
||||
Action::EmptyServer => job.EmptyServer(context),
|
||||
Action::DeleteMsgOnImap => job.DeleteMsgOnImap(context),
|
||||
Action::MarkseenMsgOnImap => job.MarkseenMsgOnImap(context),
|
||||
Action::MarkseenMdnOnImap => job.MarkseenMdnOnImap(context),
|
||||
Action::MoveMsg => job.MoveMsg(context),
|
||||
Action::SendMdn => job.SendMsgToSmtp(context),
|
||||
Action::ConfigureImap => JobConfigureImap(context),
|
||||
Action::ImexImap => match JobImexImap(context, &job) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
error!(context, "{}", err);
|
||||
}
|
||||
},
|
||||
Action::MaybeSendLocations => {
|
||||
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
|
||||
}
|
||||
Action::MaybeSendLocations => location::JobMaybeSendLocations(context, &job),
|
||||
Action::MaybeSendLocationsEnded => {
|
||||
location::job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
|
||||
location::JobMaybeSendLocationsEnded(context, &mut job)
|
||||
}
|
||||
Action::Housekeeping => sql::housekeeping(context),
|
||||
Action::SendMdnOld => {}
|
||||
@@ -882,7 +789,6 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
|
||||
if job.try_again != TryAgain::AtOnce {
|
||||
break;
|
||||
}
|
||||
tries += 1
|
||||
}
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
context
|
||||
@@ -901,28 +807,31 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
|
||||
break;
|
||||
} else if job.try_again == TryAgain::AtOnce || job.try_again == TryAgain::StandardDelay {
|
||||
let tries = job.tries + 1;
|
||||
if tries < 17 {
|
||||
if tries < JOB_RETRIES {
|
||||
job.tries = tries;
|
||||
let time_offset = get_backoff_time_offset(tries);
|
||||
job.desired_timestamp = job.added_timestamp + time_offset;
|
||||
job.desired_timestamp = time() + time_offset;
|
||||
job.update(context);
|
||||
info!(
|
||||
context,
|
||||
"{:?}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
|
||||
thread,
|
||||
"{}-job #{} not succeeded on try #{}, retry in {} seconds.",
|
||||
if thread == Thread::Imap {
|
||||
"INBOX"
|
||||
} else {
|
||||
"SMTP"
|
||||
},
|
||||
job.job_id as u32,
|
||||
tries,
|
||||
time_offset,
|
||||
job.added_timestamp + time_offset - time()
|
||||
time_offset
|
||||
);
|
||||
if thread == ThreadExecutor::Smtp && tries < 17 - 1 {
|
||||
if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
|
||||
context
|
||||
.smtp_state
|
||||
.clone()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.perform_jobs_needed = 2;
|
||||
.perform_jobs_needed = PerformJobsNeeded::AvoidDos;
|
||||
}
|
||||
} else {
|
||||
if job.action == Action::SendMsgToSmtp {
|
||||
@@ -948,13 +857,11 @@ fn job_perform(context: &Context, thread: ThreadExecutor, probe_network: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn get_backoff_time_offset(c_tries: libc::c_int) -> i64 {
|
||||
// results in ~3 weeks for the last backoff timespan
|
||||
let N = 2_i32.pow((c_tries - 1) as u32) * 60;
|
||||
fn get_backoff_time_offset(tries: u32) -> i64 {
|
||||
let n = 2_i32.pow(tries - 1) * 60;
|
||||
let mut rng = thread_rng();
|
||||
let n: i32 = rng.gen();
|
||||
let mut seconds = n % (N + 1);
|
||||
let r: i32 = rng.gen();
|
||||
let mut seconds = r % (n + 1);
|
||||
if seconds < 1 {
|
||||
seconds = 1;
|
||||
}
|
||||
@@ -1043,76 +950,122 @@ pub fn job_add(
|
||||
).ok();
|
||||
|
||||
match thread {
|
||||
Thread::Imap => {
|
||||
let folder: String = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT server_folder FROM msgs WHERE id=?",
|
||||
params![foreign_id],
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mbox_thread = get_matching_mailbox(context, &folder);
|
||||
let res = mbox_thread.try_read();
|
||||
match res {
|
||||
Ok(mbox_thread) => {
|
||||
mbox_thread.interrupt_idle(context);
|
||||
}
|
||||
Err(err) => {
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = true;
|
||||
warn!(context, "could not interrupt idle: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Thread::Imap => interrupt_inbox_idle(context, false),
|
||||
Thread::Smtp => interrupt_smtp_idle(context),
|
||||
Thread::Unknown => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_folder_for_executor(context: &Context, thread: ThreadExecutor) -> String {
|
||||
let t = get_mailbox_for_executor(context, thread);
|
||||
let b = t.read().unwrap();
|
||||
b.get_watch_folder(context).unwrap_or_default().to_string()
|
||||
}
|
||||
|
||||
fn get_mailbox_for_executor(context: &Context, thread: ThreadExecutor) -> Arc<RwLock<JobThread>> {
|
||||
match thread {
|
||||
ThreadExecutor::Inbox => context.inbox_thread.clone(),
|
||||
ThreadExecutor::Mvbox => context.mvbox_thread.clone(),
|
||||
ThreadExecutor::Sentbox => context.sentbox_thread.clone(),
|
||||
ThreadExecutor::Smtp => panic!("do not use for smtp"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_matching_mailbox(context: &Context, folder: &String) -> Arc<RwLock<JobThread>> {
|
||||
let mvbox_folder = context
|
||||
.mvbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_watch_folder(context);
|
||||
let sentbox_folder = context
|
||||
.sentbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_watch_folder(context);
|
||||
|
||||
if mvbox_folder.is_some() && folder == mvbox_folder.as_ref().unwrap() {
|
||||
context.mvbox_thread.clone()
|
||||
} else if sentbox_folder.is_some() && folder == sentbox_folder.as_ref().unwrap() {
|
||||
context.sentbox_thread.clone()
|
||||
} else {
|
||||
context.inbox_thread.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interrupt_smtp_idle(context: &Context) {
|
||||
info!(context, "Interrupting SMTP-idle...",);
|
||||
|
||||
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
state.perform_jobs_needed = 1;
|
||||
state.perform_jobs_needed = PerformJobsNeeded::AtOnce;
|
||||
state.idle = true;
|
||||
cvar.notify_one();
|
||||
info!(context, "Interrupting SMTP-idle... ended",);
|
||||
}
|
||||
|
||||
/// Load jobs from the database.
|
||||
///
|
||||
/// Load jobs for this "[Thread]", i.e. either load SMTP jobs or load
|
||||
/// IMAP jobs. The `probe_network` parameter decides how to query
|
||||
/// jobs, this is tricky and probably wrong currently. Look at the
|
||||
/// SQL queries for details.
|
||||
fn load_jobs(context: &Context, thread: Thread, probe_network: bool) -> Vec<Job> {
|
||||
let query = if !probe_network {
|
||||
// processing for first-try and after backoff-timeouts:
|
||||
// process jobs in the order they were added.
|
||||
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
|
||||
FROM jobs WHERE thread=? AND desired_timestamp<=? ORDER BY action DESC, added_timestamp;"
|
||||
} else {
|
||||
// processing after call to dc_maybe_network():
|
||||
// process _all_ pending jobs that failed before
|
||||
// in the order of their backoff-times.
|
||||
"SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \
|
||||
FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;"
|
||||
};
|
||||
|
||||
let params_no_probe = params![thread as i64, time()];
|
||||
let params_probe = params![thread as i64];
|
||||
let params: &[&dyn rusqlite::ToSql] = if !probe_network {
|
||||
params_no_probe
|
||||
} else {
|
||||
params_probe
|
||||
};
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
query,
|
||||
params,
|
||||
|row| {
|
||||
let job = Job {
|
||||
job_id: row.get(0)?,
|
||||
action: row.get(1)?,
|
||||
foreign_id: row.get(2)?,
|
||||
desired_timestamp: row.get(5)?,
|
||||
added_timestamp: row.get(4)?,
|
||||
tries: row.get(6)?,
|
||||
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
|
||||
try_again: TryAgain::Dont,
|
||||
pending_error: None,
|
||||
};
|
||||
|
||||
Ok(job)
|
||||
},
|
||||
|jobs| {
|
||||
let mut ret: Vec<Job> = Vec::new();
|
||||
for job in jobs {
|
||||
match job {
|
||||
Ok(j) => ret.push(j),
|
||||
Err(e) => warn!(context, "Bad job from the database: {}", e),
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
fn insert_job(context: &Context, foreign_id: i64) {
|
||||
let now = time();
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO jobs
|
||||
(added_timestamp, thread, action, foreign_id, param, desired_timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?);",
|
||||
params![
|
||||
now,
|
||||
Thread::from(Action::MoveMsg),
|
||||
Action::MoveMsg,
|
||||
foreign_id,
|
||||
Params::new().to_string(),
|
||||
now
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_jobs() {
|
||||
// We want to ensure that loading jobs skips over jobs which
|
||||
// fails to load from the database instead of failing to load
|
||||
// all jobs.
|
||||
let t = dummy_context();
|
||||
insert_job(&t.ctx, 0);
|
||||
insert_job(&t.ctx, -1); // This can not be loaded into Job struct.
|
||||
insert_job(&t.ctx, 1);
|
||||
let jobs = load_jobs(&t.ctx, Thread::from(Action::MoveMsg), false);
|
||||
assert_eq!(jobs.len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ impl JobThread {
|
||||
|
||||
state.idle = true;
|
||||
cvar.notify_one();
|
||||
info!(context, "Interrupting {}-IDLE... finished", self.name);
|
||||
}
|
||||
|
||||
pub fn fetch(&mut self, context: &Context, use_network: bool) {
|
||||
@@ -116,7 +117,7 @@ impl JobThread {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_watch_folder(&self, context: &Context) -> Option<String> {
|
||||
fn get_watch_folder(&self, context: &Context) -> Option<String> {
|
||||
match context.sql.get_raw_config(context, self.folder_config_name) {
|
||||
Some(name) => Some(name),
|
||||
None => {
|
||||
|
||||
@@ -22,7 +22,7 @@ extern crate jetscii;
|
||||
extern crate debug_stub_derive;
|
||||
|
||||
#[macro_use]
|
||||
mod log;
|
||||
pub mod log;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
|
||||
|
||||
@@ -538,7 +538,7 @@ pub fn save(
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
pub fn JobMaybeSendLocations(context: &Context, _job: &Job) {
|
||||
let now = time();
|
||||
let mut continue_streaming = false;
|
||||
info!(
|
||||
@@ -628,7 +628,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
|
||||
pub fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) {
|
||||
// this function is called when location-streaming _might_ have ended for a chat.
|
||||
// the function checks, if location-streaming is really ended;
|
||||
// if so, a device-message is added if not yet done.
|
||||
|
||||
263
src/log.rs
263
src/log.rs
@@ -1,14 +1,180 @@
|
||||
//! # Logging macros
|
||||
//! # Logging support
|
||||
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// A logger for a [Context].
|
||||
#[derive(Debug)]
|
||||
pub struct Logger {
|
||||
created: std::time::Instant,
|
||||
logdir: PathBuf,
|
||||
logfile: String,
|
||||
file_handle: fs::File,
|
||||
max_files: u32,
|
||||
max_filesize: usize,
|
||||
bytes_written: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum LogLevel {
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl fmt::Display for LogLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LogLevel::Info => write!(f, "I"),
|
||||
LogLevel::Warning => write!(f, "W"),
|
||||
LogLevel::Error => write!(f, "E"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Callsite<'a> {
|
||||
pub file: &'a str,
|
||||
pub line: u32,
|
||||
}
|
||||
|
||||
impl fmt::Display for Callsite<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.file, self.line)
|
||||
}
|
||||
}
|
||||
|
||||
impl Logger {
|
||||
pub fn new(logdir: PathBuf) -> Result<Logger, io::Error> {
|
||||
let (fname, file) = Self::open(&logdir)?;
|
||||
let max_files = 5;
|
||||
Self::prune(&logdir, max_files)?;
|
||||
Ok(Logger {
|
||||
created: std::time::Instant::now(),
|
||||
logdir,
|
||||
logfile: fname,
|
||||
file_handle: file,
|
||||
max_files,
|
||||
max_filesize: 4 * 1024 * 1024, // 4 Mb
|
||||
bytes_written: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Opens a new logfile, returning a tuple of (file_name, file_handle).
|
||||
///
|
||||
/// This tries to create a new logfile based on the current time,
|
||||
/// creating .0.log, .1.log etc if this file already exists (up to 32).
|
||||
fn open(logdir: &Path) -> Result<(String, fs::File), io::Error> {
|
||||
let basename =
|
||||
chrono::offset::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
|
||||
let mut fname = sanitize_filename::sanitize_with_options(
|
||||
format!("{}.log", &basename),
|
||||
sanitize_filename::Options {
|
||||
truncate: true,
|
||||
windows: true,
|
||||
replacement: ".",
|
||||
},
|
||||
);
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
match std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(logdir.join(&fname))
|
||||
{
|
||||
Ok(file) => {
|
||||
return Ok((fname, file));
|
||||
}
|
||||
Err(e) => {
|
||||
if counter >= 32 {
|
||||
return Err(e);
|
||||
} else {
|
||||
counter += 1;
|
||||
fname =
|
||||
sanitize_filename::sanitize(format!("{}-{}.log", &basename, counter));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cleans up old logfiles.
|
||||
fn prune(logdir: &Path, max_files: u32) -> Result<(), io::Error> {
|
||||
let mut names: Vec<std::ffi::OsString> = Vec::new();
|
||||
for dirent in fs::read_dir(logdir)? {
|
||||
names.push(dirent?.file_name());
|
||||
}
|
||||
// Sorting like this sorts: 23.log, 24.1.log, 24.2.log,
|
||||
// 24.log, 25.log. That is 24.log is out of sequence. Oh well.
|
||||
names.sort();
|
||||
names.reverse();
|
||||
while names.len() > max_files as usize {
|
||||
if let Some(name) = names.pop() {
|
||||
fs::remove_file(logdir.join(name))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn log(
|
||||
&mut self,
|
||||
level: LogLevel,
|
||||
callsite: Callsite,
|
||||
msg: &str,
|
||||
) -> Result<(), std::io::Error> {
|
||||
if self.bytes_written > self.max_filesize {
|
||||
self.flush()?;
|
||||
let (fname, handle) = Self::open(&self.logdir)?;
|
||||
self.logfile = fname;
|
||||
self.file_handle = handle;
|
||||
Self::prune(&self.logdir, self.max_files)?;
|
||||
}
|
||||
let thread = std::thread::current();
|
||||
let msg = format!(
|
||||
"{time:8.2} {level} {thid:?}/{thname} [{callsite}]: {msg}\n",
|
||||
time = self.created.elapsed().as_secs_f64(),
|
||||
level = level,
|
||||
thid = thread.id(),
|
||||
thname = thread.name().unwrap_or("unnamed"),
|
||||
callsite = callsite,
|
||||
msg = msg,
|
||||
);
|
||||
self.file_handle.write_all(msg.as_bytes())?;
|
||||
self.bytes_written += msg.len();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> Result<(), std::io::Error> {
|
||||
self.file_handle.flush()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! callsite {
|
||||
() => {
|
||||
$crate::log::Callsite {
|
||||
file: file!(),
|
||||
line: line!(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($ctx:expr, $msg:expr) => {
|
||||
info!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||
let formatted = format!($msg, $($args),*);
|
||||
if let Ok(mut logger) = $ctx.logger.write() {
|
||||
logger.log($crate::log::LogLevel::Info, callsite!(), &formatted).ok();
|
||||
}
|
||||
emit_event!($ctx, $crate::Event::Info(formatted));
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@@ -16,10 +182,13 @@ macro_rules! warn {
|
||||
($ctx:expr, $msg:expr) => {
|
||||
warn!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||
let formatted = format!($msg, $($args),*);
|
||||
if let Ok(mut logger) = $ctx.logger.write() {
|
||||
logger.log($crate::log::LogLevel::Warning, callsite!(), &formatted).ok();
|
||||
}
|
||||
emit_event!($ctx, $crate::Event::Warning(formatted));
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@@ -27,10 +196,13 @@ macro_rules! error {
|
||||
($ctx:expr, $msg:expr) => {
|
||||
error!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||
let formatted = format!($msg, $($args),*);
|
||||
if let Ok(mut logger) = $ctx.logger.write() {
|
||||
logger.log($crate::log::LogLevel::Error, callsite!(), &formatted).ok();
|
||||
}
|
||||
emit_event!($ctx, $crate::Event::Error(formatted));
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@@ -39,3 +211,80 @@ macro_rules! emit_event {
|
||||
$ctx.call_cb($event);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_basic_logging() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = tmp.path();
|
||||
let mut logger = Logger::new(dir.to_path_buf()).unwrap();
|
||||
logger.log(LogLevel::Info, callsite!(), "foo").unwrap();
|
||||
logger.log(LogLevel::Warning, callsite!(), "bar").unwrap();
|
||||
logger.log(LogLevel::Error, callsite!(), "baz").unwrap();
|
||||
logger.flush().unwrap();
|
||||
let log = fs::read_to_string(logger.logdir.join(logger.logfile)).unwrap();
|
||||
println!("{}", log);
|
||||
let lines: Vec<&str> = log.lines().collect();
|
||||
|
||||
assert!(lines[0].contains(" I "));
|
||||
assert!(lines[0].contains(format!("{:?}", std::thread::current().id()).as_str()));
|
||||
assert!(lines[0]
|
||||
.contains(format!("{}", std::thread::current().name().unwrap_or("unnamed")).as_str()));
|
||||
assert!(lines[0].contains(&format!("src{}log.rs", std::path::MAIN_SEPARATOR)));
|
||||
assert!(lines[0].contains("foo"));
|
||||
|
||||
assert!(lines[1].contains(" W "));
|
||||
assert!(lines[1].contains(format!("{:?}", std::thread::current().id()).as_str()));
|
||||
assert!(lines[1]
|
||||
.contains(format!("{}", std::thread::current().name().unwrap_or("unnamed")).as_str()));
|
||||
assert!(lines[1].contains(&format!("src{}log.rs", std::path::MAIN_SEPARATOR)));
|
||||
assert!(lines[1].contains("bar"));
|
||||
|
||||
assert!(lines[2].contains(" E "));
|
||||
assert!(lines[2].contains(format!("{:?}", std::thread::current().id()).as_str()));
|
||||
assert!(lines[2]
|
||||
.contains(format!("{}", std::thread::current().name().unwrap_or("unnamed")).as_str()));
|
||||
assert!(lines[2].contains(&format!("src{}log.rs", std::path::MAIN_SEPARATOR)));
|
||||
assert!(lines[2].contains("baz"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reopen_logfile() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = tmp.path();
|
||||
let mut logger = Logger::new(dir.to_path_buf()).unwrap();
|
||||
logger.max_filesize = 5;
|
||||
|
||||
let fname0 = logger.logfile.clone();
|
||||
assert!(fname0.ends_with(".log"));
|
||||
logger
|
||||
.log(LogLevel::Info, callsite!(), "more than 5 bytes are written")
|
||||
.unwrap();
|
||||
logger.log(LogLevel::Info, callsite!(), "2nd msg").unwrap();
|
||||
let fname1 = logger.logfile.clone();
|
||||
assert!(fname1.ends_with("-1.log"));
|
||||
assert_ne!(fname0, fname1);
|
||||
let log0 = fs::read_to_string(logger.logdir.join(&fname0)).unwrap();
|
||||
assert!(log0.contains("more than 5 bytes are written"));
|
||||
let log1 = fs::read_to_string(logger.logdir.join(&fname1)).unwrap();
|
||||
assert!(log1.contains("2nd msg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prune() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = tmp.path();
|
||||
Logger::new(dir.to_path_buf()).unwrap();
|
||||
Logger::new(dir.to_path_buf()).unwrap();
|
||||
Logger::new(dir.to_path_buf()).unwrap();
|
||||
Logger::new(dir.to_path_buf()).unwrap();
|
||||
let dirents0: Vec<fs::DirEntry> = fs::read_dir(&dir).unwrap().map(|r| r.unwrap()).collect();
|
||||
assert_eq!(dirents0.len(), 4);
|
||||
Logger::prune(&dir, 3).unwrap();
|
||||
let dirents1: Vec<fs::DirEntry> = fs::read_dir(&dir).unwrap().map(|r| r.unwrap()).collect();
|
||||
assert_eq!(dirents1.len(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,6 @@ pub struct Message {
|
||||
pub(crate) from_id: u32,
|
||||
pub(crate) to_id: u32,
|
||||
pub(crate) chat_id: u32,
|
||||
pub(crate) move_state: MoveState,
|
||||
pub(crate) type_0: Viewtype,
|
||||
pub(crate) state: MessageState,
|
||||
pub(crate) hidden: bool,
|
||||
@@ -200,7 +199,6 @@ impl Message {
|
||||
" m.mime_in_reply_to AS mime_in_reply_to,",
|
||||
" m.server_folder AS server_folder,",
|
||||
" m.server_uid AS server_uid,",
|
||||
" m.move_state as move_state,",
|
||||
" m.chat_id AS chat_id,",
|
||||
" m.from_id AS from_id,",
|
||||
" m.to_id AS to_id,",
|
||||
@@ -228,7 +226,6 @@ impl Message {
|
||||
msg.in_reply_to = row.get::<_, Option<String>>("mime_in_reply_to")?;
|
||||
msg.server_folder = row.get::<_, Option<String>>("server_folder")?;
|
||||
msg.server_uid = row.get("server_uid")?;
|
||||
msg.move_state = row.get("move_state")?;
|
||||
msg.chat_id = row.get("chat_id")?;
|
||||
msg.from_id = row.get("from_id")?;
|
||||
msg.to_id = row.get("to_id")?;
|
||||
@@ -1074,18 +1071,6 @@ pub fn exists(context: &Context, msg_id: MsgId) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_msg_move_state(context: &Context, rfc724_mid: &str, state: MoveState) -> bool {
|
||||
// we update the move_state for all messages belonging to a given Message-ID
|
||||
// so that the state stay intact when parts are deleted
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET move_state=? WHERE rfc724_mid=?;",
|
||||
params![state as i32, rfc724_mid],
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef<str>>) {
|
||||
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
|
||||
if msg.state.can_fail() {
|
||||
|
||||
@@ -230,7 +230,7 @@ impl<'a> MimeFactory<'a> {
|
||||
|
||||
// 1=add Autocrypt-header (needed eg. for handshaking), 2=no Autocrypte-header (used for MDN)
|
||||
let mut e2ee_guaranteed = false;
|
||||
let mut min_verified: libc::c_int = 0;
|
||||
let mut min_verified = crate::peerstate::PeerstateVerifiedStatus::Unverified;
|
||||
let mut do_gossip = false;
|
||||
let mut grpimage = None;
|
||||
let force_plaintext: libc::c_int;
|
||||
@@ -246,7 +246,7 @@ impl<'a> MimeFactory<'a> {
|
||||
wrapmime::new_custom_field(imf_fields, "Chat-Verified", "1");
|
||||
force_plaintext = 0;
|
||||
e2ee_guaranteed = true;
|
||||
min_verified = 2
|
||||
min_verified = crate::peerstate::PeerstateVerifiedStatus::BidirectVerified;
|
||||
} else {
|
||||
force_plaintext = self
|
||||
.msg
|
||||
|
||||
@@ -12,6 +12,17 @@ use crate::error::*;
|
||||
use crate::key::*;
|
||||
use crate::sql::{self, Sql};
|
||||
|
||||
pub const DC_PS_GOSSIP_KEY: u32 = 0;
|
||||
pub const DC_PS_PUBLIC_KEY: u32 = 1;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum PeerstateVerifiedStatus {
|
||||
Unverified = 0,
|
||||
//Verified = 1, // not used
|
||||
BidirectVerified = 2,
|
||||
}
|
||||
|
||||
/// Peerstate represents the state of an Autocrypt peer.
|
||||
pub struct Peerstate<'a> {
|
||||
pub context: &'a Context,
|
||||
@@ -311,7 +322,7 @@ impl<'a> Peerstate<'a> {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn render_gossip_header(&self, min_verified: usize) -> Option<String> {
|
||||
pub fn render_gossip_header(&self, min_verified: PeerstateVerifiedStatus) -> Option<String> {
|
||||
if let Some(ref addr) = self.addr {
|
||||
if let Some(key) = self.peek_key(min_verified) {
|
||||
// TODO: avoid cloning
|
||||
@@ -327,12 +338,12 @@ impl<'a> Peerstate<'a> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn peek_key(&self, min_verified: usize) -> Option<&Key> {
|
||||
pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&Key> {
|
||||
if self.public_key.is_none() && self.gossip_key.is_none() && self.verified_key.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if 0 != min_verified {
|
||||
if min_verified != PeerstateVerifiedStatus::Unverified {
|
||||
return self.verified_key.as_ref();
|
||||
}
|
||||
if self.public_key.is_some() {
|
||||
@@ -342,10 +353,17 @@ impl<'a> Peerstate<'a> {
|
||||
self.gossip_key.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_verified(&mut self, which_key: usize, fingerprint: &str, verified: usize) -> bool {
|
||||
pub fn set_verified(
|
||||
&mut self,
|
||||
which_key: u32,
|
||||
fingerprint: &str,
|
||||
verified: PeerstateVerifiedStatus,
|
||||
) -> bool {
|
||||
let mut success = false;
|
||||
if !(which_key != 0 && which_key != 1 || verified != 2) {
|
||||
if which_key == 1
|
||||
if !(which_key != DC_PS_GOSSIP_KEY && which_key != DC_PS_PUBLIC_KEY
|
||||
|| verified != PeerstateVerifiedStatus::BidirectVerified)
|
||||
{
|
||||
if which_key == DC_PS_PUBLIC_KEY
|
||||
&& self.public_key_fingerprint.is_some()
|
||||
&& self.public_key_fingerprint.as_ref().unwrap() == fingerprint
|
||||
{
|
||||
@@ -354,7 +372,7 @@ impl<'a> Peerstate<'a> {
|
||||
self.verified_key_fingerprint = self.public_key_fingerprint.clone();
|
||||
success = true;
|
||||
}
|
||||
if which_key == 0
|
||||
if which_key == DC_PS_GOSSIP_KEY
|
||||
&& self.gossip_key_fingerprint.is_some()
|
||||
&& self.gossip_key_fingerprint.as_ref().unwrap() == fingerprint
|
||||
{
|
||||
|
||||
@@ -611,6 +611,14 @@ pub fn handle_securejoin_handshake(
|
||||
}
|
||||
inviter_progress!(context, contact_id, 800);
|
||||
inviter_progress!(context, contact_id, 1000);
|
||||
let field_grpid = mimeparser
|
||||
.lookup_optional_field("Secure-Join-Group")
|
||||
.unwrap_or_default();
|
||||
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
|
||||
context.call_cb(Event::SecurejoinMemberAdded {
|
||||
chat_id: group_chat_id,
|
||||
contact_id: contact_id,
|
||||
});
|
||||
} else {
|
||||
warn!(context, "vg-member-added-received invalid.",);
|
||||
return ret;
|
||||
@@ -664,7 +672,11 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
|
||||
if let Some(ref mut peerstate) =
|
||||
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref())
|
||||
{
|
||||
if peerstate.set_verified(1, fingerprint.as_ref(), 2) {
|
||||
if peerstate.set_verified(
|
||||
DC_PS_PUBLIC_KEY,
|
||||
fingerprint.as_ref(),
|
||||
PeerstateVerifiedStatus::BidirectVerified,
|
||||
) {
|
||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||
peerstate.to_save = Some(ToSave::All);
|
||||
peerstate
|
||||
|
||||
@@ -729,6 +729,7 @@ fn open(
|
||||
}
|
||||
if dbversion < 48 {
|
||||
info!(context, "[migration] v48");
|
||||
// NOTE: move_state is not used anymore
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
|
||||
params![],
|
||||
|
||||
@@ -7,7 +7,7 @@ use tempfile::{tempdir, TempDir};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::constants::KeyType;
|
||||
use crate::context::{Context, ContextCallback};
|
||||
use crate::context::{Context, ContextBuilder, ContextCallback};
|
||||
use crate::events::Event;
|
||||
use crate::key;
|
||||
|
||||
@@ -33,7 +33,9 @@ pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
|
||||
Some(cb) => cb,
|
||||
None => Box::new(|_, _| 0),
|
||||
};
|
||||
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
|
||||
let ctx = ContextBuilder::new(cb, "FakeOs".into(), dbfile)
|
||||
.create()
|
||||
.unwrap();
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ fn test_encryption_decryption() {
|
||||
public_keyring.add_ref(&public_key);
|
||||
|
||||
let mut public_keyring2 = Keyring::default();
|
||||
public_keyring2.add_owned(public_key2.clone());
|
||||
public_keyring2.add_owned(public_key2);
|
||||
|
||||
let mut valid_signatures: HashSet<String> = Default::default();
|
||||
|
||||
@@ -219,8 +219,10 @@ struct TestContext {
|
||||
fn create_test_context() -> TestContext {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap();
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
let ctx = ContextBuilder::new(Box::new(cb), "FakeOs".into(), dbfile)
|
||||
.create()
|
||||
.unwrap();
|
||||
TestContext { ctx, dir }
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user