#include #include #include #include #include #include #include #include #include #include "delta-connection.h" #include "libdelta.h" void delta_recv_im(DeltaConnectionData *conn, uint32_t msg_id); void _transpose_config(dc_context_t *mailbox, PurpleAccount *acct) { const char *addr = acct->username; const char *display = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_DISPLAY_NAME, NULL); const char *imap_host = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_IMAP_SERVER_HOST, NULL); const char *imap_user = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_IMAP_USER, NULL); const char *imap_pass = purple_account_get_password(acct); const char *imap_port = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_IMAP_SERVER_PORT, NULL); const char *smtp_host = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_SMTP_SERVER_HOST, NULL); const char *smtp_user = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_SMTP_USER, NULL); const char *smtp_pass = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_SMTP_PASS, NULL); const char *smtp_port = purple_account_get_string(acct, PLUGIN_ACCOUNT_OPT_SMTP_SERVER_PORT, NULL); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_ADDR, addr); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_DISPLAY_NAME, display); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_IMAP_SERVER_HOST, imap_host); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_IMAP_USER, imap_user); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_IMAP_PASS, imap_pass); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_IMAP_SERVER_PORT, imap_port); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_SMTP_SERVER_HOST, smtp_host); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_SMTP_USER, smtp_user); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_SMTP_PASS, smtp_pass); dc_set_config(mailbox, PLUGIN_ACCOUNT_OPT_SMTP_SERVER_PORT, smtp_port); } typedef struct { DeltaConnectionData *conn; // Used by delta_process_incoming_message uint32_t msg_id; // Used by delta_process_connection_state int connection_state; } ProcessRequest; gboolean delta_process_incoming_message(void *data) { ProcessRequest *pr = (ProcessRequest *)data; g_assert(pr != NULL); g_assert(pr->conn != NULL); delta_recv_im(pr->conn, pr->msg_id); g_free(data); return FALSE; } gboolean delta_process_connection_state(void *data) { ProcessRequest *pr = (ProcessRequest *)data; g_assert(pr != NULL); g_assert(pr->conn != NULL); purple_connection_update_progress( pr->conn->pc, "Connecting...", pr->connection_state, MAX_DELTA_CONFIGURE ); if (pr->connection_state == MAX_DELTA_CONFIGURE) { purple_connection_set_state(pr->conn->pc, PURPLE_CONNECTED); } g_free(data); return FALSE; } gboolean delta_process_fresh_messages(void *data) { ProcessRequest *pr = (ProcessRequest *)data; g_assert(pr != NULL); g_assert(pr->conn != NULL); g_assert(pr->conn->mailbox != NULL); // Spot any messages received while offline dc_array_t *fresh_msgs = dc_get_fresh_msgs(pr->conn->mailbox); size_t fresh_count = dc_array_get_cnt(fresh_msgs); purple_debug_info(PLUGIN_ID, "fresh_count: %zu\n", fresh_count); for(size_t i = 0; i < fresh_count; i++) { delta_recv_im(pr->conn, dc_array_get_id(fresh_msgs, i)); } g_free(fresh_msgs); return FALSE; } ProcessRequest * delta_build_process_request(DeltaConnectionData *conn) { g_assert(conn != NULL); ProcessRequest *pr = g_malloc(sizeof(ProcessRequest)); g_assert(pr != NULL); pr->conn = conn; return pr; } // Do not call any libpurple or delta functions in here, as it is not // thread-safe and events may be dispatched from any delta thread. Use // purple_timeout_add(0, callback, data) to run on the main thread instead void * delta_event_handler(void *context) { DeltaConnectionData *conn = (DeltaConnectionData *)context; g_assert(conn != NULL); dc_context_t *mailbox = conn->mailbox; dc_event_emitter_t* emitter = dc_get_event_emitter(mailbox); dc_event_t* event; // FIXME: do we still need runthreads? while (conn->runthreads && (event = dc_get_next_event(emitter)) != NULL) { ProcessRequest *pr = NULL; int event_id = dc_event_get_id(event); purple_debug_info(PLUGIN_ID, "Event %d received from Delta.\n", event_id); switch (event_id) { case DC_EVENT_SMTP_MESSAGE_SENT: case DC_EVENT_IMAP_CONNECTED: case DC_EVENT_SMTP_CONNECTED: case DC_EVENT_IMAP_MESSAGE_DELETED: case DC_EVENT_IMAP_MESSAGE_MOVED: case DC_EVENT_INFO: { char *info = dc_event_get_data2_str(event); purple_debug_info(PLUGIN_ID, "INFO from Delta: %s\n", info); dc_str_unref(info); break; } case DC_EVENT_WARNING: { char *warn = dc_event_get_data2_str(event); purple_debug_info(PLUGIN_ID, "WARNING from Delta: %s\n", warn); dc_str_unref(warn); break; } case DC_EVENT_ERROR: case DC_EVENT_ERROR_NETWORK: { int errcode = dc_event_get_data1_int(event); char *err = dc_event_get_data2_str(event); purple_debug_info(PLUGIN_ID, "ERROR from Delta: %d: %s\n", errcode, err); dc_str_unref(err); break; } case DC_EVENT_MSGS_CHANGED: pr = delta_build_process_request(conn); purple_timeout_add(0, delta_process_fresh_messages, pr); break; case DC_EVENT_INCOMING_MSG: // data1 is chat_id, which we don't seem to need yet. // TODO: It may be needed for group chats pr = delta_build_process_request(conn); pr->msg_id = (uint32_t)dc_event_get_data2_int(event); purple_timeout_add(0, delta_process_incoming_message, pr); break; // Things left to do case DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED: case DC_EVENT_NEW_BLOB_FILE: case DC_EVENT_DELETED_BLOB_FILE: case DC_EVENT_MSG_DELIVERED: case DC_EVENT_MSG_READ: case DC_EVENT_MSG_FAILED: case DC_EVENT_CHAT_MODIFIED: case DC_EVENT_CONTACTS_CHANGED: case DC_EVENT_ERROR_SELF_NOT_IN_GROUP: case DC_EVENT_IMEX_FILE_WRITTEN: case DC_EVENT_IMEX_PROGRESS: case DC_EVENT_LOCATION_CHANGED: case DC_EVENT_MSGS_NOTICED: case DC_EVENT_SECUREJOIN_INVITER_PROGRESS: case DC_EVENT_SECUREJOIN_JOINER_PROGRESS: purple_debug_info(PLUGIN_ID, "Event %d is TODO\n", event_id); break; case DC_EVENT_CONFIGURE_PROGRESS: pr = delta_build_process_request(conn); pr->connection_state = dc_event_get_data1_int(event); purple_timeout_add(0, delta_process_connection_state, pr); break; default: purple_debug_info(PLUGIN_ID, "Unknown Delta event: %d\n", event_id); } dc_event_unref(event); } dc_event_emitter_unref(emitter); return NULL; } void delta_connection_new(PurpleConnection *pc) { DeltaConnectionData *conn = NULL; g_assert(purple_connection_get_protocol_data(pc) == NULL); conn = g_new0(DeltaConnectionData, 1); conn->pc = pc; purple_connection_set_protocol_data(pc, conn); } void delta_connection_free(PurpleConnection *pc) { DeltaConnectionData *conn = purple_connection_get_protocol_data(pc); g_assert(conn != NULL); conn->runthreads = 0; if (conn->mailbox != NULL) { dc_stop_ongoing_process(conn->mailbox); dc_stop_io(conn->mailbox); // TODO: correctly handle join failing purple_debug_info(PLUGIN_ID, "Joining event thread\n"); if (pthread_join(conn->event_thread, NULL) != 0) { purple_debug_info(PLUGIN_ID, "joining event thread failed\n"); } dc_context_unref(conn->mailbox); } purple_connection_set_protocol_data(pc, NULL); // TODO: free resources as they are added to DeltaConnectionData conn->pc = NULL; conn->mailbox = NULL; g_free(conn); } void delta_connection_start_login(PurpleConnection *pc) { char dbname[1024]; PurpleAccount *acct = pc->account; DeltaConnectionData *conn = purple_connection_get_protocol_data(pc); dc_context_t *mailbox = NULL; g_snprintf( dbname, 1024, "%s%sdelta_db-%s", purple_user_dir(), G_DIR_SEPARATOR_S, acct->username ); mailbox = dc_context_new(PLUGIN_ID, dbname, NULL); conn->mailbox = mailbox; _transpose_config(mailbox, acct); conn->runthreads = 1; pthread_create(&conn->event_thread, NULL, delta_event_handler, conn); dc_configure(mailbox); dc_start_io(mailbox); dc_maybe_network(mailbox); return; } int delta_send_im(PurpleConnection *pc, const char *who, const char *message, PurpleMessageFlags flags) { UNUSED(flags); DeltaConnectionData *conn = (DeltaConnectionData *)purple_connection_get_protocol_data(pc); g_assert(conn != NULL); dc_context_t *mailbox = conn->mailbox; g_assert(mailbox != NULL); uint32_t contact_id = dc_create_contact(mailbox, NULL, who); uint32_t chat_id = dc_create_chat_by_contact_id(mailbox, contact_id); GData *attrs; const char *msg_ptr, *start, *end; msg_ptr = message; // Send each image included in the message. while (purple_markup_find_tag("img", msg_ptr, &start, &end, &attrs) == TRUE) { char *id_str = g_datalist_id_get_data(&attrs, g_quark_from_string("id")); purple_debug_info(PLUGIN_ID, "In a loop, got %s\n", id_str); msg_ptr = end + 1; if (id_str == NULL || strlen(id_str) == 0) { g_datalist_clear(&attrs); continue; } int id = atoi(id_str); g_datalist_clear(&attrs); if (id <= 0) { continue; } GError *err = NULL; char *tempdir = g_dir_make_tmp(NULL, &err); if (err != NULL) { purple_debug_info(PLUGIN_ID, "Couldn't get a temporary dir for image %d: %s", id, err->message); g_free(err); continue; } PurpleStoredImage *img = purple_imgstore_find_by_id(id); const char *filename = purple_imgstore_get_filename(img); const char *extension = purple_imgstore_get_extension(img); gconstpointer data = purple_imgstore_get_data(img); char *path = g_strdup_printf("%s/%s", tempdir, filename); g_file_set_contents(path, data, purple_imgstore_get_size(img), &err); if (err != NULL) { purple_debug_info(PLUGIN_ID, "failed to write %s to temporary file: %s\n", filename, err->message); g_free(err); goto next; } purple_debug_info(PLUGIN_ID, "Sending image %s from imgstore: %d\n", filename, id); dc_msg_t *img_msg = dc_msg_new(mailbox, DC_MSG_IMAGE); dc_msg_set_file(img_msg, path, extension); dc_send_msg(mailbox, chat_id, img_msg); dc_msg_unref(img_msg); next: remove(path); remove(tempdir); g_free(path); } // Send any text left char *stripped_message = purple_markup_strip_html(message); g_assert(stripped_message != NULL); if (strlen(stripped_message) > 0) { dc_send_text_msg(mailbox, chat_id, stripped_message); } g_free(stripped_message); return 1; // success; echo the message to the chat window } void delta_recv_im(DeltaConnectionData *conn, uint32_t msg_id) { dc_context_t *mailbox = conn->mailbox; g_assert(mailbox != NULL); PurpleConnection *pc = conn->pc; g_assert(pc != NULL); dc_msg_t* msg = dc_get_msg(mailbox, msg_id); int viewtype = dc_msg_get_viewtype(msg); time_t timestamp = dc_msg_get_timestamp(msg); char *text = dc_msg_get_text(msg); uint32_t contact_id = dc_msg_get_from_id(msg); dc_contact_t *contact = dc_get_contact(mailbox, contact_id); if (contact == NULL) { purple_debug_info(PLUGIN_ID, "Receiving IM: unknown contact: %d\n", contact_id); goto out; } char *who = dc_contact_get_addr(contact); int flags = PURPLE_MESSAGE_RECV; switch(viewtype) { case DC_MSG_GIF: case DC_MSG_IMAGE: flags = flags | PURPLE_MESSAGE_IMAGES; break; case DC_MSG_TEXT: flags = flags | PURPLE_MESSAGE_RAW; break; case DC_MSG_VIDEO: // Pidgin only supports these as files for download case DC_MSG_FILE: case DC_MSG_AUDIO: // Sound to play case DC_MSG_VOICE: break; default: purple_debug_info(PLUGIN_ID, "Message %d: unknown message type: %d\n", msg_id, viewtype); } int image_id = 0; if ((flags & PURPLE_MESSAGE_IMAGES) > 0) { char *filename = dc_msg_get_file(msg); gchar *data; gsize length; GError *err = NULL; g_file_get_contents(filename, &data, &length, &err); if (err != NULL) { purple_debug_info(PLUGIN_ID, "Failed to read image %s: %s\n", filename, err->message); g_error_free(err); goto out; } image_id = purple_imgstore_add_with_id(data, length, filename); text = g_strdup_printf("", image_id); } serv_got_im(pc, who, text, flags, timestamp); if (image_id > 0) { purple_imgstore_unref_by_id(image_id); } dc_markseen_msgs(mailbox, &msg_id, 1); g_free(who); out: g_free(text); dc_msg_unref(msg); }