diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index f1f8697b8..950a8a089 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -24,7 +24,7 @@ use num_traits::{FromPrimitive, ToPrimitive}; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::contact::Contact; -use deltachat::context::Context; +use deltachat::context::{Context, ContextBuilder}; use deltachat::dc_tools::{ as_path, dc_strdup, to_opt_string_lossy, to_string_lossy, OsStrExt, StrExt, }; @@ -248,22 +248,15 @@ pub unsafe extern "C" fn dc_open( } let ffi_context = &*context; let rust_cb = move |_ctx: &Context, evt: Event| ffi_context.translate_cb(evt); - - let ctx = if blobdir.is_null() || *blobdir == 0 { - Context::new( - Box::new(rust_cb), - ffi_context.os_name.clone(), - as_path(dbfile).to_path_buf(), - ) - } else { - Context::with_blobdir( - Box::new(rust_cb), - ffi_context.os_name.clone(), - as_path(dbfile).to_path_buf(), - as_path(blobdir).to_path_buf(), - ) - }; - match ctx { + let mut builder = ContextBuilder::new( + Box::new(rust_cb), + ffi_context.os_name.clone(), + as_path(dbfile).to_path_buf(), + ); + if !blobdir.is_null() && *blobdir != 0 { + builder = builder.blobdir(as_path(blobdir).to_path_buf()); + } + match builder.create() { Ok(ctx) => { let mut inner_guard = ffi_context.inner.write().unwrap(); *inner_guard = Some(ctx); diff --git a/examples/repl/main.rs b/examples/repl/main.rs index bc7528c5d..ef6a05c68 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -366,11 +366,12 @@ fn main_0(args: Vec) -> Result<(), failure::Error> { println!("Error: Bad arguments, expected [db-name]."); return Err(format_err!("No db-name specified")); } - let context = Context::new( + let context = ContextBuilder::new( Box::new(receive_event), "CLI".into(), Path::new(&args[1]).to_path_buf(), - )?; + ) + .create()?; println!("Delta Chat Core is awaiting your commands."); diff --git a/examples/simple.rs b/examples/simple.rs index 67c8fbbc7..85835f951 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -39,8 +39,9 @@ fn main() { 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 ctx = ContextBuilder::new(Box::new(cb), "FakeOs".into(), dbfile) + .create() + .expect("Failed to create context"); let running = Arc::new(RwLock::new(true)); let info = ctx.get_info(); let duration = time::Duration::from_millis(4000); diff --git a/src/context.rs b/src/context.rs index 8d2d10e95..dc893776b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -94,30 +94,92 @@ pub fn get_info() -> HashMap<&'static str, String> { res } -impl Context { - /// Creates new 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()); +/// Builder for [Context]. +/// +/// # Example +/// +/// ``` +/// use deltachat::context::ContextBuilder; +/// let dir = tempfile::tempdir().unwrap(); +/// let dbfile = dir.path().join("my-context.db"); +/// let ctx = ContextBuilder::new(Box::new(|_, _| 0), "AppName".into(), dbfile) +/// .blobdir(dir.path().join("my-context-blobs")) +/// .logdir(dir.path().join("my-context-logs")) +/// .create() +/// .unwrap(); +/// assert_eq!(ctx.get_blobdir(), dir.path().join("my-context-blobs")); +/// ``` +#[derive(DebugStub)] +pub struct ContextBuilder { + #[debug_stub = "Callback"] + cb: Box, + os_name: String, + dbfile: PathBuf, + blobdir: PathBuf, + logdir: PathBuf, +} + +impl ContextBuilder { + pub fn new(cb: Box, os_name: String, dbfile: PathBuf) -> Self { + let db_fname = OsString::from(dbfile.file_name().unwrap_or(&OsString::from("dc-context"))); + let mut blob_fname = db_fname.clone(); blob_fname.push("-blobs"); - let blobdir = dbfile.with_file_name(blob_fname); - if !blobdir.exists() { - std::fs::create_dir_all(&blobdir)?; + let mut log_fname = db_fname.clone(); + log_fname.push("-logs"); + ContextBuilder { + cb, + os_name, + blobdir: dbfile.with_file_name(blob_fname), + logdir: dbfile.with_file_name(log_fname), + dbfile, } - Context::with_blobdir(cb, os_name, dbfile, blobdir) } - pub fn with_blobdir( + pub fn blobdir(mut self, path: PathBuf) -> Self { + self.blobdir = path; + self + } + + pub fn logdir(mut self, path: PathBuf) -> Self { + self.logdir = path; + self + } + + pub fn create(self) -> Result { + if !self.blobdir.exists() { + std::fs::create_dir_all(&self.blobdir)?; + } + if !self.logdir.exists() { + std::fs::create_dir_all(&self.logdir)?; + } + Context::new( + self.cb, + self.os_name, + self.dbfile, + self.blobdir, + self.logdir, + ) + } +} + +impl Context { + fn new( cb: Box, os_name: String, dbfile: PathBuf, blobdir: PathBuf, + logdir: PathBuf, ) -> Result { ensure!( blobdir.is_dir(), "Blobdir does not exist: {}", blobdir.display() ); + ensure!( + logdir.is_dir(), + "Logdir does not exist: {}", + logdir.display() + ); let ctx = Context { blobdir, dbfile, @@ -526,7 +588,7 @@ mod tests { 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); + let res = ContextBuilder::new(Box::new(|_, _| 0), "FakeOs".into(), dbfile).create(); assert!(res.is_err()); } @@ -541,7 +603,9 @@ mod tests { 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(); + ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile) + .create() + .unwrap(); let blobdir = tmp.path().join("db.sqlite-blobs"); assert!(blobdir.is_dir()); } @@ -552,7 +616,7 @@ mod tests { 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); + let res = ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).create(); assert!(res.is_err()); } @@ -562,7 +626,9 @@ mod tests { 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(); + ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile) + .create() + .unwrap(); assert!(subdir.is_dir()); assert!(dbfile2.is_file()); } @@ -572,7 +638,9 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = PathBuf::new(); - let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir); + let res = ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile) + .blobdir(blobdir) + .create(); assert!(res.is_err()); } @@ -581,10 +649,36 @@ mod tests { 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); + ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile) + .blobdir(blobdir.clone()) + .create() + .unwrap(); + assert!(blobdir.is_dir()); + } + + #[test] + fn test_with_empty_logdir() { + let tmp = tempfile::tempdir().unwrap(); + let dbfile = tmp.path().join("db.sqlite"); + let logdir = PathBuf::new(); + let res = ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile) + .logdir(logdir) + .create(); assert!(res.is_err()); } + #[test] + fn test_with_logdir_not_exists() { + let tmp = tempfile::tempdir().unwrap(); + let dbfile = tmp.path().join("db.sqlite"); + let logdir = tmp.path().join("logs"); + ContextBuilder::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile) + .logdir(logdir.clone()) + .create() + .unwrap(); + assert!(logdir.is_dir()); + } + #[test] fn no_crashes_on_context_deref() { let t = dummy_context(); diff --git a/src/test_utils.rs b/src/test_utils.rs index 0be80ce2d..0a5fa5d80 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -7,7 +7,7 @@ use tempfile::{tempdir, TempDir}; use crate::config::Config; use crate::constants::KeyType; -use crate::context::{Context, ContextCallback}; +use crate::context::{Context, ContextBuilder, ContextCallback}; use crate::events::Event; use crate::key; @@ -33,7 +33,9 @@ pub fn test_context(callback: Option>) -> TestContext { Some(cb) => cb, None => Box::new(|_, _| 0), }; - let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap(); + let ctx = ContextBuilder::new(cb, "FakeOs".into(), dbfile) + .create() + .unwrap(); TestContext { ctx: ctx, dir: dir } } diff --git a/tests/stress.rs b/tests/stress.rs index 5ce1b8e45..a1a25d406 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -219,7 +219,9 @@ struct TestContext { fn create_test_context() -> TestContext { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap(); + let ctx = ContextBuilder::new(Box::new(cb), "FakeOs".into(), dbfile) + .create() + .unwrap(); TestContext { ctx, dir } }