Skip to content

Commit

Permalink
Improve media handling
Browse files Browse the repository at this point in the history
Handle m.video, m.file, m.audio and m.image in _handle_incoming_media.
Remove _handle incoming_image. On incoming media, always print a chat
message with the download link and mimetype/size. If a thumbnail_url is
available and the thumbnail size is small, download and show that. Otherwise,
only for m_image, ask for a server generated thumbnail.
  • Loading branch information
radiocane committed Feb 22, 2018
1 parent 1a53f61 commit 1ee4cc0
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 64 deletions.
21 changes: 16 additions & 5 deletions matrix-api.c
Expand Up @@ -905,6 +905,20 @@ MatrixApiRequestData *matrix_api_upload_file(MatrixConnectionData *conn,
return fetch_data;
}

GString *get_download_url(const gchar *homeserver, const gchar *uri)
{
GString *url;

/* Sanity check the uri - TODO: Add more sanity */
if (strncmp(uri, "mxc://", 6)) {
return NULL;
}
url = g_string_new(homeserver);
g_string_append(url, "_matrix/media/r0/download/");
g_string_append(url, uri + 6); /* i.e. after the mxc:// */
return url;
}

/**
* Download a file
* @param uri URI string in the form mxc://example.com/unique
Expand All @@ -920,14 +934,11 @@ MatrixApiRequestData *matrix_api_download_file(MatrixConnectionData *conn,
GString *url;
MatrixApiRequestData *fetch_data;

/* Sanity check the uri - TODO: Add more sanity */
if (strncmp(uri, "mxc://", 6)) {
url = get_download_url(conn->homeserver, uri);
if (!url) {
error_callback(conn, user_data, "bad media uri");
return NULL;
}
url = g_string_new(conn->homeserver);
g_string_append(url, "_matrix/media/r0/download/");
g_string_append(url, uri + 6); /* i.e. after the mxc:// */

/* I'd like to validate the headers etc a bit before downloading the
* data (maybe using _handle_header_completed), also I'm not convinced
Expand Down
9 changes: 9 additions & 0 deletions matrix-api.h
Expand Up @@ -301,6 +301,15 @@ MatrixApiRequestData *matrix_api_upload_file(MatrixConnectionData *conn,
MatrixApiBadResponseCallback bad_response_callback,
gpointer user_data);


/* Get the complete download url for a given uri
*
* @param homeserver The server hosting the file
* @param uri The file uri
*/
GString *get_download_url(const gchar *homeserver, const gchar *uri);


/**
* Download a file
*
Expand Down
149 changes: 90 additions & 59 deletions matrix-room.c
Expand Up @@ -21,10 +21,13 @@
/* stdlib */
#include <inttypes.h>
#include <string.h>
#include <errno.h>

#include <glib/gstdio.h>

/* libpurple */
#include "connection.h"
#include "debug.h"
#include <libpurple/connection.h>
#include <libpurple/debug.h>

#include "libmatrix.h"
#include "matrix-api.h"
Expand Down Expand Up @@ -70,7 +73,7 @@ static MatrixConnectionData *_get_connection_data_from_conversation(
#define PURPLE_CONV_FLAG_NEEDS_NAME_UPDATE 0x1

/* Arbitrary limit on the size of an image to receive; should make configurable */
static const size_t purple_max_image_size=250*1024;
static const size_t purple_max_media_size=250*1024;

/**
* Get the member table for a room
Expand Down Expand Up @@ -652,7 +655,7 @@ static void _image_download_bad_response(MatrixConnectionData *ma, gpointer user
gchar *escaped_body = purple_markup_escape_text(rid->original_body, -1);
serv_got_chat_in(rid->conv->account->gc, g_str_hash(rid->room_id),
rid->sender_display_name, PURPLE_MESSAGE_RECV,
g_strdup_printf("%s (failed to download %d)",
g_strdup_printf("%s (bad response to download image %d)",
escaped_body, http_response_code),
rid->timestamp / 1000);
purple_conversation_set_data(rid->conv, PURPLE_CONV_DATA_ACTIVE_SEND,
Expand All @@ -669,7 +672,7 @@ static void _image_download_error(MatrixConnectionData *ma, gpointer user_data,
gchar *escaped_body = purple_markup_escape_text(rid->original_body, -1);
serv_got_chat_in(rid->conv->account->gc, g_str_hash(rid->room_id),
rid->sender_display_name, PURPLE_MESSAGE_RECV,
g_strdup_printf("%s (failed to download %s)",
g_strdup_printf("%s (failed to download image %s)",
escaped_body, error_message), rid->timestamp / 1000);
purple_conversation_set_data(rid->conv, PURPLE_CONV_DATA_ACTIVE_SEND,
NULL);
Expand All @@ -680,93 +683,120 @@ static void _image_download_error(MatrixConnectionData *ma, gpointer user_data,


/*
* Called from matrix_room_handle_timeline_event when it finds an m.image;
* msg_body has the fallback text,
* Called from matrix_room_handle_timeline_event when it finds an m.video
* or m.audio or m.file or m.image; msg_body has the fallback text,
* json_content_object has the json for the content sub object
*
* Return TRUE if we managed to download the image and everything needed
* FALSE if we failed; caller does fallback.
*/
static gboolean _handle_incoming_image(PurpleConversation *conv,
static gboolean _handle_incoming_media(PurpleConversation *conv,
const gint64 timestamp, const gchar *room_id,
const gchar *sender_display_name, const gchar *msg_body,
JsonObject *json_content_object) {
JsonObject *json_content_object, const gchar *msg_type) {
MatrixConnectionData *conn = _get_connection_data_from_conversation(conv);
MatrixApiRequestData *fetch_data = NULL;
struct ReceiveImageData *rid;
gboolean use_thumb = FALSE;

const gchar *url;
GString *download_url;
guint64 size = 0;
const gchar *mime_type = "unknown";
JsonObject *json_info_object;

url = matrix_json_object_get_string_member(json_content_object, "url");
if (!url) {
/* That seems odd, oh well, no point in getting upset */
purple_debug_info("matrixprpl", "failed to get url for m.image");
purple_debug_info("matrixprpl", "failed to get url for media\n");
return FALSE;
}
download_url = get_download_url(conn->homeserver, url);
if (!download_url) {
purple_debug_error("matrixprpl", "failed to get download_url for media\n");
return FALSE;
}

/* the 'info' member is optional but if we've got it we can check it to early
* reject the image if it's something that's huge or we don't know the title.
*/
/* the 'info' member is optional */
json_info_object = matrix_json_object_get_object_member(json_content_object,
"info");
purple_debug_info("matrixprpl", "%s: %s json_info_object=%p\n", __func__,
url, json_info_object);
if (json_info_object) {
guint64 size;
const gchar *mime_type;

/* OK, we've got some (optional) info on the image */
/* OK, we've got some (optional) info */
size = matrix_json_object_get_int_member(json_info_object, "size");
if (size > purple_max_image_size) {
use_thumb = TRUE;
}
mime_type = matrix_json_object_get_string_member(json_info_object,
"mimetype");
if (mime_type) {
if (!is_known_image_type(mime_type)) {
purple_debug_info("matrixprpl", "%s: unknown mimetype %s",
__func__, mime_type);
return FALSE;
}
}
purple_debug_info("matrixprpl", "image info good: %s of %" PRId64,
purple_debug_info("matrixprpl", "media info good: %s of %" PRId64 "\n",
mime_type, size);
}

rid = g_new0(struct ReceiveImageData, 1);
rid->conv = conv;
rid->timestamp = timestamp;
rid->sender_display_name = sender_display_name;
rid->room_id = room_id;
rid->original_body = g_strdup(msg_body);
serv_got_chat_in(conv->account->gc, g_str_hash(room_id),
sender_display_name, PURPLE_MESSAGE_RECV,
/* TODO convert size into a human readable format */
g_strdup_printf("%s (type %s size %" PRId64 ") %s",
msg_body, mime_type, size, download_url->str), timestamp / 1000);

/* m.audio is not supposed to have a thumbnail, handling completed
*/
if (!strcmp("m.audio", msg_type)) {
return TRUE;
}

if (!use_thumb) {
fetch_data = matrix_api_download_file(conn, url,
purple_max_image_size,
/* If a thumbnail_url is available and the thumbnail size is small,
* download that. Otherwise, only for m.image, ask for a server generated
* thumbnail.
*/
int is_image = !strcmp("m.image", msg_type);
const gchar *thumb_url = "";
JsonObject *json_thumb_info;
guint64 thumb_size = 0;
if ((!strcmp("m.video", msg_type)) && json_info_object) {
/* m.video can have an info object containing thumbnail_* memebers */
thumb_url = matrix_json_object_get_string_member(json_info_object, "thumbnail_url");
json_thumb_info = matrix_json_object_get_object_member(json_info_object, "thumbnail_info");
} else {
/* m.image and m.file can have thumbnail_* members directly in the content object */
thumb_url = matrix_json_object_get_string_member(json_content_object, "thumbnail_url");
json_thumb_info = matrix_json_object_get_object_member(json_content_object, "thumbnail_info");
}
if (json_thumb_info) {
thumb_size = matrix_json_object_get_int_member(json_thumb_info, "size");
}
if (is_image && (size > 0) && (size < purple_max_media_size)) {
/* if an m.image is small, get that instead of the thumbnail */
thumb_url = url;
thumb_size = size;
}
if ((thumb_url && (thumb_size > 0) && (thumb_size < purple_max_media_size)) || is_image) {
struct ReceiveImageData *rid;
rid = g_new0(struct ReceiveImageData, 1);
rid->conv = conv;
rid->timestamp = timestamp;
rid->sender_display_name = sender_display_name;
rid->room_id = room_id;
rid->original_body = g_strdup(msg_body);

if (thumb_url && (thumb_size > 0) && (thumb_size < purple_max_media_size)) {
fetch_data = matrix_api_download_file(conn, thumb_url,
purple_max_media_size,
_image_download_complete,
_image_download_error,
_image_download_bad_response, rid);
} else {
/* TODO: Configure the size of thumbnails, and provide
* a way for the user to get the full image if they want.
_image_download_bad_response,
rid);
} else {
/* Ask the server to generate a thumbnail. Only for m.image.
* TODO: Configure the size of thumbnails.
* 640x480 is a good a width as any and reasonably likely to
* fit in the byte size limit unless someone has a big long
* tall png.
*/
fetch_data = matrix_api_download_thumb(conn, url,
purple_max_image_size,
purple_max_media_size,
640, 480, TRUE, /* Scaled */
_image_download_complete,
_image_download_error,
_image_download_bad_response, rid);
_image_download_bad_response,
rid);
}
purple_conversation_set_data(conv, PURPLE_CONV_DATA_ACTIVE_SEND,
fetch_data);
return fetch_data != NULL;
}

purple_conversation_set_data(conv, PURPLE_CONV_DATA_ACTIVE_SEND,
fetch_data);

return fetch_data != NULL;
return TRUE;
}

/**
Expand Down Expand Up @@ -937,12 +967,13 @@ void matrix_room_handle_timeline_event(PurpleConversation *conv,

if (!strcmp(msg_type, "m.emote")) {
tmp_body = g_strdup_printf("/me %s", msg_body);
} else if (!strcmp(msg_type, "m.image")) {
if (_handle_incoming_image(conv, timestamp, room_id, sender_display_name,
msg_body, json_content_obj)) {
} else if ((!strcmp(msg_type, "m.video")) || (!strcmp(msg_type, "m.audio")) || \
(!strcmp(msg_type, "m.file")) || (!strcmp(msg_type, "m.image"))) {
if (_handle_incoming_media(conv, timestamp, room_id, sender_display_name,
msg_body, json_content_obj, msg_type)) {
return;
}
/* Fall through - we couldn't get the image, treat as text */
/* Fall through - we couldn't handle the media, treat as text */
}
flags = PURPLE_MESSAGE_RECV;

Expand Down

0 comments on commit 1ee4cc0

Please sign in to comment.