diff --git a/CHANGELOG.md b/CHANGELOG.md index c0ccbad7e..8007b53b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ### Changes - refactorings #3545 #3551 - use [pathlib](https://docs.python.org/3/library/pathlib.html) in provider update script #3543 +- `dc_get_chat_media()` can return media globally #3528 ### Fixes - improved error handling for account setup from qrcode #3474 diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 6e8dedf85..ea342514e 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1263,7 +1263,7 @@ void dc_marknoticed_chat (dc_context_t* context, uint32_t ch /** - * Returns all message IDs of the given types in a chat. + * Returns all message IDs of the given types in a given chat or any chat. * Typically used to show a gallery. * The result must be dc_array_unref()'d * @@ -1273,7 +1273,8 @@ void dc_marknoticed_chat (dc_context_t* context, uint32_t ch * * @memberof dc_context_t * @param context The context object as returned from dc_context_new(). - * @param chat_id The chat ID to get all messages with media from. + * @param chat_id >0: get messages with media from this chat ID. + * 0: get messages with media from any chat of the currently used account. * @param msg_type Specify a message type to query here, one of the @ref DC_MSG constants. * @param msg_type2 Alternative message type to search for. 0 to skip. * @param msg_type3 Alternative message type to search for. 0 to skip. @@ -1284,7 +1285,6 @@ dc_array_t* dc_get_chat_media (dc_context_t* context, uint32_t ch /** * Search next/previous message based on a given message and a list of types. - * The * Typically used to implement the "next" and "previous" buttons * in a gallery or in a media player. * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index d355aab98..704e3bb18 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1208,6 +1208,11 @@ pub unsafe extern "C" fn dc_get_chat_media( return ptr::null_mut(); } let ctx = &*context; + let chat_id = if chat_id == 0 { + None + } else { + Some(ChatId::new(chat_id)) + }; let msg_type = from_prim(msg_type).expect(&format!("invalid msg_type = {}", msg_type)); let or_msg_type2 = from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {}", or_msg_type2)); @@ -1216,16 +1221,10 @@ pub unsafe extern "C" fn dc_get_chat_media( block_on(async move { Box::into_raw(Box::new( - chat::get_chat_media( - ctx, - ChatId::new(chat_id), - msg_type, - or_msg_type2, - or_msg_type3, - ) - .await - .unwrap_or_log_default(ctx, "Failed get_chat_media") - .into(), + chat::get_chat_media(ctx, chat_id, msg_type, or_msg_type2, or_msg_type3) + .await + .unwrap_or_log_default(ctx, "Failed get_chat_media") + .into(), )) }) } diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index 20bc39bb7..95474c0ec 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -577,28 +577,28 @@ impl CommandApi { /// The list is already sorted and starts with the oldest message. /// Clients should not try to re-sort the list as this would be an expensive action /// and would result in inconsistencies between clients. + /// + /// Setting `chat_id` to `None` (`null` in typescript) means get messages with media + /// from any chat of the currently used account. async fn chat_get_media( &self, account_id: u32, - chat_id: u32, + chat_id: Option, message_type: MessageViewtype, or_message_type2: Option, or_message_type3: Option, ) -> Result> { let ctx = self.get_context(account_id).await?; + let chat_id = match chat_id { + None | Some(0) => None, + Some(id) => Some(ChatId::new(id)), + }; let msg_type = message_type.into(); let or_msg_type2 = or_message_type2.map_or(Viewtype::Unknown, |v| v.into()); let or_msg_type3 = or_message_type3.map_or(Viewtype::Unknown, |v| v.into()); - let media = get_chat_media( - &ctx, - ChatId::new(chat_id), - msg_type, - or_msg_type2, - or_msg_type3, - ) - .await?; + let media = get_chat_media(&ctx, chat_id, msg_type, or_msg_type2, or_msg_type3).await?; Ok(media.iter().map(|msg_id| msg_id.to_u32()).collect()) } diff --git a/deltachat-jsonrpc/typescript/generated/client.ts b/deltachat-jsonrpc/typescript/generated/client.ts index c278417da..8d3f3e86d 100644 --- a/deltachat-jsonrpc/typescript/generated/client.ts +++ b/deltachat-jsonrpc/typescript/generated/client.ts @@ -299,8 +299,11 @@ export class RawClient { * The list is already sorted and starts with the oldest message. * Clients should not try to re-sort the list as this would be an expensive action * and would result in inconsistencies between clients. + * + * Setting `chat_id` to `None` (`null` in typescript) means get messages with media + * from any chat of the currently used account. */ - public chatGetMedia(accountId: T.U32, chatId: T.U32, messageType: T.Viewtype, orMessageType2: (T.Viewtype|null), orMessageType3: (T.Viewtype|null)): Promise<(T.U32)[]> { + public chatGetMedia(accountId: T.U32, chatId: (T.U32|null), messageType: T.Viewtype, orMessageType2: (T.Viewtype|null), orMessageType3: (T.Viewtype|null)): Promise<(T.U32)[]> { return (this._transport.request('chat_get_media', [accountId, chatId, messageType, orMessageType2, orMessageType3] as RPC.Params)) as Promise<(T.U32)[]>; } diff --git a/deltachat-jsonrpc/typescript/generated/types.ts b/deltachat-jsonrpc/typescript/generated/types.ts index 118d80bf2..4748902eb 100644 --- a/deltachat-jsonrpc/typescript/generated/types.ts +++ b/deltachat-jsonrpc/typescript/generated/types.ts @@ -95,4 +95,4 @@ export type WebxdcMessageInfo={ * Implementations may offer an menu or a button to open this URL. */ "sourceCodeUrl":(string|null);}; -export type __AllTyps=[string,boolean,Record,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],U32,Account,U32,string,(ProviderInfo|null),U32,boolean,U32,Record,U32,string,(string|null),null,U32,Record,null,U32,string,null,U32,string,Qr,U32,string,(string|null),U32,(string)[],Record,U32,null,U32,null,U32,(U32)[],U32,U32,Usize,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record,U32,U32,FullChat,U32,U32,null,U32,U32,null,U32,string,string,U32,U32,U32,U32,(U32)[],U32,U32,Message,U32,(U32)[],Record,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record,U32,U32,Viewtype,(Viewtype|null),(Viewtype|null),(U32)[],U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,string,U32,U32]; +export type __AllTyps=[string,boolean,Record,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],U32,Account,U32,string,(ProviderInfo|null),U32,boolean,U32,Record,U32,string,(string|null),null,U32,Record,null,U32,string,null,U32,string,Qr,U32,string,(string|null),U32,(string)[],Record,U32,null,U32,null,U32,(U32)[],U32,U32,Usize,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record,U32,U32,FullChat,U32,U32,null,U32,U32,null,U32,string,string,U32,U32,U32,U32,(U32)[],U32,U32,Message,U32,(U32)[],Record,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record,U32,(U32|null),Viewtype,(Viewtype|null),(Viewtype|null),(U32)[],U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,string,U32,U32]; diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index a21502d89..ad5a53681 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -983,11 +983,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu chat::add_device_msg(&context, None, Some(&mut msg)).await?; } "listmedia" => { - ensure!(sel_chat.is_some(), "No chat selected."); - let images = chat::get_chat_media( &context, - sel_chat.as_ref().unwrap().get_id(), + sel_chat.map(|c| c.id), Viewtype::Image, Viewtype::Gif, Viewtype::Video, diff --git a/src/chat.rs b/src/chat.rs index 10317a71b..a7060a84c 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2423,7 +2423,7 @@ pub(crate) async fn mark_old_messages_as_noticed( pub async fn get_chat_media( context: &Context, - chat_id: ChatId, + chat_id: Option, msg_type: Viewtype, msg_type2: Viewtype, msg_type3: Viewtype, @@ -2434,11 +2434,13 @@ pub async fn get_chat_media( .query_map( "SELECT id FROM msgs - WHERE chat_id=? + WHERE (1=? OR chat_id=?) AND (type=? OR type=? OR type=?) + AND hidden=0 ORDER BY timestamp, id;", paramsv![ - chat_id, + if chat_id.is_none() { 1i32 } else { 0i32 }, + chat_id.unwrap_or_else(|| ChatId::new(0)), msg_type, if msg_type2 != Viewtype::Unknown { msg_type2 @@ -2479,7 +2481,7 @@ pub async fn get_next_media( if let Ok(msg) = Message::load_from_db(context, curr_msg_id).await { let list: Vec = get_chat_media( context, - msg.chat_id, + Some(msg.chat_id), if msg_type != Viewtype::Unknown { msg_type } else { @@ -5486,4 +5488,158 @@ mod tests { Ok(()) } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_get_chat_media() -> Result<()> { + let t = TestContext::new_alice().await; + let chat_id1 = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?; + let chat_id2 = create_group_chat(&t, ProtectionStatus::Unprotected, "bar").await?; + + assert_eq!( + get_chat_media( + &t, + Some(chat_id1), + Viewtype::Image, + Viewtype::Sticker, + Viewtype::Unknown + ) + .await? + .len(), + 0 + ); + + async fn send_media( + t: &TestContext, + chat_id: ChatId, + msg_type: Viewtype, + name: &str, + bytes: &[u8], + ) -> Result { + let file = t.get_blobdir().join(name); + tokio::fs::write(&file, bytes).await?; + let mut msg = Message::new(msg_type); + msg.set_file(file.to_str().unwrap(), None); + send_msg(t, chat_id, &mut msg).await + } + + send_media( + &t, + chat_id1, + Viewtype::Image, + "a.jpg", + include_bytes!("../test-data/image/rectangle200x180-rotated.jpg"), + ) + .await?; + send_media( + &t, + chat_id1, + Viewtype::Sticker, + "b.png", + include_bytes!("../test-data/image/avatar64x64.png"), + ) + .await?; + send_media( + &t, + chat_id2, + Viewtype::Image, + "c.jpg", + include_bytes!("../test-data/image/avatar64x64.png"), + ) + .await?; + send_media( + &t, + chat_id2, + Viewtype::Webxdc, + "d.xdc", + include_bytes!("../test-data/webxdc/minimal.xdc"), + ) + .await?; + + assert_eq!( + get_chat_media( + &t, + Some(chat_id1), + Viewtype::Image, + Viewtype::Unknown, + Viewtype::Unknown, + ) + .await? + .len(), + 1 + ); + assert_eq!( + get_chat_media( + &t, + Some(chat_id1), + Viewtype::Sticker, + Viewtype::Unknown, + Viewtype::Unknown, + ) + .await? + .len(), + 1 + ); + assert_eq!( + get_chat_media( + &t, + Some(chat_id1), + Viewtype::Sticker, + Viewtype::Image, + Viewtype::Unknown, + ) + .await? + .len(), + 2 + ); + assert_eq!( + get_chat_media( + &t, + Some(chat_id2), + Viewtype::Webxdc, + Viewtype::Unknown, + Viewtype::Unknown, + ) + .await? + .len(), + 1 + ); + assert_eq!( + get_chat_media( + &t, + None, + Viewtype::Image, + Viewtype::Unknown, + Viewtype::Unknown, + ) + .await? + .len(), + 2 + ); + assert_eq!( + get_chat_media( + &t, + None, + Viewtype::Image, + Viewtype::Sticker, + Viewtype::Unknown, + ) + .await? + .len(), + 3 + ); + assert_eq!( + get_chat_media( + &t, + None, + Viewtype::Image, + Viewtype::Sticker, + Viewtype::Webxdc, + ) + .await? + .len(), + 4 + ); + + Ok(()) + } }