mirror of
https://github.com/chatmail/core.git
synced 2026-07-14 15:03:09 +03:00
Compare commits
1 Commits
expand-tab
...
message_tr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3927fa399e |
15
.github/workflows/rust.yml
vendored
15
.github/workflows/rust.yml
vendored
@@ -1,15 +0,0 @@
|
||||
name: Rust
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
@@ -1,6 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0-beta.7
|
||||
## untagged 1.0.0-beta.7
|
||||
|
||||
- fix location-streaming #782
|
||||
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -480,7 +480,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.7"
|
||||
version = "1.0.0-beta.6"
|
||||
dependencies = [
|
||||
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -550,9 +550,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.7"
|
||||
version = "1.0.0-beta.6"
|
||||
dependencies = [
|
||||
"deltachat 1.0.0-beta.7",
|
||||
"deltachat 1.0.0-beta.6",
|
||||
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.7"
|
||||
version = "1.0.0-beta.6"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.7"
|
||||
version = "1.0.0-beta.6"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -501,9 +501,15 @@ char* dc_get_oauth2_url (dc_context_t* context, const char*
|
||||
* To interrupt a configuration prematurely, use dc_stop_ongoing_process();
|
||||
* this is not needed if #DC_EVENT_CONFIGURE_PROGRESS reports success.
|
||||
*
|
||||
* If #DC_EVENT_CONFIGURE_PROGRESS reports failure,
|
||||
* the core continues to use the last working configuration
|
||||
* and parameters as `addr`, `mail_pw` etc. are set to that.
|
||||
* On a successfull configuration,
|
||||
* the core makes a copy of the parameters mentioned above:
|
||||
* the original parameters as are never modified by the core.
|
||||
*
|
||||
* UI-implementors should keep this in mind -
|
||||
* eg. if the UI wants to prefill a configure-edit-dialog with these parameters,
|
||||
* the UI should reset them if the user cancels the dialog
|
||||
* after a configure-attempts has failed.
|
||||
* Otherwise the parameters may not reflect the current configuation.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as created by dc_context_new().
|
||||
@@ -1092,49 +1098,6 @@ uint32_t dc_send_text_msg (dc_context_t* context, uint32_t ch
|
||||
void dc_set_draft (dc_context_t* context, uint32_t chat_id, dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Add a message to the device-chat.
|
||||
* Device-messages usually contain update information
|
||||
* and some hints that are added during the program runs, multi-device etc.
|
||||
*
|
||||
* Device-messages may be added from the core,
|
||||
* however, with this function, this can be done from the ui as well.
|
||||
* If needed, the device-chat is created before.
|
||||
*
|
||||
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
|
||||
* To check, if a given chat is a device-chat, see dc_chat_is_device_talk()
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @param msg Message to be added to the device-chat.
|
||||
* The message appears to the user as an incoming message.
|
||||
* @return The ID of the added message.
|
||||
*/
|
||||
uint32_t dc_add_device_msg (dc_context_t* context, dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Add a message only one time to the device-chat.
|
||||
* The device-message is defined by a name.
|
||||
* If a message with the same name was added before,
|
||||
* the message is not added again.
|
||||
* Use dc_add_device_msg() to add device-messages unconditionally.
|
||||
*
|
||||
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @param label A unique name for the message to add.
|
||||
* The label is typically not displayed to the user and
|
||||
* must be created from the characters `A-Z`, `a-z`, `0-9`, `_` or `-`.
|
||||
* @param msg Message to be added to the device-chat.
|
||||
* The message appears to the user as an incoming message.
|
||||
* @return The ID of the added message,
|
||||
* this might be the id of an older message with the same name.
|
||||
*/
|
||||
uint32_t dc_add_device_msg_once (dc_context_t* context, const char* label, dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Get draft for a chat, if any.
|
||||
* See dc_set_draft() for more details about drafts.
|
||||
@@ -2752,39 +2715,6 @@ int dc_chat_is_unpromoted (const dc_chat_t* chat);
|
||||
int dc_chat_is_self_talk (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a chat is a device-talk.
|
||||
* Device-talks contain update information
|
||||
* and some hints that are added during the program runs, multi-device etc.
|
||||
*
|
||||
* From the ui view, device-talks are not very special,
|
||||
* the user can delete and forward messages, archive the chat, set notifications etc.
|
||||
*
|
||||
* Messages may be added from the core to the device chat,
|
||||
* so the chat just pops up as usual.
|
||||
* However, if needed the ui can also add messages using dc_add_device_msg()
|
||||
*
|
||||
* @memberof dc_chat_t
|
||||
* @param chat The chat object.
|
||||
* @return 1=chat is device-talk, 0=chat is no device-talk
|
||||
*/
|
||||
int dc_chat_is_device_talk (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Check if messages can be sent to a give chat.
|
||||
* This is not true eg. for the deaddrop or for the device-talk, cmp. dc_chat_is_device_talk().
|
||||
*
|
||||
* Calling dc_send_msg() for these chats will fail
|
||||
* and the ui may decide to hide input controls therefore.
|
||||
*
|
||||
* @memberof dc_chat_t
|
||||
* @param chat The chat object.
|
||||
* @return 1=chat is writable, 0=chat is not writable
|
||||
*/
|
||||
int dc_chat_can_send (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a chat is verified. Verified chats contain only verified members
|
||||
* and encryption is alwasy enabled. Verified chats are created using
|
||||
@@ -3021,6 +2951,32 @@ int64_t dc_msg_get_sort_timestamp (const dc_msg_t* msg);
|
||||
*/
|
||||
char* dc_msg_get_text (const dc_msg_t* msg);
|
||||
|
||||
/**
|
||||
* Get the text of the message.
|
||||
* If there is no text associated with the message, an empty string is returned.
|
||||
* NULL is never returned.
|
||||
*
|
||||
* Same as dc_msg_get_text, but isn't truncated
|
||||
*
|
||||
* To get information about the message and more/raw text, use dc_get_msg_info().
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @return Message text. The result must be released using dc_str_unref(). Never returns NULL.
|
||||
*/
|
||||
char* dc_msg_get_full_text (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a message is truncated.
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @return 1=message is truncated, 0=message not truncated.
|
||||
*/
|
||||
int dc_msg_is_truncated (const dc_msg_t* msg);
|
||||
|
||||
// define DC_MSG_TRUNCATE_THRESHOLD
|
||||
|
||||
/**
|
||||
* Find out full path, file name and extension of the file associated with a
|
||||
@@ -3440,8 +3396,7 @@ void dc_msg_latefiling_mediasize (dc_msg_t* msg, int width, int hei
|
||||
|
||||
|
||||
#define DC_CONTACT_ID_SELF 1
|
||||
#define DC_CONTACT_ID_INFO 2 // centered messages as "member added", used in all chats
|
||||
#define DC_CONTACT_ID_DEVICE 5 // messages "update info" in the device-chat
|
||||
#define DC_CONTACT_ID_DEVICE 2
|
||||
#define DC_CONTACT_ID_LAST_SPECIAL 9
|
||||
|
||||
|
||||
@@ -4489,8 +4444,7 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
|
||||
#define DC_STR_MSGLOCATIONDISABLED 65
|
||||
#define DC_STR_LOCATION 66
|
||||
#define DC_STR_STICKER 67
|
||||
#define DC_STR_DEVICE_MESSAGES 68
|
||||
#define DC_STR_COUNT 68
|
||||
#define DC_STR_COUNT 67
|
||||
|
||||
/*
|
||||
* @}
|
||||
|
||||
@@ -811,44 +811,6 @@ pub unsafe extern "C" fn dc_set_draft(
|
||||
.unwrap_or(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_add_device_msg(context: *mut dc_context_t, msg: *mut dc_msg_t) -> u32 {
|
||||
if context.is_null() || msg.is_null() {
|
||||
eprintln!("ignoring careless call to dc_add_device_msg()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_context = &mut *context;
|
||||
let ffi_msg = &mut *msg;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
chat::add_device_msg(ctx, &mut ffi_msg.message)
|
||||
.unwrap_or_log_default(ctx, "Failed to add device message")
|
||||
})
|
||||
.map(|msg_id| msg_id.to_u32())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_add_device_msg_once(
|
||||
context: *mut dc_context_t,
|
||||
label: *const libc::c_char,
|
||||
msg: *mut dc_msg_t,
|
||||
) -> u32 {
|
||||
if context.is_null() || label.is_null() || msg.is_null() {
|
||||
eprintln!("ignoring careless call to dc_add_device_msg_once()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_context = &mut *context;
|
||||
let ffi_msg = &mut *msg;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
chat::add_device_msg_once(ctx, &to_string_lossy(label), &mut ffi_msg.message)
|
||||
.unwrap_or_log_default(ctx, "Failed to add device message once")
|
||||
})
|
||||
.map(|msg_id| msg_id.to_u32())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) -> *mut dc_msg_t {
|
||||
if context.is_null() {
|
||||
@@ -2302,26 +2264,6 @@ pub unsafe extern "C" fn dc_chat_is_self_talk(chat: *mut dc_chat_t) -> libc::c_i
|
||||
ffi_chat.chat.is_self_talk() as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_chat_is_device_talk(chat: *mut dc_chat_t) -> libc::c_int {
|
||||
if chat.is_null() {
|
||||
eprintln!("ignoring careless call to dc_chat_is_device_talk()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_chat = &*chat;
|
||||
ffi_chat.chat.is_device_talk() as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int {
|
||||
if chat.is_null() {
|
||||
eprintln!("ignoring careless call to dc_chat_can_send()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_chat = &*chat;
|
||||
ffi_chat.chat.can_send() as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_chat_is_verified(chat: *mut dc_chat_t) -> libc::c_int {
|
||||
if chat.is_null() {
|
||||
@@ -2478,7 +2420,26 @@ pub unsafe extern "C" fn dc_msg_get_text(msg: *mut dc_msg_t) -> *mut libc::c_cha
|
||||
return dc_strdup(ptr::null());
|
||||
}
|
||||
let ffi_msg = &*msg;
|
||||
ffi_msg.message.get_text().unwrap_or_default().strdup()
|
||||
ffi_msg.message.get_text(false).unwrap_or_default().strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_msg_get_full_text(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||
if msg.is_null() {
|
||||
eprintln!("ignoring careless call to dc_msg_get_full_text()");
|
||||
return dc_strdup(ptr::null());
|
||||
}
|
||||
let ffi_msg = &*msg;
|
||||
ffi_msg.message.get_text(true).unwrap_or_default().strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn is_truncated(msg: *mut dc_msg_t) -> libc::c_int {
|
||||
if msg.is_null() {
|
||||
return 0
|
||||
}
|
||||
let ffi_msg = &*msg;
|
||||
ffi_msg.message.is_truncated().into()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
@@ -189,10 +189,10 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
_ => "",
|
||||
};
|
||||
let temp2 = dc_timestamp_to_str(msg.get_timestamp());
|
||||
let msgtext = msg.get_text();
|
||||
let msgtext = msg.get_text(false);
|
||||
info!(
|
||||
context,
|
||||
"{}{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
|
||||
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
|
||||
prefix.as_ref(),
|
||||
msg.get_id(),
|
||||
if msg.get_showpadlock() { "🔒" } else { "" },
|
||||
@@ -240,7 +240,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Err
|
||||
lines_out += 1
|
||||
}
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
log_msg(context, "", &msg);
|
||||
log_msg(context, "Msg", &msg);
|
||||
}
|
||||
}
|
||||
if lines_out > 0 {
|
||||
@@ -379,7 +379,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
sendimage <file> [<text>]\n\
|
||||
sendfile <file> [<text>]\n\
|
||||
draft [<text>]\n\
|
||||
devicemsg <text>\n\
|
||||
listmedia\n\
|
||||
archive <chat-id>\n\
|
||||
unarchive <chat-id>\n\
|
||||
@@ -426,12 +425,12 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
if msg.is_setupmessage() {
|
||||
let setupcodebegin = msg.get_setupcodebegin(context);
|
||||
println!(
|
||||
"The setup code for setup message {} starts with: {}",
|
||||
"The setup code for setup message Msg#{} starts with: {}",
|
||||
msg_id,
|
||||
setupcodebegin.unwrap_or_default(),
|
||||
);
|
||||
} else {
|
||||
bail!("{} is no setup message.", msg_id,);
|
||||
bail!("Msg#{} is no setup message.", msg_id,);
|
||||
}
|
||||
}
|
||||
"continue-key-transfer" => {
|
||||
@@ -522,12 +521,13 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
|
||||
for i in (0..cnt).rev() {
|
||||
let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?;
|
||||
let temp_name = chat.get_name();
|
||||
info!(
|
||||
context,
|
||||
"{}#{}: {} [{} fresh]",
|
||||
chat_prefix(&chat),
|
||||
chat.get_id(),
|
||||
chat.get_name(),
|
||||
temp_name,
|
||||
chat::get_fresh_msg_cnt(context, chat.get_id()),
|
||||
);
|
||||
let lot = chatlist.get_summary(context, i, Some(&chat));
|
||||
@@ -586,33 +586,25 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
|
||||
let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, None);
|
||||
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 {
|
||||
let temp2 = if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
|
||||
let contact = Contact::get_by_id(context, members[0])?;
|
||||
contact.get_addr().to_string()
|
||||
} else {
|
||||
format!("{} member(s)", members.len())
|
||||
};
|
||||
let temp_name = sel_chat.get_name();
|
||||
info!(
|
||||
context,
|
||||
"{}#{}: {} [{}]{}{}",
|
||||
"{}#{}: {} [{}]{}",
|
||||
chat_prefix(sel_chat),
|
||||
sel_chat.get_id(),
|
||||
sel_chat.get_name(),
|
||||
subtitle,
|
||||
temp_name,
|
||||
temp2,
|
||||
if sel_chat.is_sending_locations() {
|
||||
"📍"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
match sel_chat.get_profile_image(context) {
|
||||
Some(icon) => match icon.to_str() {
|
||||
Some(icon) => format!(" Icon: {}", icon),
|
||||
_ => " Icon: Err".to_string(),
|
||||
},
|
||||
_ => "".to_string(),
|
||||
},
|
||||
);
|
||||
log_msglist(context, &msglist)?;
|
||||
if let Some(draft) = chat::get_draft(context, sel_chat.get_id())? {
|
||||
@@ -726,7 +718,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
let marker = location.marker.as_ref().unwrap_or(&default_marker);
|
||||
info!(
|
||||
context,
|
||||
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} {} {}",
|
||||
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} Msg#{} {}",
|
||||
location.location_id,
|
||||
dc_timestamp_to_str(location.timestamp),
|
||||
location.latitude,
|
||||
@@ -830,15 +822,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
println!("Draft deleted.");
|
||||
}
|
||||
}
|
||||
"devicemsg" => {
|
||||
ensure!(
|
||||
!arg1.is_empty(),
|
||||
"Please specify text to add as device message."
|
||||
);
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some(arg1.to_string()));
|
||||
chat::add_device_msg(context, &mut msg)?;
|
||||
}
|
||||
"listmedia" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
|
||||
@@ -852,9 +835,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
println!("{} images or videos: ", images.len());
|
||||
for (i, data) in images.iter().enumerate() {
|
||||
if 0 == i {
|
||||
print!("{}", data);
|
||||
print!("Msg#{}", data);
|
||||
} else {
|
||||
print!(", {}", data);
|
||||
print!(", Msg#{}", data);
|
||||
}
|
||||
}
|
||||
print!("\n");
|
||||
|
||||
@@ -153,14 +153,6 @@ class Account(object):
|
||||
self.check_is_configured()
|
||||
return from_dc_charpointer(lib.dc_get_info(self._dc_context))
|
||||
|
||||
def get_latest_backupfile(self, backupdir):
|
||||
""" return the latest backup file in a given directory.
|
||||
"""
|
||||
res = lib.dc_imex_has_backup(self._dc_context, as_dc_charpointer(backupdir))
|
||||
if res == ffi.NULL:
|
||||
return None
|
||||
return from_dc_charpointer(res)
|
||||
|
||||
def get_blobdir(self):
|
||||
""" return the directory for files.
|
||||
|
||||
@@ -485,9 +477,8 @@ class Account(object):
|
||||
|
||||
def stop_threads(self, wait=True):
|
||||
""" stop IMAP/SMTP threads. """
|
||||
if self._threads.is_started():
|
||||
self.stop_ongoing()
|
||||
self._threads.stop(wait=wait)
|
||||
self.stop_ongoing()
|
||||
self._threads.stop(wait=wait)
|
||||
|
||||
def shutdown(self, wait=True):
|
||||
""" stop threads and close and remove underlying dc_context and callbacks. """
|
||||
|
||||
@@ -47,8 +47,7 @@ DC_STATE_OUT_FAILED = 24
|
||||
DC_STATE_OUT_DELIVERED = 26
|
||||
DC_STATE_OUT_MDN_RCVD = 28
|
||||
DC_CONTACT_ID_SELF = 1
|
||||
DC_CONTACT_ID_INFO = 2
|
||||
DC_CONTACT_ID_DEVICE = 5
|
||||
DC_CONTACT_ID_DEVICE = 2
|
||||
DC_CONTACT_ID_LAST_SPECIAL = 9
|
||||
DC_MSG_TEXT = 10
|
||||
DC_MSG_IMAGE = 20
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import print_function
|
||||
import pytest
|
||||
import os
|
||||
import queue
|
||||
import time
|
||||
from deltachat import const, Account
|
||||
from deltachat.message import Message
|
||||
from datetime import datetime, timedelta
|
||||
@@ -21,7 +20,7 @@ class TestOfflineAccountBasic:
|
||||
d = ac1.get_info()
|
||||
assert d["arch"]
|
||||
assert d["number_of_chats"] == "0"
|
||||
assert d["bcc_self"] == "0"
|
||||
assert d["bcc_self"] == "1"
|
||||
|
||||
def test_is_not_configured(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
@@ -43,7 +42,7 @@ class TestOfflineAccountBasic:
|
||||
def test_has_bccself(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
assert "bcc_self" in ac1.get_config("sys.config_keys").split()
|
||||
assert ac1.get_config("bcc_self") == "0"
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
|
||||
def test_selfcontact_if_unconfigured(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
@@ -405,9 +404,6 @@ class TestOnlineAccount:
|
||||
wait_successful_IMAP_SMTP_connection(ac1)
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
|
||||
lp.sec("ac1: setting bcc_self=1")
|
||||
ac1.set_config("bcc_self", "1")
|
||||
|
||||
lp.sec("send out message with bcc to ourselves")
|
||||
msg_out = chat.send_text("message2")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
@@ -645,29 +641,18 @@ class TestOnlineAccount:
|
||||
assert os.path.exists(msg_in.filename)
|
||||
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
|
||||
|
||||
def test_import_export_online_all(self, acfactory, tmpdir, lp):
|
||||
def test_import_export_online_all(self, acfactory, tmpdir):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
|
||||
lp.sec("create some chat content")
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
chat.send_text("msg1")
|
||||
backupdir = tmpdir.mkdir("backup")
|
||||
|
||||
lp.sec("export all to {}".format(backupdir))
|
||||
path = ac1.export_all(backupdir.strpath)
|
||||
assert os.path.exists(path)
|
||||
t = time.time()
|
||||
|
||||
lp.sec("get fresh empty account")
|
||||
ac2 = acfactory.get_unconfigured_account()
|
||||
|
||||
lp.sec("get latest backup file")
|
||||
path2 = ac2.get_latest_backupfile(backupdir.strpath)
|
||||
assert path2 == path
|
||||
|
||||
lp.sec("import backup and check it's proper")
|
||||
ac2.import_all(path)
|
||||
contacts = ac2.get_contacts(query="some1")
|
||||
assert len(contacts) == 1
|
||||
@@ -678,18 +663,6 @@ class TestOnlineAccount:
|
||||
assert len(messages) == 1
|
||||
assert messages[0].text == "msg1"
|
||||
|
||||
pytest.xfail("cannot export twice yet, probably due to interrupt_idle failing")
|
||||
# wait until a second passed since last backup
|
||||
# because get_latest_backupfile() shall return the latest backup
|
||||
# from a UI it's unlikely anyone manages to export two
|
||||
# backups in one second.
|
||||
time.sleep(max(0, 1 - (time.time() - t)))
|
||||
lp.sec("Second-time export all to {}".format(backupdir))
|
||||
path2 = ac1.export_all(backupdir.strpath)
|
||||
assert os.path.exists(path2)
|
||||
assert path2 != path
|
||||
assert ac2.get_latest_backupfile(backupdir.strpath) == path2
|
||||
|
||||
def test_ac_setup_message(self, acfactory, lp):
|
||||
# note that the receiving account needs to be configured and running
|
||||
# before ther setup message is send. DC does not read old messages
|
||||
|
||||
283
src/chat.rs
283
src/chat.rs
@@ -1,7 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use itertools::Itertools;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::blob::{BlobErrorKind, BlobObject};
|
||||
use crate::chatlist::*;
|
||||
@@ -98,8 +97,6 @@ impl Chat {
|
||||
|
||||
if chat.param.exists(Param::Selftalk) {
|
||||
chat.name = context.stock_str(StockMessage::SelfMsg).into();
|
||||
} else if chat.param.exists(Param::Devicetalk) {
|
||||
chat.name = context.stock_str(StockMessage::DeviceMessages).into();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,14 +109,6 @@ impl Chat {
|
||||
self.param.exists(Param::Selftalk)
|
||||
}
|
||||
|
||||
pub fn is_device_talk(&self) -> bool {
|
||||
self.param.exists(Param::Devicetalk)
|
||||
}
|
||||
|
||||
pub fn can_send(&self) -> bool {
|
||||
self.id > DC_CHAT_ID_LAST_SPECIAL && !self.is_device_talk()
|
||||
}
|
||||
|
||||
pub fn update_param(&mut self, context: &Context) -> Result<(), Error> {
|
||||
sql::execute(
|
||||
context,
|
||||
@@ -593,12 +582,6 @@ pub fn set_blocking(context: &Context, chat_id: u32, new_blocking: Blocked) -> b
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn copy_device_icon_to_blobs(context: &Context) -> Result<String, Error> {
|
||||
let icon = include_bytes!("../assets/icon-device.png");
|
||||
let blob = BlobObject::create(context, "icon-device.png".to_string(), icon)?;
|
||||
Ok(blob.as_name().to_string())
|
||||
}
|
||||
|
||||
pub fn create_or_lookup_by_contact_id(
|
||||
context: &Context,
|
||||
contact_id: u32,
|
||||
@@ -622,14 +605,7 @@ pub fn create_or_lookup_by_contact_id(
|
||||
"INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')",
|
||||
100,
|
||||
chat_name,
|
||||
match contact_id {
|
||||
DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk
|
||||
DC_CONTACT_ID_DEVICE => {
|
||||
let icon = copy_device_icon_to_blobs(context)?;
|
||||
format!("D=1\ni={}", icon) // D = Param::Devicetalk, i = Param::ProfileImage
|
||||
},
|
||||
_ => "".to_string()
|
||||
},
|
||||
if contact_id == DC_CONTACT_ID_SELF as u32 { "K=1" } else { "" },
|
||||
create_blocked as u8,
|
||||
contact.get_addr(),
|
||||
),
|
||||
@@ -701,7 +677,8 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
|
||||
fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Result<MsgId, Error> {
|
||||
msg.id = MsgId::new_unset();
|
||||
if msg.type_0 == Viewtype::Text {
|
||||
// the caller should check if the message text is empty
|
||||
} else if msgtype_has_file(msg.type_0) {
|
||||
@@ -737,16 +714,10 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
|
||||
} else {
|
||||
bail!("Cannot send messages of type #{}.", msg.type_0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Result<MsgId, Error> {
|
||||
msg.id = MsgId::new_unset();
|
||||
prepare_msg_blob(context, msg)?;
|
||||
unarchive(context, chat_id)?;
|
||||
|
||||
let mut chat = Chat::load_from_db(context, chat_id)?;
|
||||
ensure!(chat.can_send(), "cannot send to chat #{}", chat_id);
|
||||
|
||||
// The OutPreparing state is set by dc_prepare_msg() before it
|
||||
// calls this function and the message is left in the OutPreparing
|
||||
@@ -797,8 +768,7 @@ pub fn is_contact_in_chat(context: &Context, chat_id: u32, contact_id: u32) -> b
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
// note that unarchive() is not the same as archive(false) -
|
||||
// eg. unarchive() does not send events as done for archive(false).
|
||||
// Should return Result
|
||||
pub fn unarchive(context: &Context, chat_id: u32) -> Result<(), Error> {
|
||||
sql::execute(
|
||||
context,
|
||||
@@ -1014,8 +984,7 @@ pub fn get_chat_msgs(
|
||||
Ok(ret)
|
||||
};
|
||||
let success = if chat_id == DC_CHAT_ID_DEADDROP {
|
||||
let show_emails =
|
||||
ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default();
|
||||
let show_emails = context.get_config_int(Config::ShowEmails);
|
||||
context.sql.query_map(
|
||||
concat!(
|
||||
"SELECT m.id AS id, m.timestamp AS timestamp",
|
||||
@@ -1025,14 +994,14 @@ pub fn get_chat_msgs(
|
||||
" LEFT JOIN contacts",
|
||||
" ON m.from_id=contacts.id",
|
||||
" WHERE m.from_id!=1", // 1=DC_CONTACT_ID_SELF
|
||||
" AND m.from_id!=2", // 2=DC_CONTACT_ID_INFO
|
||||
" AND m.from_id!=2", // 2=DC_CONTACT_ID_DEVICE
|
||||
" AND m.hidden=0",
|
||||
" AND chats.blocked=2",
|
||||
" AND contacts.blocked=0",
|
||||
" AND m.msgrmsg>=?",
|
||||
" ORDER BY m.timestamp,m.id;"
|
||||
),
|
||||
params![if show_emails == ShowEmails::All { 0 } else { 1 }],
|
||||
params![if show_emails == 2 { 0 } else { 1 }],
|
||||
process_row,
|
||||
process_rows,
|
||||
)
|
||||
@@ -1929,87 +1898,15 @@ pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef<str>) -> (u32,
|
||||
.unwrap_or((0, false, Blocked::Not))
|
||||
}
|
||||
|
||||
pub fn add_device_msg(context: &Context, msg: &mut Message) -> Result<MsgId, Error> {
|
||||
add_device_msg_maybe_labelled(context, None, msg)
|
||||
}
|
||||
|
||||
pub fn add_device_msg_once(
|
||||
context: &Context,
|
||||
label: &str,
|
||||
msg: &mut Message,
|
||||
) -> Result<MsgId, Error> {
|
||||
add_device_msg_maybe_labelled(context, Some(label), msg)
|
||||
}
|
||||
|
||||
fn add_device_msg_maybe_labelled(
|
||||
context: &Context,
|
||||
label: Option<&str>,
|
||||
msg: &mut Message,
|
||||
) -> Result<MsgId, Error> {
|
||||
let (chat_id, _blocked) =
|
||||
create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?;
|
||||
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
|
||||
|
||||
// chat_id has an sql-index so it makes sense to add this although redundant
|
||||
if let Some(label) = label {
|
||||
if let Ok(msg_id) = context.sql.query_row(
|
||||
"SELECT id FROM msgs WHERE chat_id=? AND label=?",
|
||||
params![chat_id, label],
|
||||
|row| {
|
||||
let msg_id: MsgId = row.get(0)?;
|
||||
Ok(msg_id)
|
||||
},
|
||||
) {
|
||||
info!(
|
||||
context,
|
||||
"device-message {} already exist as {}", label, msg_id
|
||||
);
|
||||
return Ok(msg_id);
|
||||
}
|
||||
}
|
||||
|
||||
prepare_msg_blob(context, msg)?;
|
||||
unarchive(context, chat_id)?;
|
||||
|
||||
context.sql.execute(
|
||||
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid,label) \
|
||||
VALUES (?,?,?, ?,?,?, ?,?,?,?);",
|
||||
params![
|
||||
chat_id,
|
||||
DC_CONTACT_ID_DEVICE,
|
||||
DC_CONTACT_ID_SELF,
|
||||
dc_create_smeared_timestamp(context),
|
||||
msg.type_0,
|
||||
MessageState::InFresh,
|
||||
msg.text.as_ref().map_or("", String::as_str),
|
||||
msg.param.to_string(),
|
||||
rfc724_mid,
|
||||
label.unwrap_or_default(),
|
||||
],
|
||||
)?;
|
||||
|
||||
let row_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid);
|
||||
let msg_id = MsgId::new(row_id);
|
||||
context.call_cb(Event::IncomingMsg { chat_id, msg_id });
|
||||
info!(
|
||||
context,
|
||||
"device-message {} added as {}",
|
||||
label.unwrap_or("without label"),
|
||||
msg_id
|
||||
);
|
||||
|
||||
Ok(msg_id)
|
||||
}
|
||||
|
||||
pub fn add_info_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
|
||||
pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
|
||||
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
|
||||
|
||||
if context.sql.execute(
|
||||
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,rfc724_mid) VALUES (?,?,?, ?,?,?, ?,?);",
|
||||
params![
|
||||
chat_id as i32,
|
||||
DC_CONTACT_ID_INFO,
|
||||
DC_CONTACT_ID_INFO,
|
||||
DC_CONTACT_ID_DEVICE,
|
||||
DC_CONTACT_ID_DEVICE,
|
||||
dc_create_smeared_timestamp(context),
|
||||
Viewtype::Text,
|
||||
MessageState::InNoticed,
|
||||
@@ -2078,164 +1975,4 @@ mod tests {
|
||||
let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false).unwrap();
|
||||
assert_eq!(added, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_self_talk() {
|
||||
let t = dummy_context();
|
||||
let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
|
||||
assert_eq!(DC_CONTACT_ID_SELF, 1);
|
||||
assert!(chat_id > DC_CHAT_ID_LAST_SPECIAL);
|
||||
let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap();
|
||||
assert_eq!(chat.id, chat_id);
|
||||
assert!(chat.is_self_talk());
|
||||
assert!(!chat.archived);
|
||||
assert!(!chat.is_device_talk());
|
||||
assert!(chat.can_send());
|
||||
assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SelfMsg));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deaddrop_chat() {
|
||||
let t = dummy_context();
|
||||
let chat = Chat::load_from_db(&t.ctx, DC_CHAT_ID_DEADDROP).unwrap();
|
||||
assert_eq!(DC_CHAT_ID_DEADDROP, 1);
|
||||
assert_eq!(chat.id, DC_CHAT_ID_DEADDROP);
|
||||
assert!(!chat.is_self_talk());
|
||||
assert!(!chat.archived);
|
||||
assert!(!chat.is_device_talk());
|
||||
assert!(!chat.can_send());
|
||||
assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_device_msg() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
|
||||
// add two device-messages
|
||||
let mut msg1 = Message::new(Viewtype::Text);
|
||||
msg1.text = Some("first message".to_string());
|
||||
let msg1_id = add_device_msg(&t.ctx, &mut msg1);
|
||||
assert!(msg1_id.is_ok());
|
||||
|
||||
let mut msg2 = Message::new(Viewtype::Text);
|
||||
msg2.text = Some("second message".to_string());
|
||||
let msg2_id = add_device_msg(&t.ctx, &mut msg2);
|
||||
assert!(msg2_id.is_ok());
|
||||
assert_ne!(msg1_id.as_ref().unwrap(), msg2_id.as_ref().unwrap());
|
||||
|
||||
// check added messages
|
||||
let msg1 = message::Message::load_from_db(&t.ctx, msg1_id.unwrap());
|
||||
assert!(msg1.is_ok());
|
||||
let msg1 = msg1.unwrap();
|
||||
assert_eq!(msg1.text.as_ref().unwrap(), "first message");
|
||||
assert_eq!(msg1.from_id, DC_CONTACT_ID_DEVICE);
|
||||
assert_eq!(msg1.to_id, DC_CONTACT_ID_SELF);
|
||||
assert!(!msg1.is_info());
|
||||
assert!(!msg1.is_setupmessage());
|
||||
|
||||
let msg2 = message::Message::load_from_db(&t.ctx, msg2_id.unwrap());
|
||||
assert!(msg2.is_ok());
|
||||
let msg2 = msg2.unwrap();
|
||||
assert_eq!(msg2.text.as_ref().unwrap(), "second message");
|
||||
|
||||
// check device chat
|
||||
assert_eq!(get_msg_cnt(&t.ctx, msg2.chat_id), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_device_msg_once() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
|
||||
// add two device-messages with the same label (second attempt is not added)
|
||||
let mut msg1 = Message::new(Viewtype::Text);
|
||||
msg1.text = Some("first message".to_string());
|
||||
let msg1_id = add_device_msg_once(&t.ctx, "any-label", &mut msg1);
|
||||
assert!(msg1_id.is_ok());
|
||||
|
||||
let mut msg2 = Message::new(Viewtype::Text);
|
||||
msg2.text = Some("second message".to_string());
|
||||
let msg2_id = add_device_msg_once(&t.ctx, "any-label", &mut msg2);
|
||||
assert!(msg2_id.is_ok());
|
||||
assert_eq!(msg1_id.as_ref().unwrap(), msg2_id.as_ref().unwrap());
|
||||
|
||||
// check added message
|
||||
let msg2 = message::Message::load_from_db(&t.ctx, msg2_id.unwrap());
|
||||
assert!(msg2.is_ok());
|
||||
let msg2 = msg2.unwrap();
|
||||
assert_eq!(msg1_id.unwrap(), msg2.id);
|
||||
assert_eq!(msg2.text.as_ref().unwrap(), "first message");
|
||||
assert_eq!(msg2.from_id, DC_CONTACT_ID_DEVICE);
|
||||
assert_eq!(msg2.to_id, DC_CONTACT_ID_SELF);
|
||||
assert!(!msg2.is_info());
|
||||
assert!(!msg2.is_setupmessage());
|
||||
|
||||
// check device chat
|
||||
let chat_id = msg2.chat_id;
|
||||
assert_eq!(get_msg_cnt(&t.ctx, chat_id), 1);
|
||||
assert!(chat_id > DC_CHAT_ID_LAST_SPECIAL);
|
||||
let chat = Chat::load_from_db(&t.ctx, chat_id);
|
||||
assert!(chat.is_ok());
|
||||
let chat = chat.unwrap();
|
||||
assert!(chat.is_device_talk());
|
||||
assert!(!chat.is_self_talk());
|
||||
assert!(!chat.can_send());
|
||||
assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeviceMessages));
|
||||
assert!(chat.get_profile_image(&t.ctx).is_some());
|
||||
}
|
||||
|
||||
fn chatlist_len(ctx: &Context, listflags: usize) -> usize {
|
||||
Chatlist::try_load(ctx, listflags, None, None)
|
||||
.unwrap()
|
||||
.len()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_archive() {
|
||||
// create two chats
|
||||
let t = dummy_context();
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some("foo".to_string());
|
||||
let msg_id = add_device_msg(&t.ctx, &mut msg).unwrap();
|
||||
let chat_id1 = message::Message::load_from_db(&t.ctx, msg_id)
|
||||
.unwrap()
|
||||
.chat_id;
|
||||
let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
|
||||
assert!(chat_id1 > DC_CHAT_ID_LAST_SPECIAL);
|
||||
assert!(chat_id2 > DC_CHAT_ID_LAST_SPECIAL);
|
||||
assert_eq!(get_chat_cnt(&t.ctx), 2);
|
||||
assert_eq!(chatlist_len(&t.ctx, 0), 2);
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 2);
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 0);
|
||||
assert_eq!(DC_GCL_ARCHIVED_ONLY, 0x01);
|
||||
assert_eq!(DC_GCL_NO_SPECIALS, 0x02);
|
||||
|
||||
// archive first chat
|
||||
assert!(archive(&t.ctx, chat_id1, true).is_ok());
|
||||
assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived());
|
||||
assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived());
|
||||
assert_eq!(get_chat_cnt(&t.ctx), 2);
|
||||
assert_eq!(chatlist_len(&t.ctx, 0), 2); // including DC_CHAT_ID_ARCHIVED_LINK now
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1);
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1);
|
||||
|
||||
// archive second chat
|
||||
assert!(archive(&t.ctx, chat_id2, true).is_ok());
|
||||
assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived());
|
||||
assert!(Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived());
|
||||
assert_eq!(get_chat_cnt(&t.ctx), 2);
|
||||
assert_eq!(chatlist_len(&t.ctx, 0), 1); // only DC_CHAT_ID_ARCHIVED_LINK now
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 0);
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 2);
|
||||
|
||||
// archive already archived first chat, unarchive second chat two times
|
||||
assert!(archive(&t.ctx, chat_id1, true).is_ok());
|
||||
assert!(archive(&t.ctx, chat_id2, false).is_ok());
|
||||
assert!(archive(&t.ctx, chat_id2, false).is_ok());
|
||||
assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived());
|
||||
assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived());
|
||||
assert_eq!(get_chat_cnt(&t.ctx), 2);
|
||||
assert_eq!(chatlist_len(&t.ctx, 0), 2);
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1);
|
||||
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ impl Chatlist {
|
||||
" SELECT MAX(timestamp)",
|
||||
" FROM msgs",
|
||||
" WHERE chat_id=c.id",
|
||||
" AND hidden=0)",
|
||||
" AND (hidden=0 OR (hidden=1 AND state=19)))",
|
||||
" WHERE c.id>9",
|
||||
" AND c.blocked=0",
|
||||
" AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
|
||||
@@ -149,7 +149,7 @@ impl Chatlist {
|
||||
" SELECT MAX(timestamp)",
|
||||
" FROM msgs",
|
||||
" WHERE chat_id=c.id",
|
||||
" AND hidden=0)",
|
||||
" AND (hidden=0 OR (hidden=1 AND state=19)))",
|
||||
" WHERE c.id>9",
|
||||
" AND c.blocked=0",
|
||||
" AND c.archived=1",
|
||||
@@ -175,7 +175,7 @@ impl Chatlist {
|
||||
" SELECT MAX(timestamp)",
|
||||
" FROM msgs",
|
||||
" WHERE chat_id=c.id",
|
||||
" AND hidden=0)",
|
||||
" AND (hidden=0 OR (hidden=1 AND state=19)))",
|
||||
" WHERE c.id>9",
|
||||
" AND c.blocked=0",
|
||||
" AND c.name LIKE ?",
|
||||
@@ -198,7 +198,7 @@ impl Chatlist {
|
||||
" SELECT MAX(timestamp)",
|
||||
" FROM msgs",
|
||||
" WHERE chat_id=c.id",
|
||||
" AND hidden=0)",
|
||||
" AND (hidden=0 OR (hidden=1 AND state=19)))",
|
||||
" WHERE c.id>9",
|
||||
" AND c.blocked=0",
|
||||
" AND c.archived=0",
|
||||
@@ -294,7 +294,7 @@ impl Chatlist {
|
||||
let lastmsg_id = self.ids[index].1;
|
||||
let mut lastcontact = None;
|
||||
|
||||
let mut lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
|
||||
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
|
||||
if lastmsg.from_id != DC_CONTACT_ID_SELF
|
||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
||||
{
|
||||
@@ -306,16 +306,6 @@ impl Chatlist {
|
||||
None
|
||||
};
|
||||
|
||||
if let Ok(draft) = get_draft(context, chat.id) {
|
||||
if draft.is_some()
|
||||
&& (lastmsg.is_none()
|
||||
|| draft.as_ref().unwrap().timestamp_sort
|
||||
> lastmsg.as_ref().unwrap().timestamp_sort)
|
||||
{
|
||||
lastmsg = draft;
|
||||
}
|
||||
}
|
||||
|
||||
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
|
||||
ret.text2 = None;
|
||||
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
|
||||
|
||||
@@ -31,7 +31,7 @@ pub enum Config {
|
||||
Displayname,
|
||||
Selfstatus,
|
||||
Selfavatar,
|
||||
#[strum(props(default = "0"))]
|
||||
#[strum(props(default = "1"))]
|
||||
BccSelf,
|
||||
#[strum(props(default = "1"))]
|
||||
E2eeEnabled,
|
||||
|
||||
@@ -421,16 +421,6 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
// remember the entered parameters on success
|
||||
// and restore to last-entered on failure.
|
||||
// this way, the parameters visible to the ui are always in-sync with the current configuration.
|
||||
if success {
|
||||
LoginParam::from_database(context, "").save_to_database(context, "configured_raw_");
|
||||
} else {
|
||||
LoginParam::from_database(context, "configured_raw_").save_to_database(context, "");
|
||||
}
|
||||
|
||||
context.free_ongoing();
|
||||
progress!(context, if success { 1000 } else { 0 });
|
||||
}
|
||||
|
||||
@@ -130,8 +130,7 @@ const DC_MAX_GET_INFO_LEN: usize = 100000;
|
||||
|
||||
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
|
||||
pub const DC_CONTACT_ID_SELF: u32 = 1;
|
||||
pub const DC_CONTACT_ID_INFO: u32 = 2;
|
||||
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
|
||||
pub const DC_CONTACT_ID_DEVICE: u32 = 2;
|
||||
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
||||
|
||||
pub const DC_CREATE_MVBOX: usize = 1;
|
||||
@@ -250,6 +249,8 @@ impl Default for Viewtype {
|
||||
}
|
||||
}
|
||||
|
||||
pub const DC_MSG_TRUNCATE_THRESHOLD:usize = 30000;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -153,16 +153,7 @@ impl Contact {
|
||||
blocked: false,
|
||||
origin: Origin::Unknown,
|
||||
};
|
||||
return Ok(contact);
|
||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
||||
let contact = Contact {
|
||||
id: contact_id,
|
||||
name: context.stock_str(StockMessage::DeviceMessages).into(),
|
||||
authname: "".into(),
|
||||
addr: "device@localhost".into(),
|
||||
blocked: false,
|
||||
origin: Origin::Unknown,
|
||||
};
|
||||
|
||||
return Ok(contact);
|
||||
}
|
||||
|
||||
|
||||
@@ -850,7 +850,7 @@ fn save_locations(
|
||||
if mime_parser.message_kml.is_some() {
|
||||
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
|
||||
let newest_location_id =
|
||||
location::save(context, chat_id, from_id, locations, true).unwrap_or_default();
|
||||
location::save(context, chat_id, from_id, locations, 1).unwrap_or_default();
|
||||
if 0 != newest_location_id && 0 == hidden {
|
||||
if location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() {
|
||||
location_id_written = true;
|
||||
@@ -865,8 +865,7 @@ fn save_locations(
|
||||
if contact.get_addr().to_lowercase() == addr.to_lowercase() {
|
||||
let locations = &mime_parser.location_kml.as_ref().unwrap().locations;
|
||||
let newest_location_id =
|
||||
location::save(context, chat_id, from_id, locations, false)
|
||||
.unwrap_or_default();
|
||||
location::save(context, chat_id, from_id, locations, 0).unwrap_or_default();
|
||||
if newest_location_id != 0 && hidden == 0 && !location_id_written {
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
|
||||
@@ -154,15 +154,13 @@ pub(crate) fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
|
||||
******************************************************************************/
|
||||
|
||||
pub fn dc_timestamp_to_str(wanted: i64) -> String {
|
||||
let ts = Local.timestamp(wanted, 0);
|
||||
let ts = chrono::Utc.timestamp(wanted, 0);
|
||||
ts.format("%Y.%m.%d %H:%M:%S").to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn dc_gm2local_offset() -> i64 {
|
||||
/* returns the offset that must be _added_ to an UTC/GMT-time to create the localtime.
|
||||
the function may return negative values. */
|
||||
let lt = Local::now();
|
||||
lt.offset().local_minus_utc() as i64
|
||||
((lt.offset().local_minus_utc() / (60 * 60)) * 100) as i64
|
||||
}
|
||||
|
||||
/* timesmearing */
|
||||
@@ -429,55 +427,21 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
|
||||
|
||||
pub(crate) fn dc_copy_file(
|
||||
context: &Context,
|
||||
src_path: impl AsRef<std::path::Path>,
|
||||
dest_path: impl AsRef<std::path::Path>,
|
||||
src: impl AsRef<std::path::Path>,
|
||||
dest: impl AsRef<std::path::Path>,
|
||||
) -> bool {
|
||||
let src_abs = dc_get_abs_path(context, &src_path);
|
||||
let mut src_file = match fs::File::open(&src_abs) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"failed to open for read '{}': {}",
|
||||
src_abs.display(),
|
||||
err
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let dest_abs = dc_get_abs_path(context, &dest_path);
|
||||
let mut dest_file = match fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&dest_abs)
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"failed to open for write '{}': {}",
|
||||
dest_abs.display(),
|
||||
err
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
match std::io::copy(&mut src_file, &mut dest_file) {
|
||||
let src_abs = dc_get_abs_path(context, &src);
|
||||
let dest_abs = dc_get_abs_path(context, &dest);
|
||||
match fs::copy(&src_abs, &dest_abs) {
|
||||
Ok(_) => true,
|
||||
Err(err) => {
|
||||
error!(
|
||||
context,
|
||||
"Cannot copy \"{}\" to \"{}\": {}",
|
||||
src_abs.display(),
|
||||
dest_abs.display(),
|
||||
src.as_ref().display(),
|
||||
dest.as_ref().display(),
|
||||
err
|
||||
);
|
||||
{
|
||||
// Attempt to remove the failed file, swallow errors resulting from that.
|
||||
fs::remove_file(dest_abs).ok();
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1319,9 +1283,6 @@ mod tests {
|
||||
|
||||
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
|
||||
|
||||
// attempting to copy a second time should fail
|
||||
assert!(!dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
|
||||
|
||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
|
||||
|
||||
let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();
|
||||
|
||||
66
src/imex.rs
66
src/imex.rs
@@ -1,5 +1,5 @@
|
||||
use core::cmp::{max, min};
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
use rand::{thread_rng, Rng};
|
||||
@@ -75,7 +75,7 @@ pub fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<Path>>)
|
||||
job_add(context, Action::ImexImap, 0, param, 0);
|
||||
}
|
||||
|
||||
/// Returns the filename of the backup found (otherwise an error)
|
||||
/// Returns the filename of the backup if found, nullptr otherwise.
|
||||
pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> {
|
||||
let dir_name = dir_name.as_ref();
|
||||
let dir_iter = std::fs::read_dir(dir_name)?;
|
||||
@@ -90,15 +90,13 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &path, true) {
|
||||
let curr_backup_time = sql
|
||||
.get_raw_config_int(context, "backup_time")
|
||||
.unwrap_or_default();
|
||||
let curr_backup_time =
|
||||
sql.get_raw_config_int(context, "backup_time")
|
||||
.unwrap_or_default() as u64;
|
||||
if curr_backup_time > newest_backup_time {
|
||||
newest_backup_path = Some(path);
|
||||
newest_backup_time = curr_backup_time;
|
||||
}
|
||||
info!(context, "backup_time of {} is {}", name, curr_backup_time);
|
||||
sql.close(&context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +105,7 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
|
||||
}
|
||||
match newest_backup_path {
|
||||
Some(path) => Ok(path.to_string_lossy().into_owned()),
|
||||
None => bail!("no backup found in {}", dir_name.display()),
|
||||
None => bail!("no backup found"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,11 +153,6 @@ fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// no maybe_add_bcc_self_device_msg() here.
|
||||
// the ui shows the dialog with the setup code on this device,
|
||||
// it would be too much noise to have two things popping up at the same time.
|
||||
// maybe_add_bcc_self_device_msg() is called on the other device
|
||||
// once the transfer is completed.
|
||||
Ok(setup_code)
|
||||
}
|
||||
|
||||
@@ -235,21 +228,6 @@ pub fn create_setup_code(_context: &Context) -> String {
|
||||
ret
|
||||
}
|
||||
|
||||
fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> {
|
||||
if !context.sql.get_raw_config_bool(context, "bcc_self") {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
// TODO: define this as a stockstring once the wording is settled.
|
||||
msg.text = Some(
|
||||
"It seems you are using multiple devices with Delta Chat. Great!\n\n\
|
||||
If you also want to synchronize outgoing messages accross all devices, \
|
||||
go to the settings and enable \"Send copy to self\"."
|
||||
.to_string(),
|
||||
);
|
||||
chat::add_device_msg_once(context, "bcc-self-hint", &mut msg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) -> Result<()> {
|
||||
ensure!(!msg_id.is_special(), "wrong id");
|
||||
|
||||
@@ -264,7 +242,6 @@ pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str)
|
||||
let sc = normalize_setup_code(setup_code);
|
||||
let armored_key = decrypt_setup_file(context, &sc, file)?;
|
||||
set_self_key(context, &armored_key, true, true)?;
|
||||
maybe_add_bcc_self_device_msg(context)?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -506,13 +483,10 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
|
||||
let now = time();
|
||||
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
|
||||
let dest_path_string = dest_path_filename.to_string_lossy().to_string();
|
||||
|
||||
sql::housekeeping(context);
|
||||
|
||||
sql::try_execute(context, &context.sql, "VACUUM;").ok();
|
||||
|
||||
// we close the database during the copy of the dbfile
|
||||
context.sql.close(context);
|
||||
info!(
|
||||
context,
|
||||
@@ -522,40 +496,38 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
);
|
||||
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename);
|
||||
context.sql.open(&context, &context.get_dbfile(), false);
|
||||
|
||||
if !copied {
|
||||
let s = dest_path_filename.to_string_lossy().to_string();
|
||||
bail!(
|
||||
"could not copy file from '{}' to '{}'",
|
||||
context.get_dbfile().display(),
|
||||
dest_path_string
|
||||
s
|
||||
);
|
||||
}
|
||||
let dest_sql = Sql::new();
|
||||
ensure!(
|
||||
dest_sql.open(context, &dest_path_filename, false),
|
||||
"could not open exported database {}",
|
||||
dest_path_string
|
||||
);
|
||||
let res = match add_files_to_export(context, &dest_sql) {
|
||||
match add_files_to_export(context, &dest_path_filename) {
|
||||
Err(err) => {
|
||||
dc_delete_file(context, &dest_path_filename);
|
||||
error!(context, "backup failed: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
Ok(()) => {
|
||||
dest_sql.set_raw_config_int(context, "backup_time", now as i32)?;
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int(context, "backup_time", now as i32)?;
|
||||
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
dest_sql.close(context);
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
fn add_files_to_export(context: &Context, dest_path_filename: &PathBuf) -> Result<()> {
|
||||
// add all files as blobs to the database copy (this does not require
|
||||
// the source to be locked, neigher the destination as it is used only here)
|
||||
let sql = Sql::new();
|
||||
ensure!(
|
||||
sql.open(context, &dest_path_filename, false),
|
||||
"could not open db"
|
||||
);
|
||||
if !sql.table_exists("backup_blobs") {
|
||||
sql::execute(
|
||||
context,
|
||||
|
||||
24
src/key.rs
24
src/key.rs
@@ -29,44 +29,44 @@ impl From<SignedSecretKey> for Key {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Key> for SignedSecretKey {
|
||||
impl std::convert::TryInto<SignedSecretKey> for Key {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Key) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
fn try_into(self) -> Result<SignedSecretKey, Self::Error> {
|
||||
match self {
|
||||
Key::Public(_) => Err(()),
|
||||
Key::Secret(key) => Ok(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey {
|
||||
impl<'a> std::convert::TryInto<&'a SignedSecretKey> for &'a Key {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &'a Key) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
fn try_into(self) -> Result<&'a SignedSecretKey, Self::Error> {
|
||||
match self {
|
||||
Key::Public(_) => Err(()),
|
||||
Key::Secret(key) => Ok(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Key> for SignedPublicKey {
|
||||
impl std::convert::TryInto<SignedPublicKey> for Key {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Key) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
fn try_into(self) -> Result<SignedPublicKey, Self::Error> {
|
||||
match self {
|
||||
Key::Public(key) => Ok(key),
|
||||
Key::Secret(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey {
|
||||
impl<'a> std::convert::TryInto<&'a SignedPublicKey> for &'a Key {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &'a Key) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
fn try_into(self) -> Result<&'a SignedPublicKey, Self::Error> {
|
||||
match self {
|
||||
Key::Public(key) => Ok(key),
|
||||
Key::Secret(_) => Err(()),
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
let stock_str =
|
||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_info_msg(context, chat_id, stock_str);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
}
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
if 0 != seconds {
|
||||
@@ -492,7 +492,7 @@ pub fn save(
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
locations: &[Location],
|
||||
independent: bool,
|
||||
independent: i32,
|
||||
) -> Result<u32, Error> {
|
||||
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
|
||||
context.sql.prepare2(
|
||||
@@ -507,7 +507,7 @@ pub fn save(
|
||||
for location in locations {
|
||||
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||
|
||||
if independent || !exists {
|
||||
if 0 != independent || !exists {
|
||||
stmt_insert.execute(params![
|
||||
location.timestamp,
|
||||
contact_id as i32,
|
||||
@@ -651,7 +651,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
|
||||
params![chat_id as i32],
|
||||
).is_ok() {
|
||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_info_msg(context, chat_id, stock_str);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,10 +381,23 @@ impl Message {
|
||||
self.timestamp_sort
|
||||
}
|
||||
|
||||
pub fn get_text(&self) -> Option<String> {
|
||||
self.text
|
||||
pub fn is_truncated(&self) -> bool {
|
||||
if let Some(text) = &self.text {
|
||||
return text.chars().count() > DC_MSG_TRUNCATE_THRESHOLD
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_text(&self, full_message: bool) -> Option<String> {
|
||||
if full_message {
|
||||
self.text
|
||||
.as_ref()
|
||||
.map(|text| dc_truncate(text, 30000, false).to_string())
|
||||
.map(|text| text.to_string())
|
||||
} else {
|
||||
self.text
|
||||
.as_ref()
|
||||
.map(|text| dc_truncate(text, DC_MSG_TRUNCATE_THRESHOLD, false).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_filename(&self) -> Option<String> {
|
||||
@@ -476,8 +489,8 @@ impl Message {
|
||||
|
||||
pub fn is_info(&self) -> bool {
|
||||
let cmd = self.param.get_cmd();
|
||||
self.from_id == DC_CONTACT_ID_INFO as libc::c_uint
|
||||
|| self.to_id == DC_CONTACT_ID_INFO as libc::c_uint
|
||||
self.from_id == DC_CONTACT_ID_DEVICE as libc::c_uint
|
||||
|| self.to_id == DC_CONTACT_ID_DEVICE as libc::c_uint
|
||||
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
|
||||
}
|
||||
|
||||
@@ -714,7 +727,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
ret += "\n";
|
||||
}
|
||||
|
||||
if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO {
|
||||
if msg.from_id == DC_CONTACT_ID_DEVICE || msg.to_id == DC_CONTACT_ID_DEVICE {
|
||||
// device-internal message, no further details needed
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -76,8 +76,6 @@ pub enum Param {
|
||||
ProfileImage = b'i',
|
||||
// For Chats
|
||||
Selftalk = b'K',
|
||||
// For Chats
|
||||
Devicetalk = b'D',
|
||||
// For QR
|
||||
Auth = b's',
|
||||
// For QR
|
||||
|
||||
@@ -382,11 +382,11 @@ impl<'a> Peerstate<'a> {
|
||||
sql::execute(
|
||||
self.context,
|
||||
sql,
|
||||
"UPDATE acpeerstates \
|
||||
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \
|
||||
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \
|
||||
"UPDATE acpeerstates \
|
||||
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \
|
||||
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \
|
||||
verified_key=?, verified_key_fingerprint=? \
|
||||
WHERE addr=?;",
|
||||
WHERE addr=?;",
|
||||
params![
|
||||
self.last_seen,
|
||||
self.last_seen_autocrypt,
|
||||
|
||||
19
src/pgp.rs
19
src/pgp.rs
@@ -5,7 +5,7 @@ use std::io::Cursor;
|
||||
use pgp::armor::BlockType;
|
||||
use pgp::composed::{
|
||||
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
|
||||
SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder,
|
||||
SignedSecretKey, SubkeyParamsBuilder,
|
||||
};
|
||||
use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
|
||||
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
|
||||
@@ -97,29 +97,18 @@ pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
||||
Some((Key::Public(public_key), Key::Secret(private_key)))
|
||||
}
|
||||
|
||||
/// Select subkey of the public key to use for encryption.
|
||||
///
|
||||
/// Currently the first subkey is selected.
|
||||
fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> {
|
||||
key.public_subkeys.iter().find(|_k|
|
||||
// TODO: check if it is an encryption subkey
|
||||
true)
|
||||
}
|
||||
|
||||
pub fn pk_encrypt(
|
||||
plain: &[u8],
|
||||
public_keys_for_encryption: &Keyring,
|
||||
private_key_for_signing: Option<&Key>,
|
||||
) -> Result<String, Error> {
|
||||
let lit_msg = Message::new_literal_bytes("", plain);
|
||||
let pkeys: Vec<&SignedPublicSubKey> = public_keys_for_encryption
|
||||
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
|
||||
.keys()
|
||||
.iter()
|
||||
.filter_map(|key| {
|
||||
key.as_ref()
|
||||
.try_into()
|
||||
.ok()
|
||||
.and_then(select_pk_for_encryption)
|
||||
let k: &Key = &key;
|
||||
k.try_into().ok()
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop)
|
||||
.unwrap_or_default();
|
||||
|
||||
chat::add_info_msg(
|
||||
chat::add_device_msg(
|
||||
context,
|
||||
id,
|
||||
format!("{} verified.", peerstate.addr.unwrap_or_default()),
|
||||
|
||||
@@ -638,7 +638,7 @@ fn secure_connection_established(context: &Context, contact_chat_id: u32) {
|
||||
"?"
|
||||
};
|
||||
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
|
||||
chat::add_info_msg(context, contact_chat_id, msg);
|
||||
chat::add_device_msg(context, contact_chat_id, msg);
|
||||
emit_event!(context, Event::ChatModified(contact_chat_id));
|
||||
}
|
||||
|
||||
@@ -654,7 +654,7 @@ fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32
|
||||
},
|
||||
);
|
||||
|
||||
chat::add_info_msg(context, contact_chat_id, &msg);
|
||||
chat::add_device_msg(context, contact_chat_id, &msg);
|
||||
error!(context, "{} ({})", &msg, details);
|
||||
}
|
||||
|
||||
@@ -735,7 +735,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<
|
||||
};
|
||||
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
|
||||
|
||||
chat::add_info_msg(context, contact_chat_id, msg);
|
||||
chat::add_device_msg(context, contact_chat_id, msg);
|
||||
emit_event!(context, Event::ChatModified(contact_chat_id));
|
||||
}
|
||||
}
|
||||
|
||||
51
src/sql.rs
51
src/sql.rs
@@ -5,7 +5,6 @@ use std::time::Duration;
|
||||
use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS};
|
||||
use thread_local_object::ThreadLocal;
|
||||
|
||||
use crate::constants::ShowEmails;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::{Error, Result};
|
||||
@@ -93,7 +92,8 @@ impl Sql {
|
||||
self.start_stmt(sql.to_string());
|
||||
self.with_conn(|conn| {
|
||||
let stmt = conn.prepare(sql)?;
|
||||
g(stmt, conn)
|
||||
let res = g(stmt, conn)?;
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@ impl Sql {
|
||||
let stmt1 = conn.prepare(sql1)?;
|
||||
let stmt2 = conn.prepare(sql2)?;
|
||||
|
||||
g(stmt1, stmt2, conn)
|
||||
let res = g(stmt1, stmt2, conn)?;
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -349,7 +350,7 @@ fn open(
|
||||
}
|
||||
|
||||
if !readonly {
|
||||
let mut exists_before_update = false;
|
||||
let mut exists_before_update = 0;
|
||||
let mut dbversion_before_update = 0;
|
||||
/* Init tables to dbversion=0 */
|
||||
if !sql.table_exists("config") {
|
||||
@@ -384,8 +385,8 @@ fn open(
|
||||
)?;
|
||||
sql.execute(
|
||||
"INSERT INTO contacts (id,name,origin) VALUES \
|
||||
(1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \
|
||||
(4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \
|
||||
(1,'self',262144), (2,'device',262144), (3,'rsvd',262144), \
|
||||
(4,'rsvd',262144), (5,'rsvd',262144), (6,'rsvd',262144), \
|
||||
(7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);",
|
||||
params![],
|
||||
)?;
|
||||
@@ -479,7 +480,7 @@ fn open(
|
||||
sql.set_raw_config_int(context, "dbversion", 0)?;
|
||||
}
|
||||
} else {
|
||||
exists_before_update = true;
|
||||
exists_before_update = 1;
|
||||
dbversion_before_update = sql
|
||||
.get_raw_config_int(context, "dbversion")
|
||||
.unwrap_or_default();
|
||||
@@ -489,7 +490,6 @@ fn open(
|
||||
// this should be done before updates that use high-level objects that
|
||||
// rely themselves on the low-level structure.
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
let mut dbversion = dbversion_before_update;
|
||||
let mut recalc_fingerprints = 0;
|
||||
let mut update_file_paths = 0;
|
||||
@@ -590,8 +590,6 @@ fn open(
|
||||
}
|
||||
if dbversion < 27 {
|
||||
info!(context, "[migration] v27");
|
||||
// chat.id=1 and chat.id=2 are the old deaddrops,
|
||||
// the current ones are defined by chats.blocked=2
|
||||
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?;
|
||||
sql.execute(
|
||||
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
|
||||
@@ -657,9 +655,6 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
if dbversion_before_update == 34 {
|
||||
// migrate database from the use of verified-flags to verified_key,
|
||||
// _only_ version 34 (0.17.0) has the fields public_key_verified and gossip_key_verified
|
||||
// this block can be deleted in half a year or so (created 5/2018)
|
||||
sql.execute(
|
||||
"UPDATE acpeerstates SET verified_key=gossip_key, verified_key_fingerprint=gossip_key_fingerprint WHERE gossip_key_verified=2;",
|
||||
params![]
|
||||
@@ -689,8 +684,6 @@ fn open(
|
||||
}
|
||||
if dbversion < 42 {
|
||||
info!(context, "[migration] v42");
|
||||
// older versions set the txt-field to the filenames, for debugging and fulltext search.
|
||||
// to allow text+attachment compound messages, we need to reset these fields.
|
||||
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
|
||||
dbversion = 42;
|
||||
sql.set_raw_config_int(context, "dbversion", 42)?;
|
||||
@@ -744,19 +737,14 @@ fn open(
|
||||
}
|
||||
if dbversion < 50 {
|
||||
info!(context, "[migration] v50");
|
||||
// installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly;
|
||||
// keep this default and use DC_SHOW_EMAILS_NO
|
||||
// only for new installations
|
||||
if exists_before_update {
|
||||
sql.set_raw_config_int(context, "show_emails", ShowEmails::All as i32)?;
|
||||
if 0 != exists_before_update {
|
||||
sql.set_raw_config_int(context, "show_emails", 2)?;
|
||||
}
|
||||
dbversion = 50;
|
||||
sql.set_raw_config_int(context, "dbversion", 50)?;
|
||||
}
|
||||
if dbversion < 53 {
|
||||
info!(context, "[migration] v53");
|
||||
// the messages containing _only_ locations
|
||||
// are also added to the database as _hidden_.
|
||||
sql.execute(
|
||||
"CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);",
|
||||
params![]
|
||||
@@ -804,26 +792,9 @@ fn open(
|
||||
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
)?;
|
||||
|
||||
sql.set_raw_config_int(context, "dbversion", 55)?;
|
||||
}
|
||||
if dbversion < 57 {
|
||||
info!(context, "[migration] v57");
|
||||
// label is a unique name and is currently used for device-messages only.
|
||||
// in contrast to rfc724_mid and other fields, the label is generated on the device
|
||||
// and allows reliable identifications this way.
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN label TEXT DEFAULT '';",
|
||||
params![],
|
||||
)?;
|
||||
if exists_before_update && sql.get_raw_config_int(context, "bcc_self").is_none() {
|
||||
sql.set_raw_config_int(context, "bcc_self", 1)?;
|
||||
}
|
||||
sql.set_raw_config_int(context, "dbversion", 57)?;
|
||||
}
|
||||
|
||||
// (2) updates that require high-level objects
|
||||
// (the structure is complete now and all objects are usable)
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
if 0 != recalc_fingerprints {
|
||||
info!(context, "[migration] recalc fingerprints");
|
||||
|
||||
@@ -110,8 +110,6 @@ pub enum StockMessage {
|
||||
Location = 66,
|
||||
#[strum(props(fallback = "Sticker"))]
|
||||
Sticker = 67,
|
||||
#[strum(props(fallback = "Device Messages"))]
|
||||
DeviceMessages = 68,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
|
||||
Reference in New Issue
Block a user