From: 志宇 Date: Mon, 30 Oct 2023 03:02:50 +0000 (+0800) Subject: feat(file_store)!: have separate methods for creating and opening Store X-Git-Tag: v1.0.0-alpha.3~17^2~7 X-Git-Url: http://internal-gitweb-vhost/script/%22https:/struct.CommandStringError.html?a=commitdiff_plain;h=24994a3ed47aacf203ca0b456eb22f4b93ec58a8;p=bdk feat(file_store)!: have separate methods for creating and opening Store --- diff --git a/crates/file_store/src/store.rs b/crates/file_store/src/store.rs index fa73480e..22e8b39d 100644 --- a/crates/file_store/src/store.rs +++ b/crates/file_store/src/store.rs @@ -51,20 +51,55 @@ impl<'a, C> Store<'a, C> where C: Default + Append + serde::Serialize + serde::de::DeserializeOwned, { - /// Creates a new store from a [`File`]. + /// Create a new [`Store`] file in write-only mode; error if the file exists. /// - /// The file must have been opened with read and write permissions. + /// `magic` is the prefixed bytes to write to the new file. This will be checked when opening + /// the `Store` in the future with [`open`]. /// - /// `magic` is the expected prefixed bytes of the file. If this does not match, an error will be - /// returned. + /// [`open`]: Store::open + pub fn create_new

(magic: &'a [u8], file_path: P) -> Result + where + P: AsRef, + { + if file_path.as_ref().exists() { + // `io::Error` is used instead of a variant on `FileError` because there is already a + // nightly-only `File::create_new` method + return Err(FileError::Io(io::Error::new( + io::ErrorKind::Other, + "file already exists", + ))); + } + let mut f = OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(file_path)?; + f.write_all(magic)?; + Ok(Self { + magic, + db_file: f, + marker: Default::default(), + }) + } + + /// Open an existing [`Store`]. + /// + /// Use [`create_new`] to create a new `Store`. /// - /// [`File`]: std::fs::File - pub fn new(magic: &'a [u8], mut db_file: File) -> Result { - db_file.rewind()?; + /// # Errors + /// + /// If the prefixed bytes of the opened file does not match the provided `magic`, the + /// [`FileError::InvalidMagicBytes`] error variant will be returned. + /// + /// [`create_new`]: Store::create_new + pub fn open

(magic: &'a [u8], file_path: P) -> Result + where + P: AsRef, + { + let mut f = OpenOptions::new().read(true).write(true).open(file_path)?; let mut magic_buf = vec![0_u8; magic.len()]; - db_file.read_exact(magic_buf.as_mut())?; - + f.read_exact(&mut magic_buf)?; if magic_buf != magic { return Err(FileError::InvalidMagicBytes { got: magic_buf, @@ -74,35 +109,26 @@ where Ok(Self { magic, - db_file, + db_file: f, marker: Default::default(), }) } - /// Creates or loads a store from `db_path`. - /// - /// If no file exists there, it will be created. + /// Attempt to open existing [`Store`] file; create it if the file is non-existant. /// - /// Refer to [`new`] for documentation on the `magic` input. + /// Internally, this calls either [`open`] or [`create_new`]. /// - /// [`new`]: Self::new - pub fn new_from_path

(magic: &'a [u8], db_path: P) -> Result + /// [`open`]: Store::open + /// [`create_new`]: Store::create_new + pub fn open_or_create_new

(magic: &'a [u8], file_path: P) -> Result where P: AsRef, { - let already_exists = db_path.as_ref().exists(); - - let mut db_file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(db_path)?; - - if !already_exists { - db_file.write_all(magic)?; + if file_path.as_ref().exists() { + Self::open(magic, file_path) + } else { + Self::create_new(magic, file_path) } - - Self::new(magic, db_file) } /// Iterates over the stored changeset from first to last, changing the seek position at each @@ -195,8 +221,8 @@ mod test { let mut file = NamedTempFile::new().unwrap(); file.write_all(&TEST_MAGIC_BYTES).expect("should write"); - let mut db = Store::::new(&TEST_MAGIC_BYTES, file.reopen().unwrap()) - .expect("must open"); + let mut db = + Store::::open(&TEST_MAGIC_BYTES, file.path()).expect("must open"); assert!(db.is_empty().expect("must read")); db.write_changes(&vec!["hello".to_string(), "world".to_string()]) .expect("must write"); @@ -209,7 +235,7 @@ mod test { file.write_all(&TEST_MAGIC_BYTES[..TEST_MAGIC_BYTES_LEN - 1]) .expect("should write"); - match Store::::new(&TEST_MAGIC_BYTES, file.reopen().unwrap()) { + match Store::::open(&TEST_MAGIC_BYTES, file.path()) { Err(FileError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof), unexpected => panic!("unexpected result: {:?}", unexpected), }; @@ -223,7 +249,7 @@ mod test { file.write_all(invalid_magic_bytes.as_bytes()) .expect("should write"); - match Store::::new(&TEST_MAGIC_BYTES, file.reopen().unwrap()) { + match Store::::open(&TEST_MAGIC_BYTES, file.path()) { Err(FileError::InvalidMagicBytes { got, .. }) => { assert_eq!(got, invalid_magic_bytes.as_bytes()) } @@ -242,8 +268,8 @@ mod test { let mut file = NamedTempFile::new().unwrap(); file.write_all(&data).expect("should write"); - let mut store = Store::::new(&TEST_MAGIC_BYTES, file.reopen().unwrap()) - .expect("should open"); + let mut store = + Store::::open(&TEST_MAGIC_BYTES, file.path()).expect("should open"); match store.iter_changesets().next() { Some(Err(IterError::Bincode(_))) => {} unexpected_res => panic!("unexpected result: {:?}", unexpected_res), diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 36499803..0b5d9cd3 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -681,7 +681,7 @@ where index.add_keychain(Keychain::Internal, internal_descriptor); } - let mut db_backend = match Store::<'m, C>::new_from_path(db_magic, &args.db_path) { + let mut db_backend = match Store::<'m, C>::open_or_create_new(db_magic, &args.db_path) { Ok(db_backend) => db_backend, // we cannot return `err` directly as it has lifetime `'m` Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)), diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index da0c5e5d..471caccf 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -18,7 +18,7 @@ use bdk_file_store::Store; fn main() -> Result<(), Box> { let db_path = std::env::temp_dir().join("bdk-electrum-example"); - let mut db = Store::::new_from_path(DB_MAGIC.as_bytes(), db_path)?; + let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index e44db9d8..72a9911d 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -17,7 +17,7 @@ const PARALLEL_REQUESTS: usize = 5; #[tokio::main] async fn main() -> Result<(), Box> { let db_path = std::env::temp_dir().join("bdk-esplora-async-example"); - let mut db = Store::::new_from_path(DB_MAGIC.as_bytes(), db_path)?; + let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index ec107659..aa434b63 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -16,7 +16,7 @@ use bdk_file_store::Store; fn main() -> Result<(), Box> { let db_path = std::env::temp_dir().join("bdk-esplora-example"); - let mut db = Store::::new_from_path(DB_MAGIC.as_bytes(), db_path)?; + let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";