diff --git a/cmdline/cmdline.c b/cmdline/cmdline.c index 032e7d37..d436b2cf 100644 --- a/cmdline/cmdline.c +++ b/cmdline/cmdline.c @@ -437,6 +437,8 @@ char* dc_cmdline(dc_context_t* context, const char* cmdline) "groupname \n" "groupimage []\n" "chatinfo\n" + "sendlocations \n" + "setlocation \n" "send \n" "sendimage []\n" "sendfile \n" @@ -885,7 +887,9 @@ char* dc_cmdline(dc_context_t* context, const char* cmdline) if (contacts) { dc_log_info(context, 0, "Memberlist:"); log_contactlist(context, contacts); - ret = dc_mprintf("%i contacts.", (int)dc_array_get_cnt(contacts)); + ret = dc_mprintf("%i contacts\nLocation streaming: %i", + (int)dc_array_get_cnt(contacts), + dc_is_sending_locations_to_chat(context, dc_chat_get_id(sel_chat))); } else { ret = COMMAND_FAILED; @@ -895,6 +899,36 @@ char* dc_cmdline(dc_context_t* context, const char* cmdline) ret = dc_strdup("No chat selected."); } } + else if (strcmp(cmd, "sendlocations")==0) + { + if (sel_chat) { + if (arg1 && arg1[0]) { + int seconds = atoi(arg1); + dc_send_locations_to_chat(context, dc_chat_get_id(sel_chat), seconds); + ret = COMMAND_SUCCEEDED; + } + else { + ret = dc_strdup("ERROR: No timeout given."); + } + } + else { + ret = dc_strdup("No chat selected."); + } + } + else if (strcmp(cmd, "setlocation")==0) { + char* arg2 = NULL; + if (arg1) { arg2 = strrchr(arg1, ' '); } + if (arg1 && arg2) { + *arg2 = 0; arg2++; + double latitude = atof(arg1); + double longitude = atof(arg2); + dc_set_location(context, latitude, longitude, 0.0); + ret = COMMAND_SUCCEEDED; + } + else { + ret = dc_strdup("ERROR: Latitude or longitude not given."); + } + } else if (strcmp(cmd, "send")==0) { if (sel_chat) { diff --git a/src/dc_context.h b/src/dc_context.h index aead09e2..ed927a66 100644 --- a/src/dc_context.h +++ b/src/dc_context.h @@ -121,6 +121,8 @@ int dc_is_inbox (dc_context_t*, const char* folder); int dc_is_sentbox (dc_context_t*, const char* folder); int dc_is_mvbox (dc_context_t*, const char* folder); +char* dc_get_location_str (dc_context_t*); + #define DC_BAK_PREFIX "delta-chat" #define DC_BAK_SUFFIX "bak" diff --git a/src/dc_location.c b/src/dc_location.c new file mode 100644 index 00000000..8e9abe97 --- /dev/null +++ b/src/dc_location.c @@ -0,0 +1,163 @@ +#include "dc_context.h" +#include "dc_mimeparser.h" +#include "dc_job.h" + + +/** + * Enable or disable location streaming for a chat. + * Locations are sent to all members of the chat for the given number of seconds; + * after that, location streaming is automatically disabled for the chat. + * The current location streaming state of a chat + * can be checked using dc_is_sending_locations_to_chat(). + * + * The locations that should be sent to the chat can be set using + * dc_set_location(). + * + * @memberof dc_context_t + * @param context The context object. + * @param chat_id Chat id to enable location streaming for. + * @param seconds >0: enable location streaming for the given number of seconds; + * 0: disable location streaming. + * @return None. + */ +void dc_send_locations_to_chat(dc_context_t* context, uint32_t chat_id, + int seconds) +{ + sqlite3_stmt* stmt = NULL; + + if (context==0 || context->magic!=DC_CONTEXT_MAGIC || seconds<0) { + goto cleanup; + } + + stmt = dc_sqlite3_prepare(context->sql, + "UPDATE chats " + " SET locations_send_until=? " + " WHERE id=?"); + sqlite3_bind_int64(stmt, 1, time(NULL)+seconds); + sqlite3_bind_int (stmt, 2, chat_id); + sqlite3_step(stmt); + + // TODO: send a status message + +cleanup: + sqlite3_finalize(stmt); +} + + +/** + * Check if location streaming is enabled for a chat. + * Location stream can be enabled or disabled using dc_send_locations_to_chat(). + * + * @memberof dc_context_t + * @param context The context object. + * @param chat_id Chat id to check. + * @return 1: location streaming is enabled for the given chat; + * 0: location streaming is disabled for the given chat. + */ +int dc_is_sending_locations_to_chat(dc_context_t* context, uint32_t chat_id) +{ + int is_sending_locations = 0; + sqlite3_stmt* stmt = NULL; + time_t send_until = 0; + + if (context==0 || context->magic!=DC_CONTEXT_MAGIC) { + goto cleanup; + } + + stmt = dc_sqlite3_prepare(context->sql, + "SELECT locations_send_until " + " FROM chats " + " WHERE id=?"); + sqlite3_bind_int(stmt, 1, chat_id); + if (sqlite3_step(stmt)!=SQLITE_ROW) { + goto cleanup; + } + + send_until = sqlite3_column_int64(stmt, 0); + + if (time(NULL) < send_until) { + is_sending_locations = 1; + } + +cleanup: + sqlite3_finalize(stmt); + return is_sending_locations; +} + + +/** + * Set current location. + * The location is sent to all chats where location streaming is enabled + * using dc_send_locations_to_chat(). + * + * @memberof dc_context_t + * @param context The context object. + * @param latitude North-south position of the location. + * Set to 0.0 if the latitude is not known. + * @param longitude East-west position of the location. + * Set to 0.0 if the longitude is not known. + * @param accuracy Estimated accuracy of the location, radial, in meters. + * Set to 0.0 if the accuracy is not known. + * @return 1: location streaming is still enabled for at least one chat, + * this dc_set_location() should be called as soon as the location changes; + * 0: location streaming is no longer needed, + * dc_is_sending_locations_to_chat() is false for all chats. + */ +int dc_set_location(dc_context_t* context, + double latitude, double longitude, double accuracy) +{ + sqlite3_stmt* stmt = NULL; + + if (context==0 || context->magic!=DC_CONTEXT_MAGIC + || (latitude==0.0 && longitude==0.0)) { + goto cleanup; + } + + stmt = dc_sqlite3_prepare(context->sql, + "INSERT INTO locations " + " (latitude, longitude, accuracy, timestamp, from_id)" + " VALUES (?,?,?,?,?);"); + sqlite3_bind_double(stmt, 1, latitude); + sqlite3_bind_double(stmt, 2, longitude); + sqlite3_bind_double(stmt, 3, accuracy); + sqlite3_bind_int64 (stmt, 4, time(NULL)); + sqlite3_bind_int (stmt, 5, DC_CONTACT_ID_SELF); + sqlite3_step(stmt); + + context->cb(context, DC_EVENT_LOCATION_CHANGED, DC_CONTACT_ID_SELF, 0); + +cleanup: + sqlite3_finalize(stmt); + return 1; // TODO: check state +} + + + +char* dc_get_location_str(dc_context_t* context) +{ + sqlite3_stmt* stmt = NULL; + double latitude = 0.0; + double longitude = 0.0; + double accuracy = 0.0; + + if (context==0 || context->magic!=DC_CONTEXT_MAGIC) { + goto cleanup; + } + + stmt = dc_sqlite3_prepare(context->sql, + "SELECT latitude, longitude, accuracy, timestamp " + " FROM locations " + " WHERE from_id=? " + " AND timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?) "); + sqlite3_bind_int (stmt, 1, DC_CONTACT_ID_SELF); + sqlite3_bind_int (stmt, 2, DC_CONTACT_ID_SELF); + if (sqlite3_step(stmt)==SQLITE_ROW) { + latitude = sqlite3_column_double(stmt, 0); + longitude = sqlite3_column_double(stmt, 1); + accuracy = sqlite3_column_double(stmt, 2); + } + +cleanup: + sqlite3_finalize(stmt); + return dc_mprintf("%f %f %f", latitude, longitude, accuracy); +} diff --git a/src/dc_mimefactory.c b/src/dc_mimefactory.c index 622ae1ed..47f5b4bc 100644 --- a/src/dc_mimefactory.c +++ b/src/dc_mimefactory.c @@ -612,6 +612,12 @@ int dc_mimefactory_render(dc_mimefactory_t* factory) } } + if (dc_is_sending_locations_to_chat(msg->context, msg->chat_id)) { + mailimf_fields_add(imf_fields, mailimf_field_new_custom( + strdup("Chat-Location"), + dc_get_location_str(msg->context))); + } + if (grpimage) { dc_msg_t* meta = dc_msg_new_untyped(factory->context); diff --git a/src/dc_sqlite3.c b/src/dc_sqlite3.c index edb6be6b..8c6b1764 100644 --- a/src/dc_sqlite3.c +++ b/src/dc_sqlite3.c @@ -566,6 +566,30 @@ int dc_sqlite3_open(dc_sqlite3_t* sql, const char* dbfile, int flags) } #undef NEW_DB_VERSION + #define NEW_DB_VERSION 51 + if (dbversion < NEW_DB_VERSION) + { + // the messages containing _only_ locations + // are also added to the database as _hidden_. + dc_sqlite3_execute(sql, "CREATE TABLE locations (" + " id INTEGER PRIMARY KEY," + " latitude REAL DEFAULT 0.0," + " longitude REAL DEFAULT 0.0," + " accuracy REAL DEFAULT 0.0," + " timestamp INTEGER DEFAULT 0," + " chat_id INTEGER DEFAULT 0," + " from_id INTEGER DEFAULT 0," + " msg_id INTEGER DEFAULT 0);"); + dc_sqlite3_execute(sql, "CREATE INDEX locations_index1 ON locations (msg_id);"); + dc_sqlite3_execute(sql, "CREATE INDEX locations_index2 ON locations (timestamp);"); + dc_sqlite3_execute(sql, "ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;"); + dc_sqlite3_execute(sql, "ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;"); + + dbversion = NEW_DB_VERSION; + dc_sqlite3_set_config_int(sql, "dbversion", NEW_DB_VERSION); + } + #undef NEW_DB_VERSION + // (2) updates that require high-level objects // (the structure is complete now and all objects are usable) // -------------------------------------------------------------------- diff --git a/src/deltachat.h b/src/deltachat.h index 97461211..324f1a1b 100644 --- a/src/deltachat.h +++ b/src/deltachat.h @@ -360,6 +360,11 @@ char* dc_get_securejoin_qr (dc_context_t*, uint32_t chat_id); uint32_t dc_join_securejoin (dc_context_t*, const char* qr); +// location streaming +void dc_send_locations_to_chat (dc_context_t*, uint32_t chat_id, int seconds); +int dc_is_sending_locations_to_chat (dc_context_t*, uint32_t chat_id); +int dc_set_location (dc_context_t*, double latitude, double longitude, double accuracy); + /** * @class dc_array_t * @@ -985,6 +990,17 @@ time_t dc_lot_get_timestamp (const dc_lot_t*); #define DC_EVENT_CONTACTS_CHANGED 2030 + +/** + * Location of one or more contact has changed. + * + * @param data1 (int) contact_id of the contact for which the location has changed. + * @param data2 0 + * @return 0 + */ +#define DC_EVENT_LOCATION_CHANGED 2035 + + /** * Inform about the configuration progress started by dc_configure(). *