From 74a8138084562e6aa27eafefc3e69aeceaa524cb Mon Sep 17 00:00:00 2001 From: Chris T Date: Wed, 3 Feb 2021 19:44:34 -0500 Subject: [PATCH] Add MMSD support --- src/chatty-message.c | 29 ++ src/chatty-message.h | 5 + src/chatty-window.c | 3 + src/meson.build | 1 + src/users/chatty-mm-account.c | 244 ++++++++- src/users/chatty-mm-account.h | 8 + src/users/chatty-mmsd.c | 952 ++++++++++++++++++++++++++++++++++ src/users/chatty-mmsd.h | 40 ++ 8 files changed, 1276 insertions(+), 6 deletions(-) create mode 100644 src/users/chatty-mmsd.c create mode 100644 src/users/chatty-mmsd.h diff --git a/src/chatty-message.c b/src/chatty-message.c index 574edcd..5b96c2e 100644 --- a/src/chatty-message.c +++ b/src/chatty-message.c @@ -38,6 +38,7 @@ struct _ChattyMessage ChattyFileInfo *file; ChattyFileInfo *preview; + GList *files; ChattyMsgType type; ChattyMsgStatus status; @@ -163,6 +164,24 @@ chatty_message_set_file (ChattyMessage *self, self->file = file; } +GList * +chatty_message_get_files_list (ChattyMessage *self) +{ + g_return_val_if_fail (CHATTY_IS_MESSAGE (self), NULL); + + return self->files; +} + +void +chatty_message_set_files_list (ChattyMessage *self, + GList *files) +{ + g_return_if_fail (CHATTY_IS_MESSAGE (self)); + g_return_if_fail (!self->files); + + self->files = files; +} + ChattyFileInfo * chatty_message_get_preview (ChattyMessage *self) { @@ -254,6 +273,16 @@ chatty_message_get_user (ChattyMessage *self) return self->user; } +void +chatty_message_set_user (ChattyMessage *self, + ChattyItem *user) +{ + g_return_if_fail (CHATTY_IS_MESSAGE (self)); + + self->user = user; + +} + const char * chatty_message_get_user_name (ChattyMessage *self) { diff --git a/src/chatty-message.h b/src/chatty-message.h index 053db12..e505b96 100644 --- a/src/chatty-message.h +++ b/src/chatty-message.h @@ -56,6 +56,9 @@ void chatty_message_set_encrypted (ChattyMessage *self, ChattyFileInfo *chatty_message_get_file (ChattyMessage *self); void chatty_message_set_file (ChattyMessage *self, ChattyFileInfo *file); +GList *chatty_message_get_files_list (ChattyMessage *self); +void chatty_message_set_files_list (ChattyMessage *self, + GList *files); ChattyFileInfo *chatty_message_get_preview (ChattyMessage *self); void chatty_message_set_preview (ChattyMessage *self, ChattyFileInfo *info); @@ -71,6 +74,8 @@ void chatty_message_set_sms_id (ChattyMessage *self, guint id); const char *chatty_message_get_text (ChattyMessage *self); ChattyItem *chatty_message_get_user (ChattyMessage *self); +void chatty_message_set_user (ChattyMessage *self, + ChattyItem *user); const char *chatty_message_get_user_name (ChattyMessage *self); void chatty_message_set_user_name (ChattyMessage *self, const char *user_name); diff --git a/src/chatty-window.c b/src/chatty-window.c index 8d12b11..b71aa96 100644 --- a/src/chatty-window.c +++ b/src/chatty-window.c @@ -295,6 +295,9 @@ window_chat_name_matches (ChattyItem *item, protocols = chatty_manager_get_active_protocols (self->manager); protocol = chatty_item_get_protocols (item); + /* Debug */ + return TRUE; + if (protocol != CHATTY_PROTOCOL_MATRIX && !(protocols & chatty_item_get_protocols (item))) return FALSE; diff --git a/src/meson.build b/src/meson.build index ce26f77..9aaab8e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,7 @@ libsrc = [ 'users/chatty-item.c', 'users/chatty-contact.c', 'users/chatty-mm-buddy.c', + 'users/chatty-mmsd.c', 'users/chatty-pp-buddy.c', 'users/chatty-account.c', 'users/chatty-mm-account.c', diff --git a/src/users/chatty-mm-account.c b/src/users/chatty-mm-account.c index 6177d8b..1a1df2b 100644 --- a/src/users/chatty-mm-account.c +++ b/src/users/chatty-mm-account.c @@ -20,6 +20,7 @@ #include "chatty-window.h" #include "chatty-history.h" #include "chatty-mm-chat.h" +#include "chatty-mm-buddy.h" #include "chatty-utils.h" #include "chatty-mm-account.h" @@ -248,6 +249,92 @@ sms_create_cb (GObject *object, g_steal_pointer (&task)); } +/* This function assumes the modem number has already been taken out */ +static ChattyChat * +chatty_mm_account_find_group_mms_chat (ChattyMmAccount *self, + const char *recipientlist) +{ + gsize n_items; + int mms_recipient_number, chat_user_number; + int recipient_matches; + gchar **send; + + g_assert (CHATTY_IS_MM_ACCOUNT (self)); + + if (!recipientlist || !*recipientlist) + return NULL; + + /* Figure out how many recipients there are */ + g_debug ("Recipients: %s", recipientlist); + send = g_strsplit (recipientlist, ",", -1); + + mms_recipient_number = g_strv_length (send); + g_debug ("You have %d Recipients!", mms_recipient_number); + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->chat_list)); + + for (guint i = 0; i < n_items; i++) { + g_autoptr(ChattyChat) chat = NULL; + g_autoptr(ChattyItem) item = NULL; + g_autofree char *number1 = NULL; + g_autofree char *number2 = NULL; + GListModel *user_list; + const char *user_id, *country_code; + + chat = g_list_model_get_item (G_LIST_MODEL (self->chat_list), i); + + user_list = chatty_chat_get_users (chat); + chat_user_number = g_list_model_get_n_items (user_list); + + /* + * Compare number of people in chat to number of recipients. If they + * don't match, then not worth checking + */ + g_debug ("This chat has %d Recipients!", chat_user_number); + if(chat_user_number != mms_recipient_number) + continue; + + recipient_matches = 0; + for (guint j = 0; j < mms_recipient_number; j++) { + for (guint k = 0; k < chat_user_number; k++) { + item = g_list_model_get_item (user_list, k); + + if (item) + user_id = chatty_item_get_name (item); + else { + if (chatty_chat_is_im (chat)) { + g_debug("IM does not have users, using title instead"); + user_id = chatty_chat_get_chat_name (CHATTY_CHAT (chat)); + } else { + g_warning("Not a valid user!"); + continue; + } + } + + country_code = chatty_settings_get_country_iso_code (chatty_settings_get_default ()); + number1 = chatty_utils_check_phonenumber (user_id, country_code); + number2 = chatty_utils_check_phonenumber (send[j], country_code); + g_debug ("User %s Recipient: %s", number1, number2); + g_debug ("User id %s send[j]: %s", user_id, send[j]); + if (g_strcmp0 (number1, number2) == 0) { + recipient_matches = recipient_matches + 1; + g_debug ("User matches Recipient! Recipient Matches: %d", recipient_matches); + break; + } + } + } + g_debug ("Number of Recipients: %d", mms_recipient_number); + g_debug ("Number of Recipient Matches: %d", recipient_matches); + if(recipient_matches == mms_recipient_number) { + g_debug("Found the right chat!"); + g_strfreev(send); + return chat; + } + } + g_strfreev(send); + return NULL; +} + static ChattyChat * chatty_mm_account_find_sms_chat (ChattyMmAccount *self, const char *phone) @@ -279,14 +366,19 @@ chatty_mm_account_find_sms_chat (ChattyMmAccount *self, if (user_list) item = g_list_model_get_item (user_list, 0); - if (item) + if (item) { user_id = chatty_item_get_name (item); - else + g_debug("UserId: %s", user_id); + } + else { user_id = chatty_chat_get_chat_name (CHATTY_CHAT (chat)); + g_debug("chatty_chat name: %s", user_id); + } country_code = chatty_settings_get_country_iso_code (chatty_settings_get_default ()); number1 = chatty_utils_check_phonenumber (user_id, country_code); number2 = chatty_utils_check_phonenumber (phone, country_code); + g_debug("Number1 %s, Number2 %s", number1, number2); if (g_strcmp0 (number1, number2) == 0) return chat; @@ -295,6 +387,49 @@ chatty_mm_account_find_sms_chat (ChattyMmAccount *self, return NULL; } +void +chatty_mm_account_recieve_mms_cb(ChattyMmAccount *self, + ChattyMessage *message, + gchar *sender, + gchar *recipientlist) +{ + ChattyChat *chat; + int recipients; + gchar **send; + GPtrArray *array; + g_autofree char *number = NULL; + const char *country_code; + ChattyMmBuddy *newbuddy; + + chat = chatty_mm_account_find_group_mms_chat (self, recipientlist); + if (!chat) { + chat = (ChattyChat *)chatty_mm_chat_new (recipientlist, recipientlist, CHATTY_PROTOCOL_SMS, TRUE); + + country_code = chatty_settings_get_country_iso_code (chatty_settings_get_default ()); + + send = g_strsplit (recipientlist, ",", -1); + recipients = g_strv_length (send); + array = g_ptr_array_new (); + for (guint j = 0; j < recipients; j++) { + number = chatty_utils_check_phonenumber (send[j], country_code); + newbuddy = chatty_mm_buddy_new (number, number); + g_ptr_array_add (array, newbuddy); + g_debug("Added Number: %s", number); + } + chatty_mm_chat_add_users (CHATTY_MM_CHAT (chat), array); + g_list_store_append (self->chat_list, chat); + g_object_unref (chat); + g_strfreev(send); + } + + chatty_message_set_user (message, + CHATTY_ITEM (chatty_mm_chat_get_user (CHATTY_MM_CHAT (chat)))); + + chatty_mm_chat_append_message (CHATTY_MM_CHAT (chat), message); + chatty_history_add_message (self->history_db, chat, message); + +} + static void mm_account_delete_message_async (ChattyMmAccount *self, ChattyMmDevice *device, @@ -313,6 +448,34 @@ mm_account_delete_message_async (ChattyMmAccount *self, sms_path, self->cancellable, NULL, NULL); } +struct parse_sms_properties { + ChattyMmAccount *self; + ChattyMmDevice *device; +}; + +void +chatty_mm_account_push_notify_cb (gpointer user_data, + MMSms *sms, + gboolean push_notify_complete) +{ + struct parse_sms_properties *props = user_data; + ChattyMmAccount *self; + ChattyMmDevice *device; + if(push_notify_complete) { + g_debug("Push Notify Complete!"); + self = props->self; + device = props->device; + g_free(props); + mm_account_delete_message_async (self, device, sms, NULL, NULL); + } else { + /* + * TODO: Handle the SMS WAP not being sent. + * This is usually due to a network outage. + */ + g_debug("Push Notify had an Error!"); + } +} + static void mm_account_add_sms (ChattyMmAccount *self, ChattyMmDevice *device, @@ -321,17 +484,43 @@ mm_account_add_sms (ChattyMmAccount *self, { g_autoptr(ChattyMessage) message = NULL; ChattyChat *chat; + ChattyMmBuddy *newbuddy; g_autofree char *phone = NULL; g_autofree char *uuid = NULL; const char *msg; ChattyMsgDirection direction = CHATTY_DIRECTION_UNKNOWN; + const guint8 *data; + gsize data_len; + struct parse_sms_properties *props; + GPtrArray *array; g_assert (CHATTY_IS_MM_ACCOUNT (self)); g_assert (MM_IS_SMS (sms)); msg = mm_sms_get_text (sms); - if (!msg) - return; + if (!msg) { + data = mm_sms_get_data(sms, &data_len); + if(!data) { + return; + } else { + props = g_try_new0(struct parse_sms_properties, 1); + props->self = self; + props->device = device; + if (chatty_mm_mmsd_push_notify_async (props, sms)) { + g_debug("SMS WAP sent successfully"); + return; + } + else { + g_warning("MMSD is not active! Could not notify MMSD"); + g_free(props); + /* + * TODO: Handle the SMS WAP not being sent. + * This is usually due to a network outage. + */ + return; + } + } + } phone = chatty_utils_check_phonenumber (mm_sms_get_number (sms), chatty_settings_get_country_iso_code (chatty_settings_get_default ())); @@ -344,6 +533,10 @@ mm_account_add_sms (ChattyMmAccount *self, if (!chat) { chat = (ChattyChat *)chatty_mm_chat_new (phone, phone, CHATTY_PROTOCOL_SMS, TRUE); + newbuddy = chatty_mm_buddy_new (phone, phone); + array = g_ptr_array_new (); + g_ptr_array_add (array, newbuddy); + chatty_mm_chat_add_users (CHATTY_MM_CHAT (chat), array); g_list_store_append (self->chat_list, chat); g_object_unref (chat); } @@ -363,6 +556,31 @@ mm_account_add_sms (ChattyMmAccount *self, mm_account_delete_message_async (self, device, sms, NULL, NULL); } +static void parse_sms (ChattyMmAccount *self, + ChattyMmDevice *device, + MMSms *sms); +static void +sms_state_change_cb (MMSms *sms, + GParamSpec *pspec, + gpointer user_data) +{ + struct parse_sms_properties *props = user_data; + MMSmsState state; + ChattyMmAccount *self; + ChattyMmDevice *device; + + state = mm_sms_get_state (sms); + g_debug("SMS State Changed: %d", state); + + if (state == MM_SMS_STATE_RECEIVED) { + // The message has been completely received + self = props->self; + device = props->device; + g_free(props); + parse_sms(self, device, sms); + } +} + static void parse_sms (ChattyMmAccount *self, ChattyMmDevice *device, @@ -371,6 +589,7 @@ parse_sms (ChattyMmAccount *self, MMSmsPduType type; MMSmsState state; guint sms_id; + struct parse_sms_properties *props; g_assert (CHATTY_IS_MM_ACCOUNT (self)); g_assert (MM_IS_SMS (sms)); @@ -405,7 +624,17 @@ parse_sms (ChattyMmAccount *self, } } else if (type == MM_SMS_PDU_TYPE_CDMA_DELIVER || type == MM_SMS_PDU_TYPE_DELIVER) { - if (state == MM_SMS_STATE_RECEIVED) + if (state == MM_SMS_STATE_RECEIVING) { + // The first chunk of a multipart SMS has been + // received -> wait for MM_SMS_STATE_RECEIVED + props = g_try_new0(struct parse_sms_properties, 1); + props->self = self; + props->device = device; + g_signal_connect (sms, + "notify::state", + G_CALLBACK (sms_state_change_cb), + props); + } else if (state == MM_SMS_STATE_RECEIVED) mm_account_add_sms (self, device, sms, state); } } @@ -489,6 +718,7 @@ mm_object_added_cb (ChattyMmAccount *self, device = chatty_mm_device_new (); device->mm_object = g_object_ref (MM_OBJECT (object)); + chatty_mm_mmsd_load (self, device->mm_object); g_list_store_append (self->device_list, device); if (g_list_model_get_n_items (G_LIST_MODEL (self->device_list)) > 0) { @@ -523,6 +753,8 @@ mm_object_removed_cb (ChattyMmAccount *self, device = g_list_model_get_item (G_LIST_MODEL (self->device_list), i); if (g_strcmp0 (mm_object_get_path (MM_OBJECT (object)), mm_object_get_path (device->mm_object)) == 0) { + //TODO: Add logic on which Modem supports MMSD + chatty_mm_mmsd_close (); g_list_store_remove (self->device_list, i); break; } @@ -831,7 +1063,7 @@ chatty_mm_account_has_mms_feature (ChattyMmAccount *self) g_return_val_if_fail (CHATTY_IS_MM_ACCOUNT (self), FALSE); /* TODO */ - return FALSE; + return TRUE; } void diff --git a/src/users/chatty-mm-account.h b/src/users/chatty-mm-account.h index 33b65b6..2e8b6d6 100644 --- a/src/users/chatty-mm-account.h +++ b/src/users/chatty-mm-account.h @@ -17,6 +17,7 @@ #include "chatty-enums.h" #include "chatty-chat.h" #include "chatty-account.h" +#include "chatty-mmsd.h" G_BEGIN_DECLS @@ -44,5 +45,12 @@ void chatty_mm_account_send_sms_async (ChattyMmAccount *self, gboolean chatty_mm_account_send_sms_finish (ChattyMmAccount *self, GAsyncResult *result, GError **error); +void chatty_mm_account_push_notify_cb (gpointer user_data, + MMSms *sms, + gboolean push_notify_complete); +void chatty_mm_account_recieve_mms_cb (ChattyMmAccount *self, + ChattyMessage *message, + gchar *sender, + gchar *recipientlist); G_END_DECLS diff --git a/src/users/chatty-mmsd.c b/src/users/chatty-mmsd.c new file mode 100644 index 0000000..5c26cd1 --- /dev/null +++ b/src/users/chatty-mmsd.c @@ -0,0 +1,952 @@ +/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* chatty-mmsd.c + * + * Copyright 2020, 2021 Purism SPC, kop316 + * + * Author(s): + * Mohammed Sadiq + * kop316 + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define _XOPEN_SOURCE 700 +#define G_LOG_DOMAIN "chatty-mm-account" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "chatty-settings.h" +#include "chatty-account.h" +#include "chatty-window.h" +#include "chatty-history.h" +#include "chatty-mm-chat.h" +#include "chatty-utils.h" +#include "chatty-mm-account.h" +#include "chatty-mmsd.h" + +/** + * SECTION: chatty-mmsd + * @title: ChattyMmsd + * @short_description: A handler for mmsd + * @include: "chatty-mmsd.h" + * + */ + +/** + * mmsd Context Connection: + * + * The enumerations if mmsd has a bearer handler error. + */ +enum { + MMSD_MM_MODEM_MMSC_MISCONFIGURED, //the MMSC is the default value + MMSD_MM_MODEM_NO_BEARERS_ACTIVE, //The Modem has no bearers + MMSD_MM_MODEM_INTERFACE_DISCONNECTED, //mmsd found the right bearer, but it is disconnected + MMSD_MM_MODEM_INCORRECT_APN_CONNECTED, //no APN is connected that matches the settings + MMSD_MM_MODEM_FUTURE_CASE_DISCONNECTED, //Reserved for future case + MMSD_MM_MODEM_CONTEXT_ACTIVE //No error, context activated properly +} mmsd_context_connection; + +struct chatty_mm_mmsd_data { + GDBusConnection *connection; + guint mmsd_watch_id; + GDBusProxy *manager_proxy; + GDBusProxy *service_proxy; + guint mmsd_message_proxy_watch_id; + guint mmsd_service_proxy_watch_id; + + GDBusConnection *modemmanager_connection; + guint modemmanager_watch_id; + guint modemmanager_bearer_handler_watch_id; + GDBusProxy *modemmanager_proxy; + + gulong modemmanager_bearer_connected_signal_id; + gboolean bearer_connected_signal_connected; + MMBearer *modem_bearer; + gboolean mms_waiting; + + MMObject *mm_object; + MMModem *modem; + gchar *modem_number; +}; + +struct chatty_mm_mmsd_data *mmsd; + +typedef struct _mms_payload mms_payload; + +struct _mms_payload { + char *sender; + char *chat; + char *uuid; + time_t timestamp; + ChattyMsgDirection direction; + ChattyMsgStatus mms_status; + ChattyMsgType chatty_msg_type; + GList *files; +}; + +/* + * TODO: when you add an attachment, + * resize it to be below 300 kB and jpeg. + * Resize Libraries: cairo or gdk-pixbuf + */ + +static void +cb_chatty_mm_mmsd_delete_mms (GObject *interface, + GAsyncResult *result, + gpointer *user_data) +{ + g_autoptr(GError) error = NULL; + + if (g_dbus_proxy_call_finish (G_DBUS_PROXY(interface), + result, + &error)) { + g_debug ("MMS delete finished"); + } else { + g_debug ("Couldn't delete MMS - error: %s", error ? error->message : "unknown"); + } +} + +static void +chatty_mm_mmsd_delete_mms (gchar *objectpath) +{ + GDBusProxy *message_proxy; + g_debug("%s", objectpath); + + /* + * I see you thinking you can move this, DONT! the objectpath is unique + * based on the message. + * + */ + + message_proxy = + g_dbus_proxy_new_sync (mmsd->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + "org.ofono.mms", + objectpath, + "org.ofono.mms.Message", + NULL, + NULL); + + g_dbus_proxy_call (message_proxy, + "Delete", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback)cb_chatty_mm_mmsd_delete_mms, + NULL); + + g_object_unref(message_proxy); +} + +static char +*format_number_e164(char *sender) { + /* + * Some MMSCs (looking at you T-Mobile USA) mangle the MMSC payload sender + * I am correcting it here to put it into E164 format, the format Chatty + * expects. + */ + + EPhoneNumber *number; + g_autoptr(GError) err = NULL; + const char *country_code; + + /* The debugs here get noisy while I have been working, so I just commented them out */ + if (!e_phone_number_is_supported()) { + g_warning ("evolution-data-server built without libphonenumber support"); + } else { + /* g_debug ("Number MMSC sent you %s", sender); */ + country_code = chatty_settings_get_country_iso_code (chatty_settings_get_default ()); + /* g_debug ("Country Code: %s", country_code); */ + number = e_phone_number_from_string (sender, country_code, &err); + if (number == NULL) { + g_warning ("E Phone number to String didn't Work! Message: %s", err->message); + } else { + sender = e_phone_number_to_string (number, E_PHONE_NUMBER_FORMAT_E164); + /* g_debug ("Number after formatting: %s", sender); */ + e_phone_number_free (number); + } + } + return sender; +} + +/* +* Chatty has the MMS payload now, and is going to parse it, figure out +* if it is a Group MMS or not, and post it in the correct IM or chat. +*/ +static mms_payload +*chatty_mm_mmsd_receive_message (GVariant *message_t) +{ + char *contents; + char *temp; + ChattyFileInfo *mms_attachment = NULL; + ChattyMsgDirection direction = CHATTY_DIRECTION_UNKNOWN; + ChattyMsgStatus mms_status = CHATTY_STATUS_UNKNOWN; + GVariant *properties, *recipients, *attachments, *attach; + GVariantDict dict; + GVariantIter iter; + GFile *container = NULL; + GFile *savepath; + GFile *new; + GFileOutputStream *out; + const gchar *home = g_get_home_dir (); + gchar *filenode, *containerpath, *mimetype, *filename; + gchar *objectpath, *date, *subject, *sender, *smil; + gchar *status; + gchar *tag; + gsize length, written = 0; + GString *who; + GVariantIter recipientiter; + GVariant *reciever; + guint size, data; + g_autoptr(GError) error = NULL; + int smil_size; + mms_payload *payload; + struct tm tm; + + /* Parse through the MMS Payload. Probably will need a better parser at some point. */ + g_variant_get (message_t, "(o@a{?*})", &objectpath, &properties); + + g_debug ("%s: New MMS at %s", __func__, objectpath); + //g_debug ("%s", g_variant_print (properties, FALSE)); + + g_variant_dict_init (&dict, properties); + g_variant_dict_lookup (&dict, "Date", "s", &date); + g_variant_dict_lookup (&dict, "Subject", "s", &subject); + g_variant_dict_lookup (&dict, "Sender", "s", &sender); + g_variant_dict_lookup (&dict, "Smil", "s", &smil); + /* refer to mms_message_status_get_string() in mmsutil.c for a listing of statuses */ + g_variant_dict_lookup (&dict, "Status", "s", &status); + + /* Determine what type of MMS we have */ + if (g_strcmp0(status, "draft") == 0) { + /* + * This is if you sent an MMS but it isn't sent yet. + * Leave it alone, as MMSD needs to send it. + */ + g_debug ("Message has not been sent yet! Leaving it alone..."); + return NULL; + } else if (g_strcmp0(status, "sent") == 0) { + /* + * This is if you sent an MMS and it was sent. + */ + direction = CHATTY_DIRECTION_OUT; + mms_status = CHATTY_STATUS_SENT; + g_debug ("Message has been sent!"); + } else if (g_strcmp0(status, "received") == 0) { + g_debug ("Recieved an MMS from someone else!"); + direction = CHATTY_DIRECTION_IN; + mms_status = CHATTY_STATUS_RECIEVED; + } else { + /* + * Other options are "downloaded" and "read". + * TODO: Not sure what to do with these? + */ + g_debug ("Not sure what to do with these!"); + } + + payload = g_try_new0(mms_payload, 1); + payload->uuid = g_uuid_string_random (); + payload->direction = direction; + payload->mms_status = mms_status; + payload->chatty_msg_type = CHATTY_MESSAGE_UNKNOWN; + strptime(date, "%Y-%m-%dT%H:%M:%S%z", &tm); + payload->timestamp = mktime(&tm); + payload->files = NULL; + + /* Fill out Sender and All Numbers */ + sender = format_number_e164 (sender); + payload->sender = g_strdup(sender); + + recipients = g_variant_dict_lookup_value (&dict, "Recipients", G_VARIANT_TYPE_STRING_ARRAY); + + who = g_string_new (sender); + g_variant_iter_init (&recipientiter, recipients); + while ((reciever = g_variant_iter_next_value (&recipientiter))) { + g_variant_get (reciever, "s", &temp); + temp = format_number_e164 (temp); + if(g_strcmp0 (mmsd->modem_number, temp) != 0) { + who = g_string_append (who, ","); + who = g_string_append (who, temp); + } + } + payload->chat = g_string_free (who, FALSE); + + /* Go through the attachments */ + attachments = g_variant_dict_lookup_value (&dict, "Attachments", G_VARIANT_TYPE_ARRAY); + //g_debug ("%s", g_variant_print (attachments, FALSE)); + g_variant_iter_init (&iter, attachments); + + while ((attach = g_variant_iter_next_value (&iter))) { + + mms_attachment = g_try_new0(ChattyFileInfo, 1); + g_variant_get (attach, "(ssstt)", &filenode, + &mimetype, + &containerpath, + &data, + &size); + + if (!container) { + /* Container holds all of the attachments, so create a directory for it */ + container = g_file_new_for_path (containerpath); + g_file_load_contents (container, + NULL, + &contents, + &length, + NULL, + &error); + + if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + g_debug("MMS Payload does not exist, deleting..."); + chatty_mm_mmsd_delete_mms(objectpath); + return NULL; + } else if (error) { + g_warning ("Error loading MMSD Payload: %s", error->message); + return NULL; + } + + tag = g_strconcat (date, sender, NULL); + savepath = g_file_new_build_filename (home, + ".purple", + "mms", + tag, + NULL); + g_free (tag); + if (!g_file_make_directory_with_parents (savepath, NULL, &error)) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { + g_debug("Directory exists, skipping error..."); + error = NULL; + } else if (error) { + g_warning ("Error creating Directory: %s", error->message); + return NULL; + } + } + if (smil != NULL) { + smil_size = strlen (smil); + smil_size = smil_size * sizeof(char); + new = g_file_get_child (savepath, "mms.smil"); + out = g_file_create(new, G_FILE_CREATE_PRIVATE, NULL, &error); + if (out == NULL) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { + g_debug("%s Exists, Skipping Error....", + g_file_peek_path (new)); + } else { + g_warning ("Failed to create %s: %s", + g_file_peek_path (new), error->message); + } + error = NULL; + } else { + if (!g_output_stream_write_all ((GOutputStream *) out, + smil, + smil_size, + &written, + NULL, + &error)) { + g_warning ("Failed to write to file %s: %s", + g_file_peek_path (new), error->message); + error = NULL; + } else { + mms_attachment->file_name = g_strdup ("mms.smil"); + mms_attachment->mime_type = g_strdup ("smil"); + mms_attachment->size = written; + mms_attachment->path = g_file_get_path (new); + mms_attachment->url = g_file_get_uri (new); + + payload->files = g_list_append (payload->files, mms_attachment); + mms_attachment = NULL; + mms_attachment = g_try_new0(ChattyFileInfo, 1); + } + } + } + } + filename = g_strdup (filenode); + g_strdelimit (filename, "<>", ' '); + g_strstrip (filename); + filename = g_path_get_basename (filename); + + new = g_file_get_child (savepath, filename); + out = g_file_create (new, G_FILE_CREATE_PRIVATE, NULL, &error); + if (error) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { + g_debug("%s Exists, Skipping Error....", + g_file_peek_path (new)); + } else { + g_warning ("Failed to create %s: %s", + g_file_peek_path (new), error->message); + } + error = NULL; + } else { + if (!g_output_stream_write_all (G_OUTPUT_STREAM(out), + contents + data, + size, + &written, + NULL, + &error)) { + g_warning ("Failed to write to file %s: %s", + g_file_peek_path (new), error->message); + error = NULL; + } + } + g_output_stream_close (G_OUTPUT_STREAM(out), NULL, NULL); + + mms_attachment->file_name = g_strdup (filename); + mms_attachment->mime_type = g_strdup (mimetype); + mms_attachment->size = written; + mms_attachment->path = g_file_get_path (new); + mms_attachment->url = g_file_get_uri (new); + + payload->files = g_list_append (payload->files, mms_attachment); + mms_attachment = NULL; + } + g_object_unref (container); + + chatty_mm_mmsd_delete_mms(objectpath); + + return payload; + +} + +struct sms_push_notify_properties { + gpointer user_data; + MMSms *sms; +}; + +static void +chatty_mm_mmsd_push_notify_cb (GObject *interface, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + gboolean push_notify_complete; + struct sms_push_notify_properties *push_notify = user_data; + gpointer pointer_mule = push_notify->user_data; + MMSms *sms = push_notify->sms; + + g_free(push_notify); + g_debug("%s",__func__); + g_dbus_proxy_call_finish (mmsd->modemmanager_proxy, + res, + &error); + + if (error != NULL) { + g_warning ("Error in Proxy call: %s\n", error->message); + g_debug("Setting MMS waiting to TRUE"); + mmsd->mms_waiting = TRUE; + push_notify_complete = FALSE; + } else { + push_notify_complete = TRUE; + } + chatty_mm_account_push_notify_cb(pointer_mule, sms, push_notify_complete); +} + +/* + * User_data is just a pointer to a structure for the callback. + * No need to define it + */ +gboolean +chatty_mm_mmsd_push_notify_async (gpointer user_data, + MMSms *sms) +{ + GVariant *args = mm_gdbus_sms_get_data (MM_GDBUS_SMS (sms)); + struct sms_push_notify_properties *push_notify; + push_notify = g_try_new0(struct sms_push_notify_properties, 1); + push_notify->user_data = user_data; + push_notify->sms = sms; + + if (!G_IS_DBUS_PROXY (mmsd->modemmanager_proxy)) { + g_warning("mmsd not active!"); + g_debug("Setting MMS waiting to TRUE"); + mmsd->mms_waiting = TRUE; + g_free(push_notify); + return FALSE; + } else { + g_dbus_proxy_call (mmsd->modemmanager_proxy, + "PushNotify", + g_variant_new ("(@ay)", args), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback)chatty_mm_mmsd_push_notify_cb, + push_notify); + return TRUE; + } +} + +static const char +*time_to_str(const time_t *t) +{ + static char buf[128]; + struct tm tm; + + strftime(buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime_r(t, &tm)); + buf[127] = '\0'; + + return buf; +} + +static void +chatty_mmsd_process_mms(ChattyMmAccount *self, + mms_payload *payload) +{ + + g_autoptr(ChattyMessage) message = NULL; + g_autoptr(GError) error = NULL; + char *contents; + gsize length; + GFile *text_file = NULL; + GList *l; + GString *url_list; + gchar *sender; + gchar *recipientlist; + + g_debug("Got the MMS Payload!."); + g_debug("MMS Payload Sender: %s", payload->sender); + g_debug("Sender and Recipients: %s", payload->chat); + g_debug("UUID: %s", payload->uuid); + g_debug("Time: %s", time_to_str(&payload->timestamp)); + g_debug("ChattyMsgDirection %d, ChattyMsgStatus %d, ChattyMsgType %d", + payload->direction, payload->mms_status, payload->chatty_msg_type); + url_list = g_string_new (NULL); + for (l = payload->files; l != NULL; l = l->next) { + ChattyFileInfo *mms_attachment = l->data; + g_debug("Attachment File Name: %s", mms_attachment->file_name); + g_debug("Attachment url: %s", mms_attachment->url); + g_debug("Attachment path: %s", mms_attachment->path); + g_debug("Attachment MIME Type: %s", mms_attachment->mime_type); + g_debug("Attachment Size: %ld", mms_attachment->size); + url_list = g_string_append (url_list, mms_attachment->url); + if (l->next != NULL) + url_list = g_string_append (url_list, "\n\n"); + + if(g_str_match_string ("text", mms_attachment->mime_type, TRUE)) { + g_debug("Found Text file!"); + text_file = g_file_new_for_path (mms_attachment->path); + g_file_load_contents (text_file, + NULL, + &contents, + &length, + NULL, + &error); + url_list = g_string_prepend (url_list, "\n\n"); + url_list = g_string_prepend (url_list, contents); + url_list = g_string_prepend (url_list, "MMS Message: "); + } + } + + message = chatty_message_new (NULL, + payload->sender, url_list->str, payload->uuid, + payload->timestamp, payload->chatty_msg_type, + payload->direction, payload->mms_status); + chatty_message_set_files_list (message, payload->files); + + sender = g_strdup(payload->sender); + recipientlist = g_strdup(payload->chat); + + g_string_free(url_list, TRUE); + g_free(payload->sender); + g_free(payload->chat); + g_free(payload->uuid); + g_free(payload); + + chatty_mm_account_recieve_mms_cb(self, message, sender, + recipientlist); + +} + +static void +chatty_mm_mmsd_get_new_mms_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + mms_payload *payload; + ChattyMmAccount *self = user_data; + g_debug ("%s", __func__); + + + payload = chatty_mm_mmsd_receive_message (parameters); + if(payload == NULL) { + g_warning("There was an error with decoding the MMS Payload!"); + } else { + chatty_mmsd_process_mms(self, payload); + } +} + +static void +chatty_mm_mmsd_get_all_mms_cb (GObject *service, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + GVariant *ret, *msg_pack; + GVariantIter iter; + gsize num; + mms_payload *payload; + ChattyMmAccount *self = user_data; + + g_debug ("%s", __func__); + + ret = g_dbus_proxy_call_finish (mmsd->service_proxy, + res, + &error); + + if (error != NULL) { + g_warning ("Error in Proxy call: %s\n", error->message); + } else { + msg_pack = g_variant_get_child_value (ret, 0); + + if ((num = g_variant_iter_init (&iter, msg_pack))) { + GVariant *message_t; + + g_debug ("%lu MMS message(s)", num); + while ((message_t = g_variant_iter_next_value (&iter))) { + payload = chatty_mm_mmsd_receive_message (message_t); + if(payload == NULL) { + g_warning("There was an error with decoding the MMS Payload!"); + } else { + chatty_mmsd_process_mms(self, payload); + } + } + } + } +} + +static void +chatty_mm_mmsd_get_service_cb (GObject *service, + GAsyncResult *res, + gpointer user_data) +{ + ChattyMmAccount *self = user_data; + mmsd->service_proxy = g_dbus_proxy_new_finish (res, NULL); + + + + g_debug ("Got MMSD Service"); + + mmsd->mmsd_service_proxy_watch_id = g_dbus_connection_signal_subscribe (mmsd->connection, + "org.ofono.mms", + "org.ofono.mms.Service", + "MessageAdded", + "/org/ofono/mms/modemmanager", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + (GDBusSignalCallback)chatty_mm_mmsd_get_new_mms_cb, + self, + NULL); + + + if (mmsd->mmsd_service_proxy_watch_id) { + g_debug ("Listening for new MMS messages"); + } else { + g_warning ("Failed to connect 'MessageAdded' signal"); + } + + /* + * There is a race condition where if mmsd loads after purple-mm-sms, + * the dbus is not set up in time for it to do this proxy call. I am sleeping + * for 1 second here to fix that. + */ + sleep(1); + + g_dbus_proxy_call (mmsd->service_proxy, + "GetMessages", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback)chatty_mm_mmsd_get_all_mms_cb, + self); + +} + +static void +chatty_mm_mmsd_get_manager_cb (GObject *manager, + GAsyncResult *res, + gpointer user_data) +{ + mmsd->manager_proxy = g_dbus_proxy_new_finish (res, NULL); + g_debug ("Got MMSD Manager"); +} + +static void +mmsd_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + ChattyMmAccount *self = user_data; + g_assert (G_IS_DBUS_CONNECTION (connection)); + mmsd->connection = connection; + + + g_debug ("MMSD appeared"); + + g_dbus_proxy_new (mmsd->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + name, + "/org/ofono/mms", + "org.ofono.mms.Manager", + NULL, + chatty_mm_mmsd_get_manager_cb, + self); + + g_dbus_proxy_new (mmsd->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + name, + "/org/ofono/mms/modemmanager", + "org.ofono.mms.Service", + NULL, + chatty_mm_mmsd_get_service_cb, + self); + +} + +static void +mmsd_vanished_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_assert (G_IS_DBUS_CONNECTION (connection)); + + g_debug ("MMSD vanished"); + if (G_IS_OBJECT (mmsd->service_proxy)) { + g_object_unref (mmsd->service_proxy); + } + if (G_IS_OBJECT (mmsd->manager_proxy)) { + g_object_unref (mmsd->manager_proxy); + } + + if (G_IS_DBUS_CONNECTION(mmsd->connection)) { + g_dbus_connection_signal_unsubscribe (mmsd->connection, + mmsd->mmsd_service_proxy_watch_id); + + g_dbus_connection_unregister_object (mmsd->connection, + mmsd->mmsd_watch_id); + } +} + +static void +chatty_mm_mmsd_bearer_handler_error_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + guint error; + //ChattyMmAccount *self = user_data; + //GList *bearer_list; + //MMBearer *modem_bearer; + //gboolean bearer_connected; + + g_variant_get (parameters, "(h)", &error); + + /* + * Note: If you get a bearer handler error other than + * MMSD_MM_MODEM_INTERFACE_DISCONNECTED, + * you must fix the problem THEN + * restart mmsd after the problem is fixed. + */ + switch(error) { + case MMSD_MM_MODEM_MMSC_MISCONFIGURED: + g_warning("Bearer Handler emitted an error, the mmsc is not configured right."); + break; + case MMSD_MM_MODEM_INCORRECT_APN_CONNECTED: + g_warning("Bearer Handler emitted an error, the APN set in mmsd's settings does not match any connected APNs"); + break; + case MMSD_MM_MODEM_NO_BEARERS_ACTIVE: + g_warning("Bearer Handler emitted an error, no bearers are active"); + break; + case MMSD_MM_MODEM_INTERFACE_DISCONNECTED: + g_debug("Bearer Handler emitted an error, the MMS bearer is disconnected"); + break; + default: + g_debug("Bearer Handler emitted an error, but Chatty does not know how to handle it"); + break; + } +} + +static void +chatty_mm_mmsd_get_modemmanager_cb (GObject *simple, + GAsyncResult *res, + gpointer user_data) +{ + ChattyMmAccount *self = user_data; + g_autoptr(GError) error = NULL; + + mmsd->modemmanager_proxy = g_dbus_proxy_new_finish (res, &error); + + mmsd->modemmanager_bearer_handler_watch_id = g_dbus_connection_signal_subscribe (mmsd->modemmanager_connection, + "org.ofono.mms", + "org.ofono.mms.ModemManager", + "BearerHandlerError", + "/org/ofono/mms", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + (GDBusSignalCallback)chatty_mm_mmsd_bearer_handler_error_cb, + self, + NULL); + + if (error != NULL) + { + // Report error to user, and free error + g_warning ("Error in Proxy call: %s\n", error->message); + } else { + g_debug ("Connected to mmsd ModemManager Plugin"); + /* + * When I start up, I am making a lot of proxy calls to mmsd + * This is ensuring there is no race conditions on them. + */ + sleep(3); + mmsd->bearer_connected_signal_connected = FALSE; + g_dbus_proxy_call_sync (mmsd->modemmanager_proxy, + "ProcessMessageQueue", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL); + if (mmsd->mms_waiting) { + g_debug("There are MMS waiting for you!"); + mmsd->mms_waiting = FALSE; + g_debug("Retrieving the SMS messages..."); + //TODO: Integrate looking for SMS Messages into Chatty + } + } +} + +static void +modemmanager_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + ChattyMmAccount *self = user_data; + g_assert (G_IS_DBUS_CONNECTION (connection)); + mmsd->modemmanager_connection = connection; + + + g_debug ("MMSD ModemManager plugin appeared"); + + g_dbus_proxy_new (mmsd->modemmanager_connection, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + name, + "/org/ofono/mms", + "org.ofono.mms.ModemManager", + NULL, + chatty_mm_mmsd_get_modemmanager_cb, + self); + +} + +static void +modemmanager_vanished_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_assert (G_IS_DBUS_CONNECTION (connection)); + + g_debug ("MMSD ModemManager Plugin vanished"); + + if (G_IS_OBJECT (mmsd->modemmanager_proxy)) { + g_object_unref (mmsd->modemmanager_proxy); + } + + if (G_IS_DBUS_CONNECTION(mmsd->modemmanager_connection)) { + g_dbus_connection_unregister_object (mmsd->modemmanager_connection, + mmsd->modemmanager_watch_id); + + g_dbus_connection_unregister_object (mmsd->modemmanager_connection, + mmsd->modemmanager_bearer_handler_watch_id); + } +} + +//TODO: Trying to use ChattyMMAccount caused compilation issues. I should fix this sometime. +void +chatty_mm_mmsd_load (gpointer user_data, + MMObject *mm_object) +{ + /* + * TODO: This really only supports one modem. That may need + * to be fixed in the future. + */ + ChattyMmAccount *self = user_data; + const gchar *const *modem_number_ref; + mmsd = g_try_new0(struct chatty_mm_mmsd_data, 1); + mmsd->mms_waiting = FALSE; + mmsd->mm_object = mm_object; + mmsd->modem = mm_object_get_modem (MM_OBJECT (mm_object)); + /* Figure out what number the modem is on. */ + modem_number_ref = mm_modem_get_own_numbers (mmsd->modem); + if (modem_number_ref != NULL) { + mmsd->modem_number = g_strdup (*modem_number_ref); + mmsd->modem_number = format_number_e164 (mmsd->modem_number); + g_debug ("modem_number: %s", mmsd->modem_number); + } else { + g_warning ("Your SIM or Modem does not support modem manger's number! Please file a bug report"); + mmsd->modem_number = g_strdup ("+1XXXXXXXXXX"); + g_debug ("Making Dummy modem number: %s", mmsd->modem_number); + } + + mmsd->mmsd_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + "org.ofono.mms", + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + (GBusNameAppearedCallback)mmsd_appeared_cb, + (GBusNameVanishedCallback)mmsd_vanished_cb, + self, NULL); + + + +/* + * In order to integrate modemmanager plugin into Modem Manager, the dbus has to talk + * 2.0. The rest of mmsd is dbus 1.0. So the "cleanest" solution (without rewriting + * all of mmsd) is to have two seperate busses. + * + */ + + mmsd->modemmanager_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + "org.ofono.mms.ModemManager", + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + (GBusNameAppearedCallback)modemmanager_appeared_cb, + (GBusNameVanishedCallback)modemmanager_vanished_cb, + self, NULL); + +} + +void +chatty_mm_mmsd_close (void) +{ + mmsd->mm_object = NULL; + mmsd->modem = NULL; + g_free(mmsd->modem_number); + if (mmsd->bearer_connected_signal_connected == TRUE) { + g_signal_handler_disconnect (mmsd->modem_bearer, + mmsd->modemmanager_bearer_connected_signal_id); + mmsd->modem_bearer = NULL; + mmsd->bearer_connected_signal_connected = FALSE; + } + + if (G_IS_DBUS_CONNECTION(mmsd->connection)) { + g_debug("Removing any active MMSD connections"); + mmsd_vanished_cb (mmsd->connection, NULL, NULL); + } + g_debug("Unwatching MMSD"); + g_bus_unwatch_name(mmsd->mmsd_watch_id); + if (G_IS_DBUS_CONNECTION(mmsd->modemmanager_connection)) { + g_debug("Removing any active MMSD MM Plugin connections"); + modemmanager_vanished_cb(mmsd->modemmanager_connection, NULL, NULL); + } + g_debug("Unwatching MMSD MM Plugin"); + g_bus_unwatch_name(mmsd->modemmanager_watch_id); + +} diff --git a/src/users/chatty-mmsd.h b/src/users/chatty-mmsd.h new file mode 100644 index 0000000..a827d8c --- /dev/null +++ b/src/users/chatty-mmsd.h @@ -0,0 +1,40 @@ +/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ +/* chatty-mmsd.c + * + * Copyright 2020, 2021 Purism SPC, kop316 + * + * Author(s): + * Mohammed Sadiq + * kop316 + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include +#include + +#include "chatty-enums.h" +#include "chatty-chat.h" +#include "chatty-account.h" +#include "chatty-settings.h" +#include "chatty-account.h" +#include "chatty-window.h" +#include "chatty-history.h" +#include "chatty-mm-chat.h" +#include "chatty-utils.h" +#include "chatty-mm-account.h" + +G_BEGIN_DECLS + +void chatty_mm_mmsd_load (gpointer user_data, + MMObject *mm_object); + +gboolean chatty_mm_mmsd_push_notify_async (gpointer user_data, + MMSms *sms); + +void chatty_mm_mmsd_close (void); + +G_END_DECLS -- 2.30.0