Skip to content

Commit

Permalink
Webxdc Integration API, Maps Integration
Browse files Browse the repository at this point in the history
  • Loading branch information
r10s committed Apr 12, 2024
1 parent 0aea7d1 commit 2f8be12
Show file tree
Hide file tree
Showing 8 changed files with 435 additions and 1 deletion.
41 changes: 40 additions & 1 deletion deltachat-ffi/deltachat.h
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,36 @@ int dc_send_webxdc_status_update (dc_context_t* context, uint32_t msg_id, const
*/
char* dc_get_webxdc_status_updates (dc_context_t* context, uint32_t msg_id, uint32_t serial);


#define DC_INTEGRATION_MAPS 1

/**
* Init Webxdc integrations to be ready to be used with a chat.
*
* A Webxdc integration is eg. a Webxdc showing a map:
* The webxdc gets locations via setUpdateListener()
* and can define POIs via sendUpdate();
* core takes care of sending the data out, handling GPS etc.
*
* Webxdc Integrations are Webxdc shipped together with the main app.
* Later on, we could allow advanced users to replace these defaults by their own Webxdc.
*
* After calling dc_init_webxdc_integration(),
* UI can open the Webxdc view with all send/receive update handling as usual.
*
* There is no need to de-initialize the integration,
* however, the integration is valid only as long as not re-initialized.
*
* @memberof dc_context_t
* @param context The context object.
* @param integration_type The integration to obtain, one of the DC_INTEGRATION_* constants.
* @param chat_id The chat to get the integration for.
* @return ID of the message that refers to the Webxdc instance.
* UI can open a Webxdc as usual with this instance.
*/
uint32_t dc_init_webxdc_integration (dc_context_t* context, int integration_type, uint32_t chat_id);


/**
* Save a draft for a chat in the database.
*
Expand Down Expand Up @@ -4107,7 +4137,6 @@ char* dc_msg_get_webxdc_blob (const dc_msg_t* msg, const char*
* true if the Webxdc should get full internet access, including Webrtc.
* currently, this is only true for encrypted Webxdc's in the self chat
* that have requested internet access in the manifest.
* this is useful for development and maybe for internal integrations at some point.
*
* @memberof dc_msg_t
* @param msg The webxdc instance.
Expand Down Expand Up @@ -4678,6 +4707,16 @@ void dc_msg_set_override_sender_name(dc_msg_t* msg, const char* name)
void dc_msg_set_file (dc_msg_t* msg, const char* file, const char* filemime);


/**
* Mark Webxdc message shipped with the main app as a default integration.
* See dc_init_webxdc_integration() for details.
*
* @memberof dc_msg_t
* @param msg The Webxdc message object to mark as default integration.
*/
void dc_msg_set_default_webxdc_integration (dc_msg_t* msg);


/**
* Set the dimensions associated with message object.
* Typically this is the width and the height of an image or video associated using dc_msg_set_file().
Expand Down
33 changes: 33 additions & 0 deletions deltachat-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,29 @@ pub unsafe extern "C" fn dc_get_webxdc_status_updates(
.strdup()
}

#[no_mangle]
pub unsafe extern "C" fn dc_init_webxdc_integration(
context: *mut dc_context_t,
integration_type: libc::c_int,
chat_id: u32,
) -> u32 {
if context.is_null() || integration_type == 0 {
eprintln!("ignoring careless call to dc_init_webxdc_integration()");
return 0;
}
let ctx = &*context;
let chat_id = if chat_id == 0 {
None
} else {
Some(ChatId::new(chat_id))
};

block_on(ctx.init_webxdc_integration(chat_id))
.log_err(ctx)
.map(|msg_id| msg_id.map(|id| id.to_u32()).unwrap_or_default())
.unwrap_or(0)
}

#[no_mangle]
pub unsafe extern "C" fn dc_set_draft(
context: *mut dc_context_t,
Expand Down Expand Up @@ -3735,6 +3758,16 @@ pub unsafe extern "C" fn dc_msg_set_file(
)
}

#[no_mangle]
pub unsafe extern "C" fn dc_msg_set_default_webxdc_integration(msg: *mut dc_msg_t) {
if msg.is_null() {
eprintln!("ignoring careless call to dc_msg_set_default_webxdc_integration()");
return;
}
let ffi_msg = &mut *msg;
ffi_msg.message.set_default_webxdc_integration()
}

#[no_mangle]
pub unsafe extern "C" fn dc_msg_set_dimension(
msg: *mut dc_msg_t,
Expand Down
1 change: 1 addition & 0 deletions src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2034,6 +2034,7 @@ impl Chat {
msg.id = MsgId::new(u32::try_from(raw_id)?);

maybe_set_logging_xdc(context, msg, self.id).await?;
context.update_webxdc_integration_database(msg).await?;
}
context.scheduler.interrupt_ephemeral_task().await;
Ok(msg.id)
Expand Down
10 changes: 10 additions & 0 deletions src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ impl MsgId {
Ok(result)
}

pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
let res: Option<String> = context
.sql
.query_get_value("SELECT param FROM msgs WHERE id=?", (self,))
.await?;
Ok(res
.map(|s| s.parse().unwrap_or_default())
.unwrap_or_default())
}

/// Put message into trash chat and delete message text.
///
/// It means the message is deleted locally, but not on the server.
Expand Down
6 changes: 6 additions & 0 deletions src/param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ pub enum Param {
/// For Webxdc Message Instances: timestamp of summary update.
WebxdcSummaryTimestamp = b'Q',

/// For Webxdc Message Instances: Webxdc is an integration, see init_webxdc_integration()
WebxdcIntegration = b'L',

/// For Webxdc Message Instances: Chat to integrate in.
WebxdcIntegrateFor = b'2',

/// For messages: Whether [crate::message::Viewtype::Sticker] should be forced.
ForceSticker = b'X',
// 'L' was defined as ProtectionSettingsTimestamp for Chats, however, never used in production.
Expand Down
17 changes: 17 additions & 0 deletions src/webxdc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
//! - `last_serial` - serial number of the last status update to send
//! - `descr` - text to send along with the updates
mod integration;
mod maps_integration;

use std::path::Path;

use anyhow::{anyhow, bail, ensure, format_err, Context as _, Result};
Expand Down Expand Up @@ -456,6 +459,12 @@ impl Context {
bail!("send_webxdc_status_update: message {instance_msg_id} is not a webxdc message, but a {viewtype} message.");
}

if instance.param.get_int(Param::WebxdcIntegration).is_some() {
return self
.intercept_send_webxdc_status_update(instance, status_update)
.await;
}

let chat_id = instance.chat_id;
let chat = Chat::load_from_db(self, chat_id)
.await
Expand Down Expand Up @@ -636,6 +645,14 @@ impl Context {
instance_msg_id: MsgId,
last_known_serial: StatusUpdateSerial,
) -> Result<String> {
let param = instance_msg_id.get_param(self).await?;
if param.get_int(Param::WebxdcIntegration).is_some() {
let instance = Message::load_from_db(self, instance_msg_id).await?;
return self
.intercept_get_webxdc_status_updates(instance, last_known_serial)
.await;
}

let json = self
.sql
.query_map(
Expand Down
88 changes: 88 additions & 0 deletions src/webxdc/integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use crate::chat::ChatId;
use crate::context::Context;
use crate::message::{Message, MsgId, Viewtype};
use crate::param::Param;
use crate::webxdc::{maps_integration, StatusUpdateItem, StatusUpdateSerial};
use anyhow::Result;

impl Message {
/// Mark Webxdc message shipped with the main app as a default integration.
pub fn set_default_webxdc_integration(&mut self) {
self.hidden = true;
self.param.set_int(Param::WebxdcIntegration, 1);
}
}

impl Context {
/// Get Webxdc instance used for optional integrations.
/// If there is no integration, the caller may decide to add a default one.
pub async fn init_webxdc_integration(
&self,
integrate_for: Option<ChatId>,
) -> Result<Option<MsgId>> {
if let Some(instance_id) = self.sql.get_raw_config_int("webxdc_integration").await? {
if let Some(mut instance) =
Message::load_from_db_optional(self, MsgId::new(instance_id as u32)).await?
{
if instance.viewtype == Viewtype::Webxdc && !instance.chat_id.is_trash() {
let integrate_for = integrate_for.unwrap_or_default().to_u32() as i32;
if instance.param.get_int(Param::WebxdcIntegrateFor) != Some(integrate_for) {
instance
.param
.set_int(Param::WebxdcIntegrateFor, integrate_for);
instance.update_param(self).await?;
}
return Ok(Some(instance.id));
}
}
}
Ok(None)
}

// Check if a Webxdc shall be used as an integration and remember that.
pub(crate) async fn update_webxdc_integration_database(&self, msg: &Message) -> Result<()> {
if msg.viewtype == Viewtype::Webxdc && msg.param.get_int(Param::WebxdcIntegration).is_some()
{
// using set_config_internal() leads to recursion warning because of sync messages
self.sql
.set_raw_config_int("webxdc_integration", msg.id.to_u32() as i32)
.await?;
}
Ok(())
}

// Intercept sending updates from Webxdc to core.
pub(crate) async fn intercept_send_webxdc_status_update(
&self,
instance: Message,
status_update: StatusUpdateItem,
) -> Result<()> {
let chat_id = self.integrate_for(&instance)?;
maps_integration::intercept_send_update(self, chat_id, status_update).await
}

// Intercept Webxdc requesting updates from core.
pub(crate) async fn intercept_get_webxdc_status_updates(
&self,
instance: Message,
last_known_serial: StatusUpdateSerial,
) -> Result<String> {
let chat_id = self.integrate_for(&instance)?;
maps_integration::intercept_get_updates(self, chat_id, last_known_serial).await
}

// Get chat the Webxdc is integrated for.
// This is the chat given to `init_webxdc_integration()`.
fn integrate_for(&self, instance: &Message) -> Result<Option<ChatId>> {
let raw_id = instance
.param
.get_int(Param::WebxdcIntegrateFor)
.unwrap_or(0) as u32;
let chat_id = if raw_id > 0 {
Some(ChatId::new(raw_id))
} else {
None
};
Ok(chat_id)
}
}
Loading

0 comments on commit 2f8be12

Please sign in to comment.