Skip to content

add a device-chat #794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Nov 6, 2019
Binary file added assets/icon-device.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 56 additions & 1 deletion deltachat-ffi/deltachat.h
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,27 @@ 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);


/**
* Get draft for a chat, if any.
* See dc_set_draft() for more details about drafts.
Expand Down Expand Up @@ -2715,6 +2736,39 @@ 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
Expand Down Expand Up @@ -3370,7 +3424,8 @@ void dc_msg_latefiling_mediasize (dc_msg_t* msg, int width, int hei


#define DC_CONTACT_ID_SELF 1
#define DC_CONTACT_ID_DEVICE 2
#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_LAST_SPECIAL 9


Expand Down
37 changes: 37 additions & 0 deletions deltachat-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,23 @@ 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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a libc::?? return type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

well, i did just a copy+paste from dc_chat_can_send() :)

i leave it for now, maybe we can go trough the ffi and streamline this at some point for all affected functions.

Copy link
Collaborator

@link2xt link2xt Nov 5, 2019

Choose a reason for hiding this comment

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

libc::uint32_t is deprecated in favor of u32 in Rust. And using u32 is better than libc::c_uint because JNI and NAPI don't work these C types anyway.

Keep it returning u32 and specify it as uint32_t in C API.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If we are going to streamline it, I would rather change all ints to int32_t, because it is equal to int32_t on all existing architectures anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks for explaining, @link2xt

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_get_draft(context: *mut dc_context_t, chat_id: u32) -> *mut dc_msg_t {
if context.is_null() {
Expand Down Expand Up @@ -2264,6 +2281,26 @@ 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() {
Expand Down
31 changes: 24 additions & 7 deletions examples/repl/cmdline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ 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\
Expand Down Expand Up @@ -521,13 +522,12 @@ 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(),
temp_name,
chat.get_name(),
chat::get_fresh_msg_cnt(context, chat.get_id()),
);
let lot = chatlist.get_summary(context, i, Some(&chat));
Expand Down Expand Up @@ -586,25 +586,33 @@ 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 temp2 = if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string()
} else 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(),
temp_name,
temp2,
sel_chat.get_name(),
subtitle,
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())? {
Expand Down Expand Up @@ -822,6 +830,15 @@ 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.");

Expand Down
3 changes: 2 additions & 1 deletion python/src/deltachat/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
DC_STATE_OUT_DELIVERED = 26
DC_STATE_OUT_MDN_RCVD = 28
DC_CONTACT_ID_SELF = 1
DC_CONTACT_ID_DEVICE = 2
DC_CONTACT_ID_INFO = 2
DC_CONTACT_ID_DEVICE = 5
DC_CONTACT_ID_LAST_SPECIAL = 9
DC_MSG_TEXT = 10
DC_MSG_IMAGE = 20
Expand Down
73 changes: 66 additions & 7 deletions src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ 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();
}
}
}
Expand All @@ -109,6 +111,14 @@ 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,
Expand Down Expand Up @@ -582,6 +592,12 @@ 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,
Expand All @@ -605,7 +621,14 @@ pub fn create_or_lookup_by_contact_id(
"INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')",
100,
chat_name,
if contact_id == DC_CONTACT_ID_SELF as u32 { "K=1" } else { "" },
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()
},
create_blocked as u8,
contact.get_addr(),
),
Expand Down Expand Up @@ -677,8 +700,7 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
}
}

fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Result<MsgId, Error> {
msg.id = MsgId::new_unset();
fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
Copy link
Contributor

Choose a reason for hiding this comment

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

this is a nice refactor

if msg.type_0 == Viewtype::Text {
// the caller should check if the message text is empty
} else if msgtype_has_file(msg.type_0) {
Expand Down Expand Up @@ -714,10 +736,16 @@ fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Res
} 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
Expand Down Expand Up @@ -994,7 +1022,7 @@ 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_DEVICE
" AND m.from_id!=2", // 2=DC_CONTACT_ID_INFO
" AND m.hidden=0",
" AND chats.blocked=2",
" AND contacts.blocked=0",
Expand Down Expand Up @@ -1898,15 +1926,46 @@ 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, chat_id: u32, text: impl AsRef<str>) {
pub fn add_device_msg(context: &Context, 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");

prepare_msg_blob(context, msg)?;
unarchive(context, chat_id)?;

context.sql.execute(
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this a custom insert? I appreciate you don't want to call send_msg() but i'm surprised to see a whole new sql query. I probably missed something though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

there is just no other INSERT INTO msgs that would only do an insert. the ones in receive_imf(), set_draft(), set_info_msg() do other, unwanted stuff and/or have a customized insert on their own.

but i think this is no issue. these sqls are easy enough and so it is pretty clear what happens.

"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \
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,
],
)?;

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 });

Ok(msg_id)
}

pub fn add_info_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_DEVICE,
DC_CONTACT_ID_DEVICE,
DC_CONTACT_ID_INFO,
DC_CONTACT_ID_INFO,
dc_create_smeared_timestamp(context),
Viewtype::Text,
MessageState::InNoticed,
Expand Down
3 changes: 2 additions & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ 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_DEVICE: u32 = 2;
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;
Expand Down
11 changes: 10 additions & 1 deletion src/contact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,16 @@ 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);
}

Expand Down
Loading