mirror of
https://github.com/deltachat/deltachat-core.git
synced 2025-10-05 10:39:27 +02:00
927 lines
30 KiB
C
927 lines
30 KiB
C
/*******************************************************************************
|
|
*
|
|
* Delta Chat Core
|
|
* Copyright (C) 2017 Björn Petersen
|
|
* Contact: r10s@b44t.com, http://b44t.com
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 3 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see http://www.gnu.org/licenses/ .
|
|
*
|
|
******************************************************************************/
|
|
|
|
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include "dc_context.h"
|
|
#include "dc_loginparam.h"
|
|
#include "dc_job.h"
|
|
#include "dc_imap.h"
|
|
#include "dc_smtp.h"
|
|
#include "dc_mimefactory.h"
|
|
|
|
|
|
/*******************************************************************************
|
|
* IMAP-jobs
|
|
******************************************************************************/
|
|
|
|
|
|
static int connect_to_imap(dc_context_t* context, dc_job_t* job /*may be NULL if the function is called directly!*/)
|
|
{
|
|
#define NOT_CONNECTED 0
|
|
#define ALREADY_CONNECTED 1
|
|
#define JUST_CONNECTED 2
|
|
int ret_connected = NOT_CONNECTED;
|
|
dc_loginparam_t* param = dc_loginparam_new();
|
|
|
|
if( context == NULL || context->magic != DC_CONTEXT_MAGIC || context->imap == NULL ) {
|
|
dc_log_warning(context, 0, "Cannot connect to IMAP: Bad parameters.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if( dc_imap_is_connected(context->imap) ) {
|
|
ret_connected = ALREADY_CONNECTED;
|
|
goto cleanup;
|
|
}
|
|
|
|
if( dc_sqlite3_get_config_int(context->sql, "configured", 0) == 0 ) {
|
|
dc_log_warning(context, 0, "Not configured, cannot connect."); // this is no error, connect() is called eg. when the screen is switched on, it's okay if the caller does not check all circumstances here
|
|
goto cleanup;
|
|
}
|
|
|
|
dc_loginparam_read(param, context->sql, "configured_" /*the trailing underscore is correct*/);
|
|
|
|
if( !dc_imap_connect(context->imap, param) ) {
|
|
dc_job_try_again_later(job, DC_STANDARD_DELAY);
|
|
goto cleanup;
|
|
}
|
|
|
|
ret_connected = JUST_CONNECTED;
|
|
|
|
cleanup:
|
|
dc_loginparam_unref(param);
|
|
return ret_connected;
|
|
}
|
|
|
|
|
|
static void dc_job_do_DC_JOB_SEND_MSG_TO_IMAP(dc_context_t* context, dc_job_t* job)
|
|
{
|
|
dc_mimefactory_t mimefactory;
|
|
char* server_folder = NULL;
|
|
uint32_t server_uid = 0;
|
|
|
|
dc_mimefactory_init(&mimefactory, context);
|
|
|
|
/* connect to IMAP-server */
|
|
if( !dc_imap_is_connected(context->imap) ) {
|
|
connect_to_imap(context, NULL);
|
|
if( !dc_imap_is_connected(context->imap) ) {
|
|
dc_job_try_again_later(job, DC_STANDARD_DELAY);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* create message */
|
|
if( dc_mimefactory_load_msg(&mimefactory, job->foreign_id)==0
|
|
|| mimefactory.from_addr == NULL ) {
|
|
goto cleanup; /* should not happen as we've sent the message to the SMTP server before */
|
|
}
|
|
|
|
if( !dc_mimefactory_render(&mimefactory) ) {
|
|
goto cleanup; /* should not happen as we've sent the message to the SMTP server before */
|
|
}
|
|
|
|
if( !dc_imap_append_msg(context->imap, mimefactory.msg->timestamp, mimefactory.out->str, mimefactory.out->len, &server_folder, &server_uid) ) {
|
|
dc_job_try_again_later(job, DC_AT_ONCE);
|
|
goto cleanup;
|
|
}
|
|
else {
|
|
dc_update_server_uid(context, mimefactory.msg->rfc724_mid, server_folder, server_uid);
|
|
}
|
|
|
|
cleanup:
|
|
dc_mimefactory_empty(&mimefactory);
|
|
free(server_folder);
|
|
}
|
|
|
|
|
|
static void dc_job_do_DC_JOB_DELETE_MSG_ON_IMAP(dc_context_t* context, dc_job_t* job)
|
|
{
|
|
int delete_from_server = 1;
|
|
dc_msg_t* msg = dc_msg_new();
|
|
sqlite3_stmt* stmt = NULL;
|
|
|
|
if( !dc_msg_load_from_db(msg, context, job->foreign_id)
|
|
|| msg->rfc724_mid == NULL || msg->rfc724_mid[0] == 0 /* eg. device messages have no Message-ID */ ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if( dc_rfc724_mid_cnt(context, msg->rfc724_mid) != 1 ) {
|
|
dc_log_info(context, 0, "The message is deleted from the server when all parts are deleted.");
|
|
delete_from_server = 0;
|
|
}
|
|
|
|
/* if this is the last existing part of the message, we delete the message from the server */
|
|
if( delete_from_server )
|
|
{
|
|
if( !dc_imap_is_connected(context->imap) ) {
|
|
connect_to_imap(context, NULL);
|
|
if( !dc_imap_is_connected(context->imap) ) {
|
|
dc_job_try_again_later(job, DC_STANDARD_DELAY);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if( !dc_imap_delete_msg(context->imap, msg->rfc724_mid, msg->server_folder, msg->server_uid) )
|
|
{
|
|
dc_job_try_again_later(job, DC_AT_ONCE);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* we delete the database entry ...
|
|
- if the message is successfully removed from the server
|
|
- or if there are other parts of the message in the database (in this case we have not deleted if from the server)
|
|
(As long as the message is not removed from the IMAP-server, we need at least one database entry to avoid a re-download) */
|
|
stmt = dc_sqlite3_prepare(context->sql,
|
|
"DELETE FROM msgs WHERE id=?;");
|
|
sqlite3_bind_int(stmt, 1, msg->id);
|
|
sqlite3_step(stmt);
|
|
sqlite3_finalize(stmt);
|
|
stmt = NULL;
|
|
|
|
stmt = dc_sqlite3_prepare(context->sql,
|
|
"DELETE FROM msgs_mdns WHERE msg_id=?;");
|
|
sqlite3_bind_int(stmt, 1, msg->id);
|
|
sqlite3_step(stmt);
|
|
sqlite3_finalize(stmt);
|
|
stmt = NULL;
|
|
|
|
char* pathNfilename = dc_param_get(msg->param, DC_PARAM_FILE, NULL);
|
|
if( pathNfilename ) {
|
|
if( strncmp(context->blobdir, pathNfilename, strlen(context->blobdir))==0 )
|
|
{
|
|
char* strLikeFilename = dc_mprintf("%%f=%s%%", pathNfilename);
|
|
stmt = dc_sqlite3_prepare(context->sql,
|
|
"SELECT id FROM msgs WHERE type!=? AND param LIKE ?;"); /* if this gets too slow, an index over "type" should help. */
|
|
sqlite3_bind_int (stmt, 1, DC_MSG_TEXT);
|
|
sqlite3_bind_text(stmt, 2, strLikeFilename, -1, SQLITE_STATIC);
|
|
int file_used_by_other_msgs = (sqlite3_step(stmt)==SQLITE_ROW)? 1 : 0;
|
|
free(strLikeFilename);
|
|
sqlite3_finalize(stmt);
|
|
stmt = NULL;
|
|
|
|
if( !file_used_by_other_msgs )
|
|
{
|
|
dc_delete_file(pathNfilename, context);
|
|
|
|
char* increation_file = dc_mprintf("%s.increation", pathNfilename);
|
|
dc_delete_file(increation_file, context);
|
|
free(increation_file);
|
|
|
|
char* filenameOnly = dc_get_filename(pathNfilename);
|
|
if( msg->type==DC_MSG_VOICE ) {
|
|
char* waveform_file = dc_mprintf("%s/%s.waveform", context->blobdir, filenameOnly);
|
|
dc_delete_file(waveform_file, context);
|
|
free(waveform_file);
|
|
}
|
|
else if( msg->type==DC_MSG_VIDEO ) {
|
|
char* preview_file = dc_mprintf("%s/%s-preview.jpg", context->blobdir, filenameOnly);
|
|
dc_delete_file(preview_file, context);
|
|
free(preview_file);
|
|
}
|
|
free(filenameOnly);
|
|
}
|
|
}
|
|
free(pathNfilename);
|
|
}
|
|
|
|
cleanup:
|
|
sqlite3_finalize(stmt);
|
|
dc_msg_unref(msg);
|
|
}
|
|
|
|
|
|
static void dc_job_do_DC_JOB_MARKSEEN_MSG_ON_IMAP(dc_context_t* context, dc_job_t* job)
|
|
{
|
|
dc_msg_t* msg = dc_msg_new();
|
|
char* new_server_folder = NULL;
|
|
uint32_t new_server_uid = 0;
|
|
int in_ms_flags = 0;
|
|
int out_ms_flags = 0;
|
|
|
|
if( !dc_imap_is_connected(context->imap) ) {
|
|
connect_to_imap(context, NULL);
|
|
if( !dc_imap_is_connected(context->imap) ) {
|
|
dc_job_try_again_later(job, DC_STANDARD_DELAY);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if( !dc_msg_load_from_db(msg, context, job->foreign_id) ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* add an additional job for sending the MDN (here in a thread for fast ui resonses) (an extra job as the MDN has a lower priority) */
|
|
if( dc_param_get_int(msg->param, DC_PARAM_WANTS_MDN, 0) /* DC_PARAM_WANTS_MDN is set only for one part of a multipart-message */
|
|
&& dc_sqlite3_get_config_int(context->sql, "mdns_enabled", DC_MDNS_DEFAULT_ENABLED) ) {
|
|
in_ms_flags |= DC_MS_SET_MDNSent_FLAG;
|
|
}
|
|
|
|
if( msg->is_msgrmsg ) {
|
|
in_ms_flags |= DC_MS_ALSO_MOVE;
|
|
}
|
|
|
|
if( dc_imap_markseen_msg(context->imap, msg->server_folder, msg->server_uid,
|
|
in_ms_flags, &new_server_folder, &new_server_uid, &out_ms_flags) != 0 )
|
|
{
|
|
if( (new_server_folder && new_server_uid) || out_ms_flags&DC_MS_MDNSent_JUST_SET )
|
|
{
|
|
if( new_server_folder && new_server_uid )
|
|
{
|
|
dc_update_server_uid(context, msg->rfc724_mid, new_server_folder, new_server_uid);
|
|
}
|
|
|
|
if( out_ms_flags&DC_MS_MDNSent_JUST_SET )
|
|
{
|
|
dc_job_add(context, DC_JOB_SEND_MDN, msg->id, NULL, 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dc_job_try_again_later(job, DC_AT_ONCE);
|
|
}
|
|
|
|
cleanup:
|
|
dc_msg_unref(msg);
|
|
free(new_server_folder);
|
|
}
|
|
|
|
|
|
static void dc_job_do_DC_JOB_MARKSEEN_MDN_ON_IMAP(dc_context_t* context, dc_job_t* job)
|
|
{
|
|
char* server_folder = dc_param_get (job->param, DC_PARAM_SERVER_FOLDER, NULL);
|
|
uint32_t server_uid = dc_param_get_int(job->param, DC_PARAM_SERVER_UID, 0);
|
|
char* new_server_folder = NULL;
|
|
uint32_t new_server_uid = 0;
|
|
int out_ms_flags = 0;
|
|
|
|
if( !dc_imap_is_connected(context->imap) ) {
|
|
connect_to_imap(context, NULL);
|
|
if( !dc_imap_is_connected(context->imap) ) {
|
|
dc_job_try_again_later(job, DC_STANDARD_DELAY);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if( dc_imap_markseen_msg(context->imap, server_folder, server_uid, DC_MS_ALSO_MOVE, &new_server_folder, &new_server_uid, &out_ms_flags) == 0 ) {
|
|
dc_job_try_again_later(job, DC_AT_ONCE);
|
|
}
|
|
|
|
cleanup:
|
|
free(server_folder);
|
|
free(new_server_folder);
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* SMTP-jobs
|
|
******************************************************************************/
|
|
|
|
|
|
static void mark_as_error(dc_context_t* context, dc_msg_t* msg)
|
|
{
|
|
if( context==NULL || msg==NULL ) {
|
|
return;
|
|
}
|
|
|
|
dc_update_msg_state(context, msg->id, DC_STATE_OUT_ERROR);
|
|
context->cb(context, DC_EVENT_MSGS_CHANGED, msg->chat_id, 0);
|
|
}
|
|
|
|
|
|
static void dc_job_do_DC_JOB_SEND_MSG_TO_SMTP(dc_context_t* context, dc_job_t* job)
|
|
{
|
|
dc_mimefactory_t mimefactory;
|
|
dc_mimefactory_init(&mimefactory, context);
|
|
|
|
/* connect to SMTP server, if not yet done */
|
|
if( !dc_smtp_is_connected(context->smtp) ) {
|
|
dc_loginparam_t* loginparam = dc_loginparam_new();
|
|
dc_loginparam_read(loginparam, context->sql, "configured_");
|
|
int connected = dc_smtp_connect(context->smtp, loginparam);
|
|
dc_loginparam_unref(loginparam);
|
|
if( !connected ) {
|
|
dc_job_try_again_later(job, DC_STANDARD_DELAY);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* load message data */
|
|
if( !dc_mimefactory_load_msg(&mimefactory, job->foreign_id)
|
|
|| mimefactory.from_addr == NULL ) {
|
|
dc_log_warning(context, 0, "Cannot load data to send, maybe the message is deleted in between.");
|
|
goto cleanup; /* no redo, no IMAP - there won't be more recipients next time (as the data does not exist, there is no need in calling mark_as_error()) */
|
|
}
|
|
|
|
/* check if the message is ready (normally, only video files may be delayed this way) */
|
|
if( mimefactory.increation ) {
|
|
dc_log_info(context, 0, "File is in creation, retrying later.");
|
|
dc_job_try_again_later(job, DC_INCREATION_POLL);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* send message - it's okay if there are no recipients, this is a group with only OURSELF; we only upload to IMAP in this case */
|
|
if( clist_count(mimefactory.recipients_addr) > 0 ) {
|
|
if( !dc_mimefactory_render(&mimefactory) ) {
|
|
mark_as_error(context, mimefactory.msg);
|
|
dc_log_error(context, 0, "Empty message."); /* should not happen */
|
|
goto cleanup; /* no redo, no IMAP - there won't be more recipients next time. */
|
|
}
|
|
|
|
/* have we guaranteed encryption but cannot fulfill it for any reason? Do not send the message then.*/
|
|
if( dc_param_get_int(mimefactory.msg->param, DC_PARAM_GUARANTEE_E2EE, 0) && !mimefactory.out_encrypted ) {
|
|
mark_as_error(context, mimefactory.msg);
|
|
dc_log_error(context, 0, "End-to-end-encryption unavailable unexpectedly.");
|
|
goto cleanup; /* unrecoverable */
|
|
}
|
|
|
|
if( !dc_smtp_send_msg(context->smtp, mimefactory.recipients_addr, mimefactory.out->str, mimefactory.out->len) ) {
|
|
dc_smtp_disconnect(context->smtp);
|
|
dc_job_try_again_later(job, DC_AT_ONCE); /* DC_AT_ONCE is only the _initial_ delay, if the second try failes, the delay gets larger */
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* done */
|
|
dc_sqlite3_begin_transaction(context->sql);
|
|
|
|
/* debug print? */
|
|
if( dc_sqlite3_get_config_int(context->sql, "save_eml", 0) ) {
|
|
char* emlname = dc_mprintf("%s/to-smtp-%i.eml", context->blobdir, (int)mimefactory.msg->id);
|
|
FILE* emlfileob = fopen(emlname, "w");
|
|
if( emlfileob ) {
|
|
if( mimefactory.out ) {
|
|
fwrite(mimefactory.out->str, 1, mimefactory.out->len, emlfileob);
|
|
}
|
|
fclose(emlfileob);
|
|
}
|
|
free(emlname);
|
|
}
|
|
|
|
dc_update_msg_state(context, mimefactory.msg->id, DC_STATE_OUT_DELIVERED);
|
|
if( mimefactory.out_encrypted && dc_param_get_int(mimefactory.msg->param, DC_PARAM_GUARANTEE_E2EE, 0)==0 ) {
|
|
dc_param_set_int(mimefactory.msg->param, DC_PARAM_GUARANTEE_E2EE, 1); /* can upgrade to E2EE - fine! */
|
|
dc_msg_save_param_to_disk(mimefactory.msg);
|
|
}
|
|
|
|
if( (context->imap->server_flags&DC_NO_EXTRA_IMAP_UPLOAD)==0
|
|
&& dc_param_get(mimefactory.chat->param, DC_PARAM_SELFTALK, 0)==0
|
|
&& dc_param_get_int(mimefactory.msg->param, DC_PARAM_CMD, 0)!=DC_CMD_SECUREJOIN_MESSAGE ) {
|
|
dc_job_add(context, DC_JOB_SEND_MSG_TO_IMAP, mimefactory.msg->id, NULL, 0); /* send message to IMAP in another job */
|
|
}
|
|
|
|
// TODO: add to keyhistory
|
|
dc_add_to_keyhistory__(context, NULL, 0, NULL, NULL);
|
|
|
|
dc_sqlite3_commit(context->sql);
|
|
|
|
context->cb(context, DC_EVENT_MSG_DELIVERED, mimefactory.msg->chat_id, mimefactory.msg->id);
|
|
|
|
cleanup:
|
|
dc_mimefactory_empty(&mimefactory);
|
|
}
|
|
|
|
|
|
static void dc_job_do_DC_JOB_SEND_MDN(dc_context_t* context, dc_job_t* job)
|
|
{
|
|
dc_mimefactory_t mimefactory;
|
|
dc_mimefactory_init(&mimefactory, context);
|
|
|
|
if( context == NULL || context->magic != DC_CONTEXT_MAGIC || job == NULL ) {
|
|
return;
|
|
}
|
|
|
|
/* connect to SMTP server, if not yet done */
|
|
if( !dc_smtp_is_connected(context->smtp) )
|
|
{
|
|
dc_loginparam_t* loginparam = dc_loginparam_new();
|
|
dc_loginparam_read(loginparam, context->sql, "configured_");
|
|
int connected = dc_smtp_connect(context->smtp, loginparam);
|
|
dc_loginparam_unref(loginparam);
|
|
if( !connected ) {
|
|
dc_job_try_again_later(job, DC_STANDARD_DELAY);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if( !dc_mimefactory_load_mdn(&mimefactory, job->foreign_id)
|
|
|| !dc_mimefactory_render(&mimefactory) ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//char* t1=dc_null_terminate(mimefactory.out->str,mimefactory.out->len);printf("~~~~~MDN~~~~~\n%s\n~~~~~/MDN~~~~~",t1);free(t1); // DEBUG OUTPUT
|
|
|
|
if( !dc_smtp_send_msg(context->smtp, mimefactory.recipients_addr, mimefactory.out->str, mimefactory.out->len) ) {
|
|
dc_smtp_disconnect(context->smtp);
|
|
dc_job_try_again_later(job, DC_AT_ONCE); /* DC_AT_ONCE is only the _initial_ delay, if the second try failes, the delay gets larger */
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
dc_mimefactory_empty(&mimefactory);
|
|
}
|
|
|
|
|
|
static void dc_suspend_smtp_thread(dc_context_t* context, int suspend)
|
|
{
|
|
pthread_mutex_lock(&context->smtpidle_condmutex);
|
|
context->smtpidle_suspend = suspend;
|
|
pthread_mutex_unlock(&context->smtpidle_condmutex);
|
|
|
|
// the smtp-thread may be in perform_jobs() when this function is called,
|
|
// wait until we arrive in idle(). for simplicity, we do this by polling a variable
|
|
// (in fact, this is only needed when calling configure() is called)
|
|
if( suspend )
|
|
{
|
|
while( 1 ) {
|
|
pthread_mutex_lock(&context->smtpidle_condmutex);
|
|
if( context->smtpidle_in_idleing ) {
|
|
context->perform_smtp_jobs_needed = 0;
|
|
pthread_mutex_unlock(&context->smtpidle_condmutex);
|
|
return;
|
|
}
|
|
pthread_mutex_unlock(&context->smtpidle_condmutex);
|
|
usleep(300*1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Tools
|
|
******************************************************************************/
|
|
|
|
|
|
void dc_job_add(dc_context_t* context, int action, int foreign_id, const char* param, int delay_seconds)
|
|
{
|
|
time_t timestamp = time(NULL);
|
|
sqlite3_stmt* stmt;
|
|
int thread;
|
|
|
|
if( action >= DC_IMAP_THREAD && action < DC_IMAP_THREAD+1000 ) {
|
|
thread = DC_IMAP_THREAD;
|
|
}
|
|
else if( action >= DC_SMTP_THREAD && action < DC_SMTP_THREAD+1000 ) {
|
|
thread = DC_SMTP_THREAD;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
|
|
stmt = dc_sqlite3_prepare(context->sql,
|
|
"INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);");
|
|
sqlite3_bind_int64(stmt, 1, timestamp);
|
|
sqlite3_bind_int (stmt, 2, thread);
|
|
sqlite3_bind_int (stmt, 3, action);
|
|
sqlite3_bind_int (stmt, 4, foreign_id);
|
|
sqlite3_bind_text (stmt, 5, param? param : "", -1, SQLITE_STATIC);
|
|
sqlite3_bind_int64(stmt, 6, delay_seconds>0? (timestamp+delay_seconds) : 0);
|
|
sqlite3_step(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if( thread == DC_IMAP_THREAD ) {
|
|
dc_interrupt_imap_idle(context);
|
|
}
|
|
else {
|
|
dc_interrupt_smtp_idle(context);
|
|
}
|
|
}
|
|
|
|
|
|
void dc_job_try_again_later(dc_job_t* job, int try_again)
|
|
{
|
|
if( job == NULL ) {
|
|
return;
|
|
}
|
|
|
|
job->try_again = try_again;
|
|
}
|
|
|
|
|
|
void dc_job_kill_actions(dc_context_t* context, int action1, int action2)
|
|
{
|
|
if( context == NULL ) {
|
|
return;
|
|
}
|
|
|
|
sqlite3_stmt* stmt = dc_sqlite3_prepare(context->sql,
|
|
"DELETE FROM jobs WHERE action=? OR action=?;");
|
|
sqlite3_bind_int(stmt, 1, action1);
|
|
sqlite3_bind_int(stmt, 2, action2);
|
|
sqlite3_step(stmt);
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
|
|
static void dc_job_perform(dc_context_t* context, int thread)
|
|
{
|
|
sqlite3_stmt* select_stmt = NULL;
|
|
dc_job_t job;
|
|
#define THREAD_STR (thread==DC_IMAP_THREAD? "IMAP" : "SMTP")
|
|
#define IS_EXCLUSIVE_JOB (DC_JOB_CONFIGURE_IMAP==job.action || DC_JOB_IMEX_IMAP==job.action)
|
|
|
|
memset(&job, 0, sizeof(dc_job_t));
|
|
job.param = dc_param_new();
|
|
|
|
if( context == NULL || context->magic != DC_CONTEXT_MAGIC ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
select_stmt = dc_sqlite3_prepare(context->sql,
|
|
"SELECT id, action, foreign_id, param FROM jobs WHERE thread=? AND desired_timestamp<=? ORDER BY action DESC, added_timestamp;");
|
|
sqlite3_bind_int64(select_stmt, 1, thread);
|
|
sqlite3_bind_int64(select_stmt, 2, time(NULL));
|
|
while( sqlite3_step(select_stmt) == SQLITE_ROW )
|
|
{
|
|
job.job_id = sqlite3_column_int (select_stmt, 0);
|
|
job.action = sqlite3_column_int (select_stmt, 1);
|
|
job.foreign_id = sqlite3_column_int (select_stmt, 2);
|
|
dc_param_set_packed(job.param, (char*)sqlite3_column_text(select_stmt, 3));
|
|
|
|
dc_log_info(context, 0, "%s-job #%i, action %i started...", THREAD_STR, (int)job.job_id, (int)job.action);
|
|
|
|
// some configuration jobs are "exclusive":
|
|
// - they are always executed in the imap-thread and the smtp-thread is suspended during execution
|
|
// - they may change the database handle change the database handle; we do not keep old pointers therefore
|
|
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
|
|
if (IS_EXCLUSIVE_JOB) {
|
|
dc_job_kill_actions(context, job.action, 0);
|
|
sqlite3_finalize(select_stmt);
|
|
select_stmt = NULL;
|
|
dc_suspend_smtp_thread(context, 1);
|
|
}
|
|
|
|
for( int tries = 0; tries <= 1; tries++ )
|
|
{
|
|
job.try_again = DC_DONT_TRY_AGAIN; // this can be modified by a job using dc_job_try_again_later()
|
|
|
|
switch( job.action ) {
|
|
case DC_JOB_SEND_MSG_TO_SMTP: dc_job_do_DC_JOB_SEND_MSG_TO_SMTP (context, &job); break;
|
|
case DC_JOB_SEND_MSG_TO_IMAP: dc_job_do_DC_JOB_SEND_MSG_TO_IMAP (context, &job); break;
|
|
case DC_JOB_DELETE_MSG_ON_IMAP: dc_job_do_DC_JOB_DELETE_MSG_ON_IMAP (context, &job); break;
|
|
case DC_JOB_MARKSEEN_MSG_ON_IMAP: dc_job_do_DC_JOB_MARKSEEN_MSG_ON_IMAP (context, &job); break;
|
|
case DC_JOB_MARKSEEN_MDN_ON_IMAP: dc_job_do_DC_JOB_MARKSEEN_MDN_ON_IMAP (context, &job); break;
|
|
case DC_JOB_SEND_MDN: dc_job_do_DC_JOB_SEND_MDN (context, &job); break;
|
|
case DC_JOB_CONFIGURE_IMAP: dc_job_do_DC_JOB_CONFIGURE_IMAP (context, &job); break;
|
|
case DC_JOB_IMEX_IMAP: dc_job_do_DC_JOB_IMEX_IMAP (context, &job); break;
|
|
}
|
|
|
|
if( job.try_again != DC_AT_ONCE ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (IS_EXCLUSIVE_JOB) {
|
|
dc_suspend_smtp_thread(context, 0);
|
|
goto cleanup;
|
|
}
|
|
else if( job.try_again == DC_INCREATION_POLL )
|
|
{
|
|
// just try over next loop unconditionally, the ui typically interrupts idle when the file (video) is ready
|
|
dc_log_info(context, 0, "%s-job #%i not yet ready and will be delayed.", THREAD_STR, (int)job.job_id);
|
|
}
|
|
else if( job.try_again == DC_AT_ONCE || job.try_again == DC_STANDARD_DELAY )
|
|
{
|
|
int tries = dc_param_get_int(job.param, DC_PARAM_TIMES, 0) + 1;
|
|
dc_param_set_int(job.param, DC_PARAM_TIMES, tries);
|
|
|
|
sqlite3_stmt* update_stmt = dc_sqlite3_prepare(context->sql,
|
|
"UPDATE jobs SET desired_timestamp=0, param=? WHERE id=?;");
|
|
sqlite3_bind_text (update_stmt, 1, job.param->packed, -1, SQLITE_STATIC);
|
|
sqlite3_bind_int (update_stmt, 2, job.job_id);
|
|
sqlite3_step(update_stmt);
|
|
sqlite3_finalize(update_stmt);
|
|
dc_log_info(context, 0, "%s-job #%i not succeeded, trying again asap.", THREAD_STR, (int)job.job_id);
|
|
|
|
// if the job did not succeeded AND this is a smtp-job AND we're online, try over after a mini-delay of one second.
|
|
// if we're not online, the ui calls interrupt idle as soon as we're online again.
|
|
// if nothing of this happens, after DC_SMTP_IDLE_SEC (60) we try again.
|
|
if( thread == DC_SMTP_THREAD
|
|
&& dc_is_online(context) )
|
|
{
|
|
pthread_mutex_lock(&context->smtpidle_condmutex);
|
|
context->perform_smtp_jobs_needed = DC_JOBS_NEEDED_AVOID_DOS;
|
|
pthread_mutex_unlock(&context->smtpidle_condmutex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sqlite3_stmt* delete_stmt = dc_sqlite3_prepare(context->sql,
|
|
"DELETE FROM jobs WHERE id=?;");
|
|
sqlite3_bind_int(delete_stmt, 1, job.job_id);
|
|
sqlite3_step(delete_stmt);
|
|
sqlite3_finalize(delete_stmt);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
dc_param_unref(job.param);
|
|
sqlite3_finalize(select_stmt);
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* User-functions handle IMAP-jobs from the IMAP-thread
|
|
******************************************************************************/
|
|
|
|
|
|
/**
|
|
* Execute pending imap-jobs.
|
|
* This function and dc_perform_imap_fetch() and dc_perform_imap_idle() must be called from the same thread,
|
|
* typically in a loop.
|
|
*
|
|
* See dc_interrupt_imap_idle() for an example.
|
|
*
|
|
* @memberof dc_context_t
|
|
* @param context The context as created by dc_context_new().
|
|
* @return None
|
|
*/
|
|
void dc_perform_imap_jobs(dc_context_t* context)
|
|
{
|
|
dc_log_info(context, 0, "IMAP-jobs started...");
|
|
|
|
pthread_mutex_lock(&context->imapidle_condmutex);
|
|
context->perform_imap_jobs_needed = 0;
|
|
pthread_mutex_unlock(&context->imapidle_condmutex);
|
|
|
|
dc_job_perform(context, DC_IMAP_THREAD);
|
|
|
|
dc_log_info(context, 0, "IMAP-jobs ended.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetch new messages, if any.
|
|
* This function and dc_perform_imap_jobs() and dc_perform_imap_idle() must be called from the same thread,
|
|
* typically in a loop.
|
|
*
|
|
* See dc_interrupt_imap_idle() for an example.
|
|
*
|
|
* @memberof dc_context_t
|
|
* @param context The context as created by dc_context_new().
|
|
* @return None.
|
|
*/
|
|
void dc_perform_imap_fetch(dc_context_t* context)
|
|
{
|
|
clock_t start = clock();
|
|
|
|
if( !connect_to_imap(context, NULL) ) {
|
|
return;
|
|
}
|
|
|
|
dc_log_info(context, 0, "IMAP-fetch started...");
|
|
|
|
dc_imap_fetch(context->imap);
|
|
|
|
if( context->imap->should_reconnect
|
|
&& context->cb(context, DC_EVENT_IS_OFFLINE, 0, 0)==0 )
|
|
{
|
|
dc_log_info(context, 0, "IMAP-fetch aborted, starting over...");
|
|
dc_imap_fetch(context->imap);
|
|
}
|
|
|
|
dc_log_info(context, 0, "IMAP-fetch done in %.0f ms.", (double)(clock()-start)*1000.0/CLOCKS_PER_SEC);
|
|
}
|
|
|
|
|
|
/**
|
|
* Wait for messages or jobs.
|
|
* This function and dc_perform_imap_jobs() and dc_perform_imap_fetch() must be called from the same thread,
|
|
* typically in a loop.
|
|
*
|
|
* You should call this function directly after calling dc_perform_imap_fetch().
|
|
*
|
|
* See dc_interrupt_imap_idle() for an example.
|
|
*
|
|
* @memberof dc_context_t
|
|
* @param context The context as created by dc_context_new().
|
|
* @return None.
|
|
*/
|
|
void dc_perform_imap_idle(dc_context_t* context)
|
|
{
|
|
connect_to_imap(context, NULL); // also idle if connection fails because of not-configured, no-network, whatever. dc_imap_idle() will handle this by the fake-idle and log a warning
|
|
|
|
pthread_mutex_lock(&context->imapidle_condmutex);
|
|
if( context->perform_imap_jobs_needed ) {
|
|
dc_log_info(context, 0, "IMAP-IDLE will not be started because of waiting jobs.");
|
|
pthread_mutex_unlock(&context->imapidle_condmutex);
|
|
return;
|
|
}
|
|
pthread_mutex_unlock(&context->imapidle_condmutex);
|
|
|
|
dc_log_info(context, 0, "IMAP-IDLE started...");
|
|
|
|
dc_imap_idle(context->imap);
|
|
|
|
dc_log_info(context, 0, "IMAP-IDLE ended.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Interrupt waiting for imap-jobs.
|
|
* If dc_perform_imap_jobs(), dc_perform_imap_fetch() and dc_perform_imap_idle() are called in a loop,
|
|
* calling this function causes imap-jobs to be executed and messages to be fetched.
|
|
*
|
|
* Internally, this function is called whenever a imap-jobs should be processed (delete message, markseen etc.),
|
|
* for the UI view it may make sense to call the function eg. on network changes to fetch messages immediately.
|
|
*
|
|
* Example:
|
|
*
|
|
* void* imap_thread_func(void* context)
|
|
* {
|
|
* while( true ) {
|
|
* dc_perform_imap_jobs(context);
|
|
* dc_perform_imap_fetch(context);
|
|
* dc_perform_imap_idle(context);
|
|
* }
|
|
* }
|
|
*
|
|
* // start imap-thread that runs forever
|
|
* pthread_t imap_thread;
|
|
* pthread_create(&imap_thread, NULL, imap_thread_func, context);
|
|
*
|
|
* ... program runs ...
|
|
*
|
|
* // network becomes available again - the interrupt causes
|
|
* // dc_perform_imap_idle() in the thread to return so that jobs are executed
|
|
* // and messages are fetched.
|
|
* dc_interrupt_imap_idle(context);
|
|
*
|
|
* @memberof dc_context_t
|
|
* @param context The context as created by dc_context_new().
|
|
* @return None
|
|
*/
|
|
void dc_interrupt_imap_idle(dc_context_t* context)
|
|
{
|
|
if( context == NULL || context->magic != DC_CONTEXT_MAGIC || context->imap == NULL ) {
|
|
dc_log_warning(context, 0, "Interrupt IMAP-IDLE: Bad parameters.");
|
|
return;
|
|
}
|
|
|
|
dc_log_info(context, 0, "Interrupting IMAP-IDLE...");
|
|
|
|
pthread_mutex_lock(&context->imapidle_condmutex);
|
|
// when this function is called, it might be that the idle-thread is in
|
|
// perform_idle_jobs() instead of idle(). if so, added jobs will be performed after the _next_ idle-jobs loop.
|
|
// setting the flag perform_imap_jobs_needed makes sure, idle() returns immediately in this case.
|
|
context->perform_imap_jobs_needed = 1;
|
|
pthread_mutex_unlock(&context->imapidle_condmutex);
|
|
|
|
dc_imap_interrupt_idle(context->imap);
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* User-functions handle SMTP-jobs from the SMTP-thread
|
|
******************************************************************************/
|
|
|
|
|
|
/**
|
|
* Execute pending smtp-jobs.
|
|
* This function and dc_perform_smtp_idle() must be called from the same thread,
|
|
* typically in a loop.
|
|
*
|
|
* See dc_interrupt_smtp_idle() for an example.
|
|
*
|
|
* @memberof dc_context_t
|
|
* @param context The context as created by dc_context_new().
|
|
* @return None
|
|
*/
|
|
void dc_perform_smtp_jobs(dc_context_t* context)
|
|
{
|
|
dc_log_info(context, 0, "SMTP-jobs started...");
|
|
|
|
pthread_mutex_lock(&context->smtpidle_condmutex);
|
|
context->perform_smtp_jobs_needed = 0;
|
|
pthread_mutex_unlock(&context->smtpidle_condmutex);
|
|
|
|
dc_job_perform(context, DC_SMTP_THREAD);
|
|
|
|
dc_log_info(context, 0, "SMTP-jobs ended.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Wait for smtp-jobs.
|
|
* This function and dc_perform_smtp_jobs() must be called from the same thread,
|
|
* typically in a loop.
|
|
*
|
|
* See dc_interrupt_smtp_idle() for an example.
|
|
*
|
|
* @memberof dc_context_t
|
|
* @param context The context as created by dc_context_new().
|
|
* @return None
|
|
*/
|
|
void dc_perform_smtp_idle(dc_context_t* context)
|
|
{
|
|
if( context == NULL || context->magic != DC_CONTEXT_MAGIC ) {
|
|
dc_log_warning(context, 0, "Cannot perform SMTP-idle: Bad parameters.");
|
|
return;
|
|
}
|
|
|
|
dc_log_info(context, 0, "SMTP-idle started...");
|
|
|
|
pthread_mutex_lock(&context->smtpidle_condmutex);
|
|
|
|
if( context->perform_smtp_jobs_needed == DC_JOBS_NEEDED_AT_ONCE )
|
|
{
|
|
dc_log_info(context, 0, "SMTP-idle will not be started because of waiting jobs.");
|
|
}
|
|
else
|
|
{
|
|
context->smtpidle_in_idleing = 1; // checked in suspend(), for idle-interruption the pthread-condition below is used
|
|
|
|
do {
|
|
int r = 0;
|
|
struct timespec wakeup_at;
|
|
memset(&wakeup_at, 0, sizeof(wakeup_at));
|
|
wakeup_at.tv_sec = time(NULL) + ((context->perform_smtp_jobs_needed==DC_JOBS_NEEDED_AVOID_DOS)? 2 : DC_SMTP_IDLE_SEC);
|
|
while (context->smtpidle_condflag==0 && r==0) {
|
|
r = pthread_cond_timedwait(&context->smtpidle_cond, &context->smtpidle_condmutex, &wakeup_at); // unlock mutex -> wait -> lock mutex
|
|
}
|
|
} while (context->smtpidle_suspend);
|
|
context->smtpidle_condflag = 0;
|
|
|
|
context->smtpidle_in_idleing = 0;
|
|
}
|
|
|
|
pthread_mutex_unlock(&context->smtpidle_condmutex);
|
|
|
|
dc_log_info(context, 0, "SMTP-idle ended.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Interrupt waiting for smtp-jobs.
|
|
* If dc_perform_smtp_jobs() and dc_perform_smtp_idle() are called in a loop,
|
|
* calling this function causes jobs to be executed.
|
|
*
|
|
* Internally, this function is called whenever a message is to be send,
|
|
* for the UI view it may make sense to call the function eg. on network changes.
|
|
*
|
|
* Example:
|
|
*
|
|
* void* smtp_thread_func(void* context)
|
|
* {
|
|
* while( true ) {
|
|
* dc_perform_smtp_jobs(context);
|
|
* dc_perform_smtp_idle(context);
|
|
* }
|
|
* }
|
|
*
|
|
* // start smtp-thread that runs forever
|
|
* pthread_t smtp_thread;
|
|
* pthread_create(&smtp_thread, NULL, smtp_thread_func, context);
|
|
*
|
|
* ... program runs ...
|
|
*
|
|
* // network becomes available again - the interrupt causes
|
|
* // dc_perform_smtp_idle() in the thread to return so that jobs are executed
|
|
* dc_interrupt_smtp_idle(context);
|
|
*
|
|
* @memberof dc_context_t
|
|
* @param context The context as created by dc_context_new().
|
|
* @return None
|
|
*/
|
|
void dc_interrupt_smtp_idle(dc_context_t* context)
|
|
{
|
|
if( context == NULL || context->magic != DC_CONTEXT_MAGIC ) {
|
|
dc_log_warning(context, 0, "Interrupt SMTP-idle: Bad parameters.");
|
|
return;
|
|
}
|
|
|
|
dc_log_info(context, 0, "Interrupting SMTP-idle...");
|
|
|
|
pthread_mutex_lock(&context->smtpidle_condmutex);
|
|
|
|
// when this function is called, it might be that the smtp-thread is in
|
|
// perform_smtp_jobs(). if so, added jobs will be performed after the _next_ idle-jobs loop.
|
|
// setting the flag perform_smtp_jobs_needed makes sure, idle() returns immediately in this case.
|
|
context->perform_smtp_jobs_needed = DC_JOBS_NEEDED_AT_ONCE;
|
|
|
|
context->smtpidle_condflag = 1;
|
|
pthread_cond_signal(&context->smtpidle_cond);
|
|
|
|
pthread_mutex_unlock(&context->smtpidle_condmutex);
|
|
}
|