diff --git a/src/dc_imex.rs b/src/dc_imex.rs index 4d4af60f2..04fbbcf13 100644 --- a/src/dc_imex.rs +++ b/src/dc_imex.rs @@ -1010,81 +1010,82 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_ ); } else { let dir_handle = dir_handle.unwrap(); - let mut stmt = dc_sqlite3_prepare( - context, - &sql, - "INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);" - ).expect("bad sql state"); - let mut processed_files_cnt = 0; - for entry in dir_handle { - if entry.is_err() { - current_block = 2631791190359682872; - break; - } - let entry = entry.unwrap(); - if context - .running_state - .clone() - .read() - .unwrap() - .shall_stop_ongoing - { - delete_dest_file = 1; - current_block = 11487273724841241105; - break; - } else { - processed_files_cnt += 1; - let mut permille = - processed_files_cnt * 1000 / total_files_cnt; - if permille < 10 { - permille = 10; - } - if permille > 990 { - permille = 990; - } - context.call_cb( - Event::IMEX_PROGRESS, - permille as uintptr_t, - 0 as uintptr_t, - ); - - let name_f = entry.file_name(); - let name = name_f.to_string_lossy(); - if name.starts_with("delta-chat") && name.ends_with(".bak") - { - continue; - } else { - info!(context, 0, "EXPORTing filename={}", name); - let curr_pathNfilename = format!( - "{}/{}", - as_str(context.get_blobdir()), - name - ); - - if let Some(buf) = - dc_read_file_safe(context, &curr_pathNfilename) + sql.prepare( + "INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);", + move |mut stmt| { + let mut processed_files_cnt = 0; + for entry in dir_handle { + if entry.is_err() { + current_block = 2631791190359682872; + break; + } + let entry = entry.unwrap(); + if context + .running_state + .clone() + .read() + .unwrap() + .shall_stop_ongoing { - if buf.is_empty() { - continue; - } - if stmt.execute(params![name, buf]).is_err() { - error!( - context, - 0, - "Disk full? Cannot add file \"{}\" to backup.", - &curr_pathNfilename, - ); - /* this is not recoverable! writing to the sqlite database should work! */ - current_block = 11487273724841241105; - break; - } + delete_dest_file = 1; + current_block = 11487273724841241105; + break; } else { - continue; + processed_files_cnt += 1; + let mut permille = + processed_files_cnt * 1000 / total_files_cnt; + if permille < 10 { + permille = 10; + } + if permille > 990 { + permille = 990; + } + context.call_cb( + Event::IMEX_PROGRESS, + permille as uintptr_t, + 0 as uintptr_t, + ); + + let name_f = entry.file_name(); + let name = name_f.to_string_lossy(); + if name.starts_with("delta-chat") && name.ends_with(".bak") + { + continue; + } else { + info!(context, 0, "EXPORTing filename={}", name); + let curr_pathNfilename = format!( + "{}/{}", + as_str(context.get_blobdir()), + name + ); + + if let Some(buf) = + dc_read_file_safe(context, &curr_pathNfilename) + { + if buf.is_empty() { + continue; + } + if stmt.execute(params![name, buf]).is_err() { + error!( + context, + 0, + "Disk full? Cannot add file \"{}\" to backup.", + &curr_pathNfilename, + ); + /* this is not recoverable! writing to the sqlite database should work! */ + current_block = 11487273724841241105; + break; + } + } else { + continue; + } + } } } + Ok(()) } - } + ).unwrap(); } } else { info!(context, 0, "Backup: No files to copy.",); diff --git a/src/dc_location.rs b/src/dc_location.rs index 7952a8e15..32f757824 100644 --- a/src/dc_location.rs +++ b/src/dc_location.rs @@ -409,71 +409,52 @@ pub unsafe fn dc_save_locations( return 0; } - let stmt_test = dc_sqlite3_prepare( - context, - &context.sql, - "SELECT id FROM locations WHERE timestamp=? AND from_id=?", - ); - let stmt_insert = dc_sqlite3_prepare( - context, - &context.sql, - "INSERT INTO locations\ - (timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \ - VALUES (?,?,?,?,?,?,?);", - ); + context + .sql + .prepare2( + "SELECT id FROM locations WHERE timestamp=? AND from_id=?", + "INSERT INTO locations\ + (timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \ + VALUES (?,?,?,?,?,?,?);", + |mut stmt_test, mut stmt_insert, conn| { + let mut newest_timestamp = 0; + let mut newest_location_id = 0; - if stmt_test.is_none() || stmt_insert.is_none() { - return 0; - } + for i in 0..dc_array_get_cnt(locations) { + let location = dc_array_get_ptr(locations, i as size_t) as *mut dc_location_t; - let mut stmt_test = stmt_test.unwrap(); - let mut stmt_insert = stmt_insert.unwrap(); + let exists = + stmt_test.exists(params![(*location).timestamp, contact_id as i32])?; - let mut newest_timestamp = 0; - let mut newest_location_id = 0; + if 0 != independent || !exists { + stmt_insert.execute(params![ + (*location).timestamp, + contact_id as i32, + chat_id as i32, + (*location).latitude, + (*location).longitude, + (*location).accuracy, + independent, + ])?; - for i in 0..dc_array_get_cnt(locations) { - // TODO: do I need to reset? - - let location = dc_array_get_ptr(locations, i as size_t) as *mut dc_location_t; - - let exists = stmt_test - .exists(params![(*location).timestamp, contact_id as i32]) - .unwrap_or_default(); - - if 0 != independent || !exists { - // TODO: do I need to reset? - if stmt_insert - .execute(params![ - (*location).timestamp, - contact_id as i32, - chat_id as i32, - (*location).latitude, - (*location).longitude, - (*location).accuracy, - independent, - ]) - .is_err() - { - return 0; - } - - if (*location).timestamp > newest_timestamp { - newest_timestamp = (*location).timestamp; - newest_location_id = dc_sqlite3_get_rowid2( - context, - &context.sql, - "locations", - "timestamp", - (*location).timestamp, - "from_id", - contact_id as i32, - ); - } - } - } - - newest_location_id + if (*location).timestamp > newest_timestamp { + newest_timestamp = (*location).timestamp; + newest_location_id = get_rowid2( + context, + conn, + "locations", + "timestamp", + (*location).timestamp, + "from_id", + contact_id as i32, + ); + } + } + } + Ok(newest_location_id) + }, + ) + .unwrap_or_default() } pub unsafe fn dc_kml_parse( @@ -677,9 +658,7 @@ pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: *mu } }, |rows| { - let stmt_locations = dc_sqlite3_prepare( - context, - &context.sql, + context.sql.prepare( "SELECT id \ FROM locations \ WHERE from_id=? \ @@ -687,44 +666,40 @@ pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: *mu AND timestamp>? \ AND independent=0 \ ORDER BY timestamp;", - ); - if stmt_locations.is_none() { - // TODO: handle error - return Ok(()); - } - let mut stmt_locations = stmt_locations.unwrap(); - - for (chat_id, locations_send_begin, locations_last_sent) in - rows.filter_map(|r| match r { - Ok(Some(v)) => Some(v), - _ => None, - }) - { - // TODO: do I need to reset? - if !stmt_locations - .exists(params![1, locations_send_begin, locations_last_sent,]) - .unwrap_or_default() - { - // if there is no new location, there's nothing to send. - // however, maybe we want to bypass this test eg. 15 minutes - continue; - } - // pending locations are attached automatically to every message, - // so also to this empty text message. - // DC_CMD_LOCATION is only needed to create a nicer subject. - // - // for optimisation and to avoid flooding the sending queue, - // we could sending these messages only if we're really online. - // the easiest way to determine this, is to check for an empty message queue. - // (might not be 100%, however, as positions are sent combined later - // and dc_set_location() is typically called periodically, this is ok) - let mut msg = dc_msg_new(context, 10); - (*msg).hidden = 1; - dc_param_set_int((*msg).param, 'S' as i32, 9); - dc_send_msg(context, chat_id as u32, msg); - dc_msg_unref(msg); - } - Ok(()) + |mut stmt_locations| { + for (chat_id, locations_send_begin, locations_last_sent) in + rows.filter_map(|r| match r { + Ok(Some(v)) => Some(v), + _ => None, + }) + { + // TODO: do I need to reset? + if !stmt_locations + .exists(params![1, locations_send_begin, locations_last_sent,]) + .unwrap_or_default() + { + // if there is no new location, there's nothing to send. + // however, maybe we want to bypass this test eg. 15 minutes + continue; + } + // pending locations are attached automatically to every message, + // so also to this empty text message. + // DC_CMD_LOCATION is only needed to create a nicer subject. + // + // for optimisation and to avoid flooding the sending queue, + // we could sending these messages only if we're really online. + // the easiest way to determine this, is to check for an empty message queue. + // (might not be 100%, however, as positions are sent combined later + // and dc_set_location() is typically called periodically, this is ok) + let mut msg = dc_msg_new(context, 10); + (*msg).hidden = 1; + dc_param_set_int((*msg).param, 'S' as i32, 9); + dc_send_msg(context, chat_id as u32, msg); + dc_msg_unref(msg); + } + Ok(()) + }, + ) }, ) .unwrap(); // TODO: Better error handling diff --git a/src/dc_msg.rs b/src/dc_msg.rs index bad6d016f..50f71a2dc 100644 --- a/src/dc_msg.rs +++ b/src/dc_msg.rs @@ -524,54 +524,50 @@ pub fn dc_update_msg_chat_id(context: &Context, msg_id: u32, chat_id: u32) -> bo ) } -pub unsafe fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize) { +pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize) -> bool { if msg_ids.is_null() || msg_cnt <= 0 { - return; + return false; } - let stmt = dc_sqlite3_prepare( - context, &context.sql, - "SELECT m.state, c.blocked FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id WHERE m.id=? AND m.chat_id>9" - ); + context.sql.prepare( + "SELECT m.state, c.blocked FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id WHERE m.id=? AND m.chat_id>9", + |mut stmt| { + let mut send_event = false; - if stmt.is_none() { - // TODO: error handling - return; - } + for i in 0..msg_cnt { + // TODO: do I need to reset? + let id = unsafe { *msg_ids.offset(i as isize) }; + if let Ok((curr_state, curr_blocked)) = stmt + .query_row(params![id as i32], |row| { + Ok((row.get::<_, i32>(0)?, row.get::<_, i32>(1)?)) + }) + { + if curr_blocked == 0 { + if curr_state == 10 || curr_state == 13 { + dc_update_msg_state(context, id, 16); + info!(context, 0, "Seen message #{}.", id); - let mut stmt = stmt.unwrap(); - let mut send_event = false; - - for i in 0..msg_cnt { - // TODO: do I need to reset? - if let Ok((curr_state, curr_blocked)) = stmt - .query_row(params![*msg_ids.offset(i as isize) as i32], |row| { - Ok((row.get::<_, i32>(0)?, row.get::<_, i32>(1)?)) - }) - { - if curr_blocked == 0 { - if curr_state == 10 || curr_state == 13 { - dc_update_msg_state(context, *msg_ids.offset(i as isize), 16); - info!(context, 0, "Seen message #{}.", *msg_ids.offset(i as isize),); - - dc_job_add( - context, - 130, - *msg_ids.offset(i as isize) as libc::c_int, - 0 as *const libc::c_char, - 0, - ); - send_event = true; + unsafe { dc_job_add( + context, + 130, + id as i32, + 0 as *const libc::c_char, + 0, + ) }; + send_event = true; + } + } else if curr_state == 10 { + dc_update_msg_state(context, id, 13); + send_event = true; + } } - } else if curr_state == 10 { - dc_update_msg_state(context, *msg_ids.offset(i as isize), 13); - send_event = true; } - } - } - if send_event { - context.call_cb(Event::MSGS_CHANGED, 0, 0); - } + if send_event { + context.call_cb(Event::MSGS_CHANGED, 0, 0); + } + Ok(()) + } + ).is_ok() } pub fn dc_update_msg_state(context: &Context, msg_id: uint32_t, state: libc::c_int) -> bool { @@ -592,26 +588,15 @@ pub fn dc_star_msgs( if msg_ids.is_null() || msg_cnt <= 0 || star != 0 && star != 1 { return false; } - let stmt = dc_sqlite3_prepare( - context, - &context.sql, - "UPDATE msgs SET starred=? WHERE id=?;", - ); - if stmt.is_none() { - return false; - } - - let mut stmt = stmt.unwrap(); - for i in 0..msg_cnt { - if stmt - .execute(params![star, unsafe { *msg_ids.offset(i as isize) as i32 }]) - .is_err() - { - return false; - } - } - - true + context + .sql + .prepare("UPDATE msgs SET starred=? WHERE id=?;", |mut stmt| { + for i in 0..msg_cnt { + stmt.execute(params![star, unsafe { *msg_ids.offset(i as isize) as i32 }])?; + } + Ok(()) + }) + .is_ok() } pub unsafe fn dc_get_msg<'a>(context: &'a Context, msg_id: uint32_t) -> *mut dc_msg_t<'a> { diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index a2bd7db83..98c2e6f78 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -489,122 +489,121 @@ pub unsafe fn dc_receive_imf( } icnt = carray_count(mime_parser.parts) as size_t; - let mut stmt = dc_sqlite3_prepare( - context, - &context.sql, + context.sql.prepare( "INSERT INTO msgs \ (rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \ timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \ bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \ - VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);" - ).unwrap(); - i = 0; - loop { - if !(i < icnt) { - current_block = 2756754640271984560; - break; - } - let part: *mut dc_mimepart_t = - carray_get(mime_parser.parts, i as libc::c_uint) - as *mut dc_mimepart_t; - if !(0 != (*part).is_meta) { - if !mime_parser.location_kml.is_null() - && icnt == 1 - && !(*part).msg.is_null() - && (strcmp( - (*part).msg, - b"-location-\x00" as *const u8 as *const libc::c_char, - ) == 0 - || *(*part).msg.offset(0isize) as libc::c_int == 0) - { - hidden = 1; - if state == 10 { - state = 13 + VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);", + |mut stmt| { + let mut i = 0; + loop { + if !(i < icnt) { + current_block = 2756754640271984560; + break; } - } - if (*part).type_0 == 10 { - txt_raw = dc_mprintf( - b"%s\n\n%s\x00" as *const u8 as *const libc::c_char, - if !mime_parser.subject.is_null() { - mime_parser.subject + let part = carray_get(mime_parser.parts, i as libc::c_uint) as *mut dc_mimepart_t; + if !(0 != (*part).is_meta) { + if !mime_parser.location_kml.is_null() + && icnt == 1 + && !(*part).msg.is_null() + && (strcmp( + (*part).msg, + b"-location-\x00" as *const u8 as *const libc::c_char, + ) == 0 + || *(*part).msg.offset(0isize) as libc::c_int == 0) + { + hidden = 1; + if state == 10 { + state = 13 + } + } + if (*part).type_0 == 10 { + txt_raw = dc_mprintf( + b"%s\n\n%s\x00" as *const u8 as *const libc::c_char, + if !mime_parser.subject.is_null() { + mime_parser.subject + } else { + b"\x00" as *const u8 as *const libc::c_char + }, + (*part).msg_raw, + ) + } + if 0 != mime_parser.is_system_message { + dc_param_set_int( + (*part).param, + 'S' as i32, + mime_parser.is_system_message, + ); + } + + let res = stmt.execute(params![ + as_str(rfc724_mid), + server_folder.as_ref(), + server_uid as libc::c_int, + chat_id as libc::c_int, + from_id as libc::c_int, + to_id as libc::c_int, + sort_timestamp, + sent_timestamp, + rcvd_timestamp, + (*part).type_0, + state, + msgrmsg, + if !(*part).msg.is_null() { + as_str((*part).msg) + } else { + "" + }, + if !txt_raw.is_null() { + as_str(txt_raw) + } else { + "" + }, + as_str((*(*part).param).packed), + (*part).bytes, + hidden, + if 0 != save_mime_headers { + Some(as_str(imf_raw_not_terminated)) + } else { + None + }, + as_str(mime_in_reply_to), + as_str(mime_references), + ]); + + if res.is_err() { + info!(context, 0, "Cannot write DB.",); + /* i/o error - there is nothing more we can do - in other cases, we try to write at least an empty record */ + current_block = 16282941964262048061; + break; } else { - b"\x00" as *const u8 as *const libc::c_char - }, - (*part).msg_raw, - ) - } - if 0 != mime_parser.is_system_message { - dc_param_set_int( - (*part).param, - 'S' as i32, - mime_parser.is_system_message, - ); - } - - let res = stmt.execute(params![ - as_str(rfc724_mid), - server_folder.as_ref(), - server_uid as libc::c_int, - chat_id as libc::c_int, - from_id as libc::c_int, - to_id as libc::c_int, - sort_timestamp, - sent_timestamp, - rcvd_timestamp, - (*part).type_0, - state, - msgrmsg, - if !(*part).msg.is_null() { - as_str((*part).msg) - } else { - "" - }, - if !txt_raw.is_null() { - as_str(txt_raw) - } else { - "" - }, - as_str((*(*part).param).packed), - (*part).bytes, - hidden, - if 0 != save_mime_headers { - Some(as_str(imf_raw_not_terminated)) - } else { - None - }, - as_str(mime_in_reply_to), - as_str(mime_references), - ]); - - if res.is_err() { - info!(context, 0, "Cannot write DB.",); - /* i/o error - there is nothing more we can do - in other cases, we try to write at least an empty record */ - current_block = 16282941964262048061; - break; - } else { - free(txt_raw as *mut libc::c_void); - txt_raw = 0 as *mut libc::c_char; - insert_msg_id = dc_sqlite3_get_rowid( - context, - &context.sql, - "msgs", - "rfc724_mid", - as_str(rfc724_mid), - ); - carray_add( - created_db_entries, - chat_id as uintptr_t as *mut libc::c_void, - 0 as *mut libc::c_uint, - ); - carray_add( - created_db_entries, - insert_msg_id as uintptr_t as *mut libc::c_void, - 0 as *mut libc::c_uint, - ); + free(txt_raw as *mut libc::c_void); + txt_raw = 0 as *mut libc::c_char; + insert_msg_id = dc_sqlite3_get_rowid( + context, + &context.sql, + "msgs", + "rfc724_mid", + as_str(rfc724_mid), + ); + carray_add( + created_db_entries, + chat_id as uintptr_t as *mut libc::c_void, + 0 as *mut libc::c_uint, + ); + carray_add( + created_db_entries, + insert_msg_id as uintptr_t as *mut libc::c_void, + 0 as *mut libc::c_uint, + ); + } + } + i = i.wrapping_add(1) } + Ok(()) } - i = i.wrapping_add(1) - } + ).unwrap(); // TODO: better error handling match current_block { 16282941964262048061 => {} _ => { diff --git a/src/dc_sqlite3.rs b/src/dc_sqlite3.rs index d19bf06c8..33c40ed8e 100644 --- a/src/dc_sqlite3.rs +++ b/src/dc_sqlite3.rs @@ -60,6 +60,32 @@ impl SQLite { self.connection.read().unwrap() } + pub fn prepare(&self, sql: &str, g: G) -> Result + where + G: FnOnce(Statement<'_>) -> Result, + { + let conn_lock = self.connection.read().unwrap(); + let conn = conn_lock.as_ref().expect("database closed"); + + let stmt = conn.prepare(sql)?; + let res = g(stmt)?; + Ok(res) + } + + pub fn prepare2(&self, sql1: &str, sql2: &str, g: G) -> Result + where + G: FnOnce(Statement<'_>, Statement<'_>, &Connection) -> Result, + { + let conn_lock = self.connection.read().unwrap(); + let conn = conn_lock.as_ref().expect("database closed"); + + let stmt1 = conn.prepare(sql1)?; + let stmt2 = conn.prepare(sql2)?; + + let res = g(stmt1, stmt2, conn)?; + Ok(res) + } + /// Prepares and executes the statement and maps a function over the resulting rows. /// Then executes the second function over the returned iterator and returns the /// result of that function. @@ -883,16 +909,6 @@ pub fn dc_sqlite3_set_config( 1 } -// TODO: Remove the option from the return type -pub fn dc_sqlite3_prepare<'a>( - _context: &Context, - _sql: &'a SQLite, - _querystr: &'a str, -) -> Option> { - // TODO: remove once it is not used anymore - unimplemented!() -} - pub fn dc_sqlite3_get_config( context: &Context, sql: &SQLite, @@ -1047,28 +1063,39 @@ pub fn dc_sqlite3_get_rowid2( field2: impl AsRef, value2: i32, ) -> u32 { - // same as dc_sqlite3_get_rowid() with a key over two columns if let Some(conn) = &*sql.connection.read().unwrap() { - match conn.query_row( - &format!( - "SELECT id FROM ? WHERE {}=? AND {}=? ORDER BY id DESC", - field.as_ref(), - field2.as_ref(), - ), - params![table.as_ref(), value, value2], - |row| row.get::<_, u32>(0), - ) { - Ok(id) => id, - Err(err) => { - error!(context, 0, "sql: Failed to retrieve rowid2: {}", err); - 0 - } - } + get_rowid2(context, conn, table, field, value, field2, value2) } else { 0 } } +pub fn get_rowid2( + context: &Context, + conn: &Connection, + table: impl AsRef, + field: impl AsRef, + value: i64, + field2: impl AsRef, + value2: i32, +) -> u32 { + match conn.query_row( + &format!( + "SELECT id FROM ? WHERE {}=? AND {}=? ORDER BY id DESC", + field.as_ref(), + field2.as_ref(), + ), + params![table.as_ref(), value, value2], + |row| row.get::<_, u32>(0), + ) { + Ok(id) => id, + Err(err) => { + error!(context, 0, "sql: Failed to retrieve rowid2: {}", err); + 0 + } + } +} + pub fn dc_housekeeping(context: &Context) { let mut files_in_use = HashSet::new(); let mut unreferenced_count = 0;