#include #include #include #include #include #include "delta-connection.h" #include "libdelta.h" #include "util.h" void delta_recv_im(DeltaConnectionData *conn, uint32_t msg_id); void *imap_thread_func(void *delta_connection_data) { DeltaConnectionData *conn = (DeltaConnectionData *)delta_connection_data; g_assert(conn != NULL); dc_context_t *mailbox = conn->mailbox; g_assert(mailbox != NULL); while (conn->runthreads) { dc_perform_imap_jobs(mailbox); dc_perform_imap_fetch(mailbox); dc_perform_imap_idle(mailbox); } return NULL; } void *smtp_thread_func(void *delta_connection_data) { DeltaConnectionData *conn = (DeltaConnectionData *)delta_connection_data; g_assert(conn != NULL); dc_context_t *mailbox = conn->mailbox; g_assert(mailbox != NULL); while (conn->runthreads) { dc_perform_smtp_jobs(mailbox); dc_perform_smtp_idle(mailbox); } return NULL; } 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; // Used by delta_process_http_get char *http_url; char *http_response; pthread_cond_t *http_wait; } 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); printf("*** 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; } void delta_process_http_get_cb( PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message ) { UNUSED(url_data); ProcessRequest *pr = (ProcessRequest *)user_data; g_assert(pr != NULL); g_assert(pr->http_wait != NULL); if (len == 0) { printf("Failed to GET %s: %s\n", pr->http_url, error_message); pr->http_response = NULL; goto out; } pr->http_response = g_malloc(len); g_assert(pr->http_response != NULL); strncpy(pr->http_response, url_text, len); out: pthread_cond_broadcast(pr->http_wait); return; } gboolean delta_process_http_get(void *data) { ProcessRequest *pr = (ProcessRequest *)data; g_assert(pr != NULL); g_assert(pr->http_url != NULL); purple_util_fetch_url( pr->http_url, TRUE, NULL, TRUE, delta_process_http_get_cb, data ); 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 uintptr_t my_delta_handler(dc_context_t* mailbox, int event, uintptr_t data1, uintptr_t data2) { DeltaConnectionData *conn = (DeltaConnectionData *)dc_get_userdata(mailbox); g_assert(conn != NULL); ProcessRequest *pr = NULL; uintptr_t out = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; printf("my_delta_handler(mailbox, %d, %lu, %lu)\n", event, data1, data2); switch (event) { case DC_EVENT_SMTP_MESSAGE_SENT: case DC_EVENT_IMAP_CONNECTED: case DC_EVENT_SMTP_CONNECTED: case DC_EVENT_INFO: printf("INFO: %s\n", (char *)data2); break; case DC_EVENT_WARNING: printf("WARNING: %s\n", (char *)data2); break; case DC_EVENT_ERROR: case DC_EVENT_ERROR_NETWORK: printf("ERROR: %d: %s\n", (int)data1, (char *)data2); 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)data2; purple_timeout_add(0, delta_process_incoming_message, pr); break; // These are all to do with sending & receiving messages. The real meat of // the event loop case DC_EVENT_MSG_DELIVERED: case DC_EVENT_MSG_READ: case DC_EVENT_CHAT_MODIFIED: case DC_EVENT_CONTACTS_CHANGED: debug("TODO!\n"); break; case DC_EVENT_CONFIGURE_PROGRESS: pr = delta_build_process_request(conn); pr->connection_state = (int)data1; purple_timeout_add(0, delta_process_connection_state, pr); break; case DC_EVENT_HTTP_GET: printf("HTTP GET requested: %s\n", (char *)data1); pthread_mutex_lock(&mutex); pr = delta_build_process_request(conn); g_assert(pr != NULL); pr->http_url = (char *)data1; pr->http_wait = &cond; purple_timeout_add(0, delta_process_http_get, pr); // Wait patiently for the HTTP GET to complete pthread_cond_wait(pr->http_wait, &mutex); out = (uintptr_t)pr->http_response; pthread_mutex_unlock(&mutex); pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); g_free(pr); break; case DC_EVENT_IS_OFFLINE: if ( conn->pc == NULL || !PURPLE_CONNECTION_IS_CONNECTED(conn->pc) ) { debug("Telling Delta we are offline\n"); out = 1; } else { debug("Telling Delta we are online\n"); } break; case DC_EVENT_GET_STRING: break; default: printf("Unknown event: %d\n", event); } return out; } 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_maybe_network(conn->mailbox); // TODO: correctly handle join failing if (pthread_join(conn->imap_thread, NULL) != 0) { debug("joining imap thread failed!\n"); } if (pthread_join(conn->smtp_thread, NULL) != 0) { debug("joining smtp thread failed!\n"); } dc_stop_ongoing_process(conn->mailbox); dc_close(conn->mailbox); 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 = dc_context_new(my_delta_handler, conn, NULL); g_snprintf( dbname, 1024, "%s%sdelta_db-%s", purple_user_dir(), G_DIR_SEPARATOR_S, acct->username ); if (!dc_open(mailbox, dbname, NULL)) { debug("dc_open returned false...?\n"); } conn->mailbox = mailbox; _transpose_config(mailbox, acct); conn->runthreads = 1; pthread_create(&conn->imap_thread, NULL, imap_thread_func, conn); pthread_create(&conn->smtp_thread, NULL, smtp_thread_func, conn); dc_configure(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); char *unescaped_message = purple_unescape_html(message); g_assert(unescaped_message != NULL); dc_send_text_msg(mailbox, chat_id, unescaped_message); g_free(unescaped_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); 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) { debug("Unknown contact! FIXME!\n"); goto out; } char *who = dc_contact_get_addr(contact); serv_got_im(pc, who, text, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_RAW, timestamp); dc_markseen_msgs(mailbox, &msg_id, 1); g_free(who); out: g_free(text); dc_msg_unref(msg); }