diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 2fe346004..20f65fe5b 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -34,7 +34,16 @@ use deltachat::*; pub type dc_context_t = context::Context; -pub type dc_callback_t = types::dc_callback_t; +/// Callback function that should be given to dc_context_new(). +/// +/// @memberof Context +/// @param context The context object as returned by dc_context_new(). +/// @param event one of the @ref DC_EVENT constants +/// @param data1 depends on the event parameter +/// @param data2 depends on the event parameter +/// @return return 0 unless stated otherwise in the event parameter documentation +pub type dc_callback_t = + unsafe extern "C" fn(_: &Context, _: Event, _: uintptr_t, _: uintptr_t) -> uintptr_t; #[no_mangle] pub unsafe extern "C" fn dc_context_new( diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index de92a1371..30afcd589 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -447,14 +447,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E =============================================" ), }, - "open" => { - ensure!(!arg1.is_empty(), "Argument missing"); - dc_close(context); - ensure!(dc_open(context, arg1, None), "Open failed"); - } - "close" => { - dc_close(context); - } "initiate-key-transfer" => { let setup_code = dc_initiate_key_transfer(context); if !setup_code.is_null() { diff --git a/examples/repl/main.rs b/examples/repl/main.rs index 616386ff9..e95655383 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -15,8 +15,8 @@ extern crate rusqlite; use std::borrow::Cow::{self, Borrowed, Owned}; use std::io::{self, Write}; +use std::path::Path; use std::process::Command; -use std::ptr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; @@ -44,7 +44,7 @@ use self::cmdline::*; // Event Handler -unsafe extern "C" fn receive_event( +fn receive_event( _context: &Context, event: Event, data1: uintptr_t, @@ -387,15 +387,15 @@ impl Highlighter for DcHelper { impl Helper for DcHelper {} fn main_0(args: Vec) -> Result<(), failure::Error> { - let mut context = dc_context_new(Some(receive_event), ptr::null_mut(), Some("CLI".into())); - - if args.len() == 2 { - if unsafe { !dc_open(&mut context, &args[1], None) } { - println!("Error: Cannot open {}.", args[0],); - } - } else if args.len() != 1 { + if args.len() < 2 { println!("Error: Bad arguments, expected [db-name]."); + return Err(format_err!("No db-name specified")); } + let context = Context::new( + Box::new(receive_event), + "CLI".into(), + Path::new(&args[1]).to_path_buf(), + )?; println!("Delta Chat Core is awaiting your commands."); diff --git a/examples/simple.rs b/examples/simple.rs index 59df952c8..34cc8f19a 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -16,7 +16,7 @@ use deltachat::job::{ perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs, }; -extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize { +fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize { println!("[{:?}]", event); match event { @@ -39,7 +39,11 @@ extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> us fn main() { unsafe { - let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None); + let dir = tempdir().unwrap(); + let dbfile = dir.path().join("db.sqlite"); + println!("creating database {:?}", dbfile); + let ctx = + Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context"); let running = Arc::new(RwLock::new(true)); let info = dc_get_info(&ctx); let info_s = CStr::from_ptr(info); @@ -73,13 +77,6 @@ fn main() { } }); - let dir = tempdir().unwrap(); - let dbfile = dir.path().join("db.sqlite"); - - println!("opening database {:?}", dbfile); - - assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None)); - println!("configuring"); let args = std::env::args().collect::>(); assert_eq!(args.len(), 2, "missing password"); @@ -130,6 +127,5 @@ fn main() { t2.join().unwrap(); println!("closing"); - dc_close(&ctx); } } diff --git a/src/context.rs b/src/context.rs index 6502a9356..613a669c4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,3 +1,6 @@ +use std::ffi::OsString; +use std::path::PathBuf; +use std::ptr; use std::sync::{Arc, Condvar, Mutex, RwLock}; use crate::chat::*; @@ -5,6 +8,7 @@ use crate::constants::*; use crate::contact::*; use crate::dc_receive_imf::*; use crate::dc_tools::*; +use crate::error::*; use crate::imap::*; use crate::job::*; use crate::job_thread::JobThread; @@ -17,9 +21,21 @@ use crate::smtp::*; use crate::sql::Sql; use crate::types::*; use crate::x::*; -use std::ptr; -use std::path::PathBuf; +/// Callback function type for [Context] +/// +/// # Parameters +/// +/// * `context` - The context object as returned by [Context::new]. +/// * `event` - One of the [Event] items. +/// * `data1` - Depends on the event parameter, see [Event]. +/// * `data2` - Depends on the event parameter, see [Event]. +/// +/// # Returns +/// +/// This callback must return 0 unless stated otherwise in the event +/// description at [Event]. +pub type ContextCallback = dyn Fn(&Context, Event, uintptr_t, uintptr_t) -> uintptr_t; pub struct Context { pub userdata: *mut libc::c_void, @@ -34,7 +50,7 @@ pub struct Context { pub smtp: Arc>, pub smtp_state: Arc<(Mutex, Condvar)>, pub oauth2_critical: Arc>, - pub cb: Option, + pub cb: Box, pub os_name: Option, pub cmdline_sel_chat_id: Arc>, pub bob: Arc>, @@ -54,6 +70,84 @@ pub struct RunningState { } impl Context { + pub fn new(cb: Box, os_name: String, dbfile: PathBuf) -> Result { + let mut blob_fname = OsString::new(); + blob_fname.push(dbfile.file_name().unwrap_or_default()); + blob_fname.push("-blobs"); + let blobdir = dbfile.with_file_name(blob_fname); + if !blobdir.exists() { + std::fs::create_dir_all(&blobdir)?; + } + Context::with_blobdir(cb, os_name, dbfile, blobdir) + } + + pub fn with_blobdir( + cb: Box, + os_name: String, + dbfile: PathBuf, + blobdir: PathBuf, + ) -> Result { + ensure!( + blobdir.is_dir(), + "Blobdir does not exist: {}", + blobdir.display() + ); + let blobdir_c = blobdir.to_c_string()?; + let ctx = Context { + blobdir: Arc::new(RwLock::new(unsafe { dc_strdup(blobdir_c.as_ptr()) })), + dbfile: Arc::new(RwLock::new(Some(dbfile))), + inbox: Arc::new(RwLock::new({ + Imap::new( + cb_get_config, + cb_set_config, + cb_precheck_imf, + cb_receive_imf, + ) + })), + userdata: std::ptr::null_mut(), + cb, + os_name: Some(os_name), + running_state: Arc::new(RwLock::new(Default::default())), + sql: Sql::new(), + smtp: Arc::new(Mutex::new(Smtp::new())), + smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())), + oauth2_critical: Arc::new(Mutex::new(())), + bob: Arc::new(RwLock::new(Default::default())), + last_smeared_timestamp: Arc::new(RwLock::new(0)), + cmdline_sel_chat_id: Arc::new(RwLock::new(0)), + sentbox_thread: Arc::new(RwLock::new(JobThread::new( + "SENTBOX", + "configured_sentbox_folder", + Imap::new( + cb_get_config, + cb_set_config, + cb_precheck_imf, + cb_receive_imf, + ), + ))), + mvbox_thread: Arc::new(RwLock::new(JobThread::new( + "MVBOX", + "configured_mvbox_folder", + Imap::new( + cb_get_config, + cb_set_config, + cb_precheck_imf, + cb_receive_imf, + ), + ))), + probe_imap_network: Arc::new(RwLock::new(false)), + perform_inbox_jobs_needed: Arc::new(RwLock::new(false)), + generating_key_mutex: Mutex::new(()), + }; + if !ctx + .sql + .open(&ctx, &ctx.dbfile.read().unwrap().as_ref().unwrap(), 0) + { + return Err(format_err!("Failed opening sqlite database")); + } + Ok(ctx) + } + pub fn has_dbfile(&self) -> bool { self.get_dbfile().is_some() } @@ -73,18 +167,24 @@ impl Context { } pub fn call_cb(&self, event: Event, data1: uintptr_t, data2: uintptr_t) -> uintptr_t { - if let Some(cb) = self.cb { - unsafe { cb(self, event, data1, data2) } - } else { - 0 - } + (*self.cb)(self, event, data1, data2) } } impl Drop for Context { fn drop(&mut self) { unsafe { - dc_close(&self); + info!(self, "disconnecting INBOX-watch",); + self.inbox.read().unwrap().disconnect(self); + info!(self, "disconnecting sentbox-thread",); + self.sentbox_thread.read().unwrap().imap.disconnect(self); + info!(self, "disconnecting mvbox-thread",); + self.mvbox_thread.read().unwrap().imap.disconnect(self); + info!(self, "disconnecting SMTP"); + self.smtp.clone().lock().unwrap().disconnect(); + self.sql.close(self); + let blobdir = self.blobdir.write().unwrap(); + free(*blobdir as *mut libc::c_void); } } } @@ -114,60 +214,6 @@ pub struct SmtpState { pub probe_network: bool, } -// create/open/config/information -pub fn dc_context_new( - cb: Option, - userdata: *mut libc::c_void, - os_name: Option, -) -> Context { - Context { - blobdir: Arc::new(RwLock::new(std::ptr::null_mut())), - dbfile: Arc::new(RwLock::new(None)), - inbox: Arc::new(RwLock::new({ - Imap::new( - cb_get_config, - cb_set_config, - cb_precheck_imf, - cb_receive_imf, - ) - })), - userdata, - cb, - os_name, - running_state: Arc::new(RwLock::new(Default::default())), - sql: Sql::new(), - smtp: Arc::new(Mutex::new(Smtp::new())), - smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())), - oauth2_critical: Arc::new(Mutex::new(())), - bob: Arc::new(RwLock::new(Default::default())), - last_smeared_timestamp: Arc::new(RwLock::new(0)), - cmdline_sel_chat_id: Arc::new(RwLock::new(0)), - sentbox_thread: Arc::new(RwLock::new(JobThread::new( - "SENTBOX", - "configured_sentbox_folder", - Imap::new( - cb_get_config, - cb_set_config, - cb_precheck_imf, - cb_receive_imf, - ), - ))), - mvbox_thread: Arc::new(RwLock::new(JobThread::new( - "MVBOX", - "configured_mvbox_folder", - Imap::new( - cb_get_config, - cb_set_config, - cb_precheck_imf, - cb_receive_imf, - ), - ))), - probe_imap_network: Arc::new(RwLock::new(false)), - perform_inbox_jobs_needed: Arc::new(RwLock::new(false)), - generating_key_mutex: Mutex::new(()), - } -} - unsafe fn cb_receive_imf( context: &Context, imf_raw_not_terminated: *const libc::c_char, @@ -251,70 +297,10 @@ fn cb_get_config(context: &Context, key: &str) -> Option { context.sql.get_config(context, key) } -pub unsafe fn dc_close(context: &Context) { - info!(context, "disconnecting INBOX-watch",); - context.inbox.read().unwrap().disconnect(context); - info!(context, "disconnecting sentbox-thread",); - context - .sentbox_thread - .read() - .unwrap() - .imap - .disconnect(context); - info!(context, "disconnecting mvbox-thread",); - context - .mvbox_thread - .read() - .unwrap() - .imap - .disconnect(context); - - info!(context, "disconnecting SMTP"); - context.smtp.clone().lock().unwrap().disconnect(); - - context.sql.close(context); - let mut dbfile = context.dbfile.write().unwrap(); - *dbfile = None; - let mut blobdir = context.blobdir.write().unwrap(); - free(*blobdir as *mut libc::c_void); - *blobdir = ptr::null_mut(); -} - -pub unsafe fn dc_is_open(context: &Context) -> libc::c_int { - context.sql.is_open() as libc::c_int -} - pub unsafe fn dc_get_userdata(context: &mut Context) -> *mut libc::c_void { context.userdata as *mut _ } -pub unsafe fn dc_open(context: &Context, dbfile: &str, blobdir: Option<&str>) -> bool { - let mut success = false; - if 0 != dc_is_open(context) { - return false; - } - - *context.dbfile.write().unwrap() = Some(PathBuf::from(dbfile)); - let blobdir = blobdir.unwrap_or_default(); - if !blobdir.is_empty() { - let dir = dc_ensure_no_slash_safe(blobdir).strdup(); - *context.blobdir.write().unwrap() = dir; - } else { - let dir = dbfile.to_string() + "-blobs"; - dc_create_folder(context, &dir); - *context.blobdir.write().unwrap() = dir.strdup(); - } - // Create/open sqlite database, this may already use the blobdir - let dbfile_path = std::path::Path::new(dbfile); - if context.sql.open(context, dbfile_path, 0) { - success = true - } - if !success { - dc_close(context); - } - success -} - pub unsafe fn dc_get_blobdir(context: &Context) -> *mut libc::c_char { dc_strdup(*context.blobdir.clone().read().unwrap()) } @@ -607,19 +593,59 @@ pub fn do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) { mod tests { use super::*; + use crate::test_utils::*; + #[test] - fn no_crashes_on_context_deref() { - let ctx = dc_context_new(None, std::ptr::null_mut(), Some("Test OS".into())); - std::mem::drop(ctx); + fn test_wrong_db() { + let tmp = tempfile::tempdir().unwrap(); + let dbfile = tmp.path().join("db.sqlite"); + std::fs::write(&dbfile, b"123").unwrap(); + let res = Context::new(Box::new(|_, _, _, _| 0), "FakeOs".into(), dbfile); + assert!(res.is_err()); } #[test] - fn test_context_double_close() { - let ctx = dc_context_new(None, std::ptr::null_mut(), None); - unsafe { - dc_close(&ctx); - dc_close(&ctx); - } - std::mem::drop(ctx); + fn test_blobdir_exists() { + let tmp = tempfile::tempdir().unwrap(); + let dbfile = tmp.path().join("db.sqlite"); + Context::new(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile).unwrap(); + let blobdir = tmp.path().join("db.sqlite-blobs"); + assert!(blobdir.is_dir()); + } + + #[test] + fn test_wrong_blogdir() { + let tmp = tempfile::tempdir().unwrap(); + let dbfile = tmp.path().join("db.sqlite"); + let blobdir = tmp.path().join("db.sqlite-blobs"); + std::fs::write(&blobdir, b"123").unwrap(); + let res = Context::new(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile); + assert!(res.is_err()); + } + + #[test] + fn test_sqlite_parent_not_exists() { + let tmp = tempfile::tempdir().unwrap(); + let subdir = tmp.path().join("subdir"); + let dbfile = subdir.join("db.sqlite"); + let dbfile2 = dbfile.clone(); + Context::new(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile).unwrap(); + assert!(subdir.is_dir()); + assert!(dbfile2.is_file()); + } + + #[test] + fn test_with_blobdir_not_exists() { + let tmp = tempfile::tempdir().unwrap(); + let dbfile = tmp.path().join("db.sqlite"); + let blobdir = tmp.path().join("blobs"); + let res = Context::with_blobdir(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile, blobdir); + assert!(res.is_err()); + } + + #[test] + fn no_crashes_on_context_deref() { + let t = dummy_context(); + std::mem::drop(t.ctx); } } diff --git a/src/dc_imex.rs b/src/dc_imex.rs index 05aa1f389..9ee239218 100644 --- a/src/dc_imex.rs +++ b/src/dc_imex.rs @@ -1110,7 +1110,7 @@ mod tests { #[test] fn test_render_setup_file() { - let t = test_context(Some(logging_cb)); + let t = test_context(Some(Box::new(logging_cb))); configure_alice_keypair(&t.ctx); let msg = dc_render_setup_file(&t.ctx, "hello").unwrap(); @@ -1128,14 +1128,9 @@ mod tests { assert!(msg.contains("-----END PGP MESSAGE-----\n")); } - unsafe extern "C" fn ac_setup_msg_cb( - ctx: &Context, - evt: Event, - d1: uintptr_t, - d2: uintptr_t, - ) -> uintptr_t { + fn ac_setup_msg_cb(ctx: &Context, evt: Event, d1: uintptr_t, d2: uintptr_t) -> uintptr_t { if evt == Event::GET_STRING && d1 == StockMessage::AcSetupMsgBody.to_usize().unwrap() { - "hello\r\nthere".strdup() as usize + unsafe { "hello\r\nthere".strdup() as usize } } else { logging_cb(ctx, evt, d1, d2) } @@ -1143,7 +1138,7 @@ mod tests { #[test] fn test_render_setup_file_newline_replace() { - let t = test_context(Some(ac_setup_msg_cb)); + let t = test_context(Some(Box::new(ac_setup_msg_cb))); configure_alice_keypair(&t.ctx); let msg = dc_render_setup_file(&t.ctx, "pw").unwrap(); println!("{}", &msg); diff --git a/src/error.rs b/src/error.rs index 8f3bc7838..2ee35ed66 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,6 +22,8 @@ pub enum Error { Image(image_meta::ImageError), #[fail(display = "{:?}", _0)] Utf8(std::str::Utf8Error), + #[fail(display = "{:?}", _0)] + CStringError(crate::dc_tools::CStringError), } pub type Result = std::result::Result; @@ -62,6 +64,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: crate::dc_tools::CStringError) -> Error { + Error::CStringError(err) + } +} + #[macro_export] macro_rules! bail { ($e:expr) => { diff --git a/src/stock.rs b/src/stock.rs index c77081ba3..50d206676 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -237,10 +237,8 @@ mod tests { use super::*; use crate::test_utils::*; - use std::ffi::CString; use crate::constants::DC_CONTACT_ID_SELF; - use crate::context::dc_context_new; use crate::types::uintptr_t; use num_traits::ToPrimitive; @@ -258,19 +256,18 @@ mod tests { #[test] fn test_stock_str() { - let ctx = dc_context_new(None, std::ptr::null_mut(), None); - assert_eq!(ctx.stock_str(StockMessage::NoMessages), "No messages."); + let t = dummy_context(); + assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages."); } - unsafe extern "C" fn test_stock_str_no_fallback_cb( + fn test_stock_str_no_fallback_cb( _ctx: &Context, evt: Event, d1: uintptr_t, _d2: uintptr_t, ) -> uintptr_t { if evt == Event::GET_STRING && d1 == StockMessage::NoMessages.to_usize().unwrap() { - let tmp = CString::new("Hello there").unwrap(); - dc_strdup(tmp.as_ptr()) as usize + unsafe { "Hello there".strdup() as usize } } else { 0 } @@ -278,16 +275,16 @@ mod tests { #[test] fn test_stock_str_no_fallback() { - let t = test_context(Some(test_stock_str_no_fallback_cb)); + let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb))); assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there"); } #[test] fn test_stock_string_repl_str() { - let ctx = dc_context_new(None, std::ptr::null_mut(), None); + let t = dummy_context(); // uses %1$s substitution assert_eq!( - ctx.stock_string_repl_str(StockMessage::Member, "42"), + t.ctx.stock_string_repl_str(StockMessage::Member, "42"), "42 member(s)" ); // We have no string using %1$d to test... @@ -295,36 +292,38 @@ mod tests { #[test] fn test_stock_string_repl_int() { - let ctx = dc_context_new(None, std::ptr::null_mut(), None); + let t = dummy_context(); assert_eq!( - ctx.stock_string_repl_int(StockMessage::Member, 42), + t.ctx.stock_string_repl_int(StockMessage::Member, 42), "42 member(s)" ); } #[test] fn test_stock_string_repl_str2() { - let ctx = dc_context_new(None, std::ptr::null_mut(), None); + let t = dummy_context(); assert_eq!( - ctx.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"), + t.ctx + .stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"), "Response from foo: bar" ); } #[test] fn test_stock_system_msg_simple() { - let ctx = dc_context_new(None, std::ptr::null_mut(), None); + let t = dummy_context(); assert_eq!( - ctx.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0), + t.ctx + .stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0), "Location streaming enabled." ) } #[test] fn test_stock_system_msg_add_member_by_me() { - let ctx = dc_context_new(None, std::ptr::null_mut(), None); + let t = dummy_context(); assert_eq!( - ctx.stock_system_msg( + t.ctx.stock_system_msg( StockMessage::MsgAddMember, "alice@example.com", "", diff --git a/src/test_utils.rs b/src/test_utils.rs index 1749b81ba..c6d6caaf4 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -9,9 +9,8 @@ use tempfile::{tempdir, TempDir}; use crate::config::Config; use crate::constants::{Event, KeyType}; -use crate::context::{dc_context_new, dc_open, Context}; +use crate::context::{Context, ContextCallback}; use crate::key; -use crate::types::dc_callback_t; /// A Context and temporary directory. /// @@ -28,18 +27,15 @@ pub struct TestContext { /// "db.sqlite" in the [TestContext.dir] directory. /// /// [Context]: crate::context::Context -pub fn test_context(cb: Option) -> TestContext { - unsafe { - let mut ctx = dc_context_new(cb, std::ptr::null_mut(), None); - let dir = tempdir().unwrap(); - let dbfile = dir.path().join("db.sqlite"); - assert!( - dc_open(&mut ctx, dbfile.to_str().unwrap(), None), - "Failed to open {}", - dbfile.display(), - ); - TestContext { ctx: ctx, dir: dir } - } +pub fn test_context(callback: Option>) -> TestContext { + let dir = tempdir().unwrap(); + let dbfile = dir.path().join("db.sqlite"); + let cb: Box = match callback { + Some(cb) => cb, + None => Box::new(|_, _, _, _| 0), + }; + let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap(); + TestContext { ctx: ctx, dir: dir } } /// Return a dummy [TestContext]. @@ -51,13 +47,8 @@ pub fn dummy_context() -> TestContext { test_context(None) } -pub unsafe extern "C" fn logging_cb( - _ctx: &Context, - evt: Event, - _d1: uintptr_t, - d2: uintptr_t, -) -> uintptr_t { - let to_str = |x| CStr::from_ptr(x as *const libc::c_char).to_str().unwrap(); +pub fn logging_cb(_ctx: &Context, evt: Event, _d1: uintptr_t, d2: uintptr_t) -> uintptr_t { + let to_str = unsafe { |x| CStr::from_ptr(x as *const libc::c_char).to_str().unwrap() }; match evt { Event::INFO => println!("I: {}", to_str(d2)), Event::WARNING => println!("W: {}", to_str(d2)), diff --git a/src/types.rs b/src/types.rs index 41f9bb50f..94ab0d072 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,21 +1,9 @@ #![allow(non_camel_case_types)] -use crate::constants::Event; use crate::context::Context; pub use mmime::clist::*; pub use rusqlite::ffi::*; -/// Callback function that should be given to dc_context_new(). -/// -/// @memberof Context -/// @param context The context object as returned by dc_context_new(). -/// @param event one of the @ref DC_EVENT constants -/// @param data1 depends on the event parameter -/// @param data2 depends on the event parameter -/// @return return 0 unless stated otherwise in the event parameter documentation -pub type dc_callback_t = - unsafe extern "C" fn(_: &Context, _: Event, _: uintptr_t, _: uintptr_t) -> uintptr_t; - pub type dc_receive_imf_t = unsafe fn( _: &Context, _: *const libc::c_char, diff --git a/tests/stress.rs b/tests/stress.rs index 6a307955a..de4d296f1 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -30,103 +30,101 @@ static mut S_EM_SETUPFILE: *const libc::c_char = as *const u8 as *const libc::c_char; unsafe fn stress_functions(context: &Context) { - if 0 != dc_is_open(context) { - if dc_file_exist(context, "$BLOBDIR/foobar") - || dc_file_exist(context, "$BLOBDIR/dada") - || dc_file_exist(context, "$BLOBDIR/foobar.dadada") - || dc_file_exist(context, "$BLOBDIR/foobar-folder") - { - dc_delete_file(context, "$BLOBDIR/foobar"); - dc_delete_file(context, "$BLOBDIR/dada"); - dc_delete_file(context, "$BLOBDIR/foobar.dadada"); - dc_delete_file(context, "$BLOBDIR/foobar-folder"); - } - dc_write_file( - context, - b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char, - b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void, - 7i32 as size_t, - ); - assert!(dc_file_exist(context, "$BLOBDIR/foobar",)); - assert!(!dc_file_exist(context, "$BLOBDIR/foobarx")); - assert_eq!( - dc_get_filebytes(context, "$BLOBDIR/foobar",), - 7i32 as libc::c_ulonglong - ); - - let abs_path: *mut libc::c_char = dc_mprintf( - b"%s/%s\x00" as *const u8 as *const libc::c_char, - context.get_blobdir(), - b"foobar\x00" as *const u8 as *const libc::c_char, - ); - assert!(dc_is_blobdir_path(context, as_str(abs_path))); - assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",)); - assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",)); - assert!(dc_file_exist(context, as_path(abs_path))); - free(abs_path as *mut libc::c_void); - assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",)); - assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7); - - let mut buf: *mut libc::c_void = ptr::null_mut(); - let mut buf_bytes: size_t = 0; - - assert_eq!( - dc_read_file( - context, - b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char, - &mut buf, - &mut buf_bytes, - ), - 1 - ); - assert_eq!(buf_bytes, 7); - assert_eq!( - std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(), - "content" - ); - - free(buf as *mut _); - assert!(dc_delete_file(context, "$BLOBDIR/foobar")); - assert!(dc_delete_file(context, "$BLOBDIR/dada")); - assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder")); - assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",)); - assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder")); - let fn0: *mut libc::c_char = dc_get_fine_pathNfilename( - context, - b"$BLOBDIR\x00" as *const u8 as *const libc::c_char, - b"foobar.dadada\x00" as *const u8 as *const libc::c_char, - ); - assert!(!fn0.is_null()); - assert_eq!( - strcmp( - fn0, - b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char, - ), - 0 - ); - dc_write_file( - context, - fn0, - b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void, - 7i32 as size_t, - ); - let fn1: *mut libc::c_char = dc_get_fine_pathNfilename( - context, - b"$BLOBDIR\x00" as *const u8 as *const libc::c_char, - b"foobar.dadada\x00" as *const u8 as *const libc::c_char, - ); - assert!(!fn1.is_null()); - assert_eq!( - strcmp( - fn1, - b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char, - ), - 0 - ); - assert!(dc_delete_file(context, as_path(fn0))); - free(fn0 as *mut libc::c_void); - free(fn1 as *mut libc::c_void); + if dc_file_exist(context, "$BLOBDIR/foobar") + || dc_file_exist(context, "$BLOBDIR/dada") + || dc_file_exist(context, "$BLOBDIR/foobar.dadada") + || dc_file_exist(context, "$BLOBDIR/foobar-folder") + { + dc_delete_file(context, "$BLOBDIR/foobar"); + dc_delete_file(context, "$BLOBDIR/dada"); + dc_delete_file(context, "$BLOBDIR/foobar.dadada"); + dc_delete_file(context, "$BLOBDIR/foobar-folder"); } + dc_write_file( + context, + b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char, + b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void, + 7i32 as size_t, + ); + assert!(dc_file_exist(context, "$BLOBDIR/foobar",)); + assert!(!dc_file_exist(context, "$BLOBDIR/foobarx")); + assert_eq!( + dc_get_filebytes(context, "$BLOBDIR/foobar",), + 7i32 as libc::c_ulonglong + ); + + let abs_path: *mut libc::c_char = dc_mprintf( + b"%s/%s\x00" as *const u8 as *const libc::c_char, + context.get_blobdir(), + b"foobar\x00" as *const u8 as *const libc::c_char, + ); + assert!(dc_is_blobdir_path(context, as_str(abs_path))); + assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",)); + assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",)); + assert!(dc_file_exist(context, as_path(abs_path))); + free(abs_path as *mut libc::c_void); + assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",)); + assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7); + + let mut buf: *mut libc::c_void = ptr::null_mut(); + let mut buf_bytes: size_t = 0; + + assert_eq!( + dc_read_file( + context, + b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char, + &mut buf, + &mut buf_bytes, + ), + 1 + ); + assert_eq!(buf_bytes, 7); + assert_eq!( + std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(), + "content" + ); + + free(buf as *mut _); + assert!(dc_delete_file(context, "$BLOBDIR/foobar")); + assert!(dc_delete_file(context, "$BLOBDIR/dada")); + assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder")); + assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",)); + assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder")); + let fn0: *mut libc::c_char = dc_get_fine_pathNfilename( + context, + b"$BLOBDIR\x00" as *const u8 as *const libc::c_char, + b"foobar.dadada\x00" as *const u8 as *const libc::c_char, + ); + assert!(!fn0.is_null()); + assert_eq!( + strcmp( + fn0, + b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char, + ), + 0 + ); + dc_write_file( + context, + fn0, + b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void, + 7i32 as size_t, + ); + let fn1: *mut libc::c_char = dc_get_fine_pathNfilename( + context, + b"$BLOBDIR\x00" as *const u8 as *const libc::c_char, + b"foobar.dadada\x00" as *const u8 as *const libc::c_char, + ); + assert!(!fn1.is_null()); + assert_eq!( + strcmp( + fn1, + b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char, + ), + 0 + ); + assert!(dc_delete_file(context, as_path(fn0))); + free(fn0 as *mut libc::c_void); + free(fn1 as *mut libc::c_void); let res = context.get_config(config::Config::SysConfigKeys).unwrap(); @@ -567,12 +565,7 @@ fn test_encryption_decryption() { assert_eq!(plain, original_text); } -unsafe extern "C" fn cb( - _context: &Context, - _event: Event, - _data1: uintptr_t, - _data2: uintptr_t, -) -> uintptr_t { +fn cb(_context: &Context, _event: Event, _data1: uintptr_t, _data2: uintptr_t) -> uintptr_t { 0 } @@ -582,21 +575,16 @@ struct TestContext { dir: TempDir, } -unsafe fn create_test_context() -> TestContext { - let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None); +fn create_test_context() -> TestContext { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - assert!( - dc_open(&mut ctx, dbfile.to_str().unwrap(), None), - "Failed to open {}", - dbfile.display() - ); + let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap(); TestContext { ctx: ctx, dir: dir } } #[test] fn test_dc_get_oauth2_url() { - let ctx = unsafe { create_test_context() }; + let ctx = create_test_context(); let addr = "dignifiedquire@gmail.com"; let redirect_uri = "chat.delta:/com.b44t.messenger"; let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri); @@ -606,7 +594,7 @@ fn test_dc_get_oauth2_url() { #[test] fn test_dc_get_oauth2_addr() { - let ctx = unsafe { create_test_context() }; + let ctx = create_test_context(); let addr = "dignifiedquire@gmail.com"; let code = "fail"; let res = dc_get_oauth2_addr(&ctx.ctx, addr, code); @@ -616,7 +604,7 @@ fn test_dc_get_oauth2_addr() { #[test] fn test_dc_get_oauth2_token() { - let ctx = unsafe { create_test_context() }; + let ctx = create_test_context(); let addr = "dignifiedquire@gmail.com"; let code = "fail"; let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, 0); @@ -634,50 +622,33 @@ fn test_stress_tests() { #[test] fn test_get_contacts() { - unsafe { - let context = create_test_context(); - let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap(); - assert_eq!(contacts.len(), 0); + let context = create_test_context(); + let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap(); + assert_eq!(contacts.len(), 0); - let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap(); - assert_ne!(id, 0); + let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap(); + assert_ne!(id, 0); - let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap(); - assert_eq!(contacts.len(), 1); + let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap(); + assert_eq!(contacts.len(), 1); - let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap(); - assert_eq!(contacts.len(), 0); - } + let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap(); + assert_eq!(contacts.len(), 0); } #[test] fn test_chat() { - unsafe { - let context = create_test_context(); - let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap(); - assert_ne!(contact1, 0); + let context = create_test_context(); + let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap(); + assert_ne!(contact1, 0); - let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap(); - assert!(chat_id > 9, "chat_id too small {}", chat_id); - let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap(); + let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap(); + assert!(chat_id > 9, "chat_id too small {}", chat_id); + let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap(); - let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap(); - assert_eq!(chat2_id, chat_id); - let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap(); + let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap(); + assert_eq!(chat2_id, chat_id); + let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap(); - assert_eq!(chat2.name, chat.name); - } -} - -#[test] -fn test_wrong_db() { - unsafe { - let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None); - let dir = tempdir().unwrap(); - let dbfile = dir.path().join("db.sqlite"); - std::fs::write(&dbfile, b"123").unwrap(); - - let res = dc_open(&mut ctx, dbfile.to_str().unwrap(), None); - assert!(!res); - } + assert_eq!(chat2.name, chat.name); }