]> Untitled Git - bdk/commitdiff
feat(file_store)!: have separate methods for creating and opening Store
author志宇 <hello@evanlinjin.me>
Mon, 30 Oct 2023 03:02:50 +0000 (11:02 +0800)
committer志宇 <hello@evanlinjin.me>
Wed, 15 Nov 2023 23:07:48 +0000 (07:07 +0800)
crates/file_store/src/store.rs
example-crates/example_cli/src/lib.rs
example-crates/wallet_electrum/src/main.rs
example-crates/wallet_esplora_async/src/main.rs
example-crates/wallet_esplora_blocking/src/main.rs

index fa73480e5202e4d675ef6ae4c90e3eb345546780..22e8b39d68610e9f79c9992050b32577635bd741 100644 (file)
@@ -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<P>(magic: &'a [u8], file_path: P) -> Result<Self, FileError>
+    where
+        P: AsRef<Path>,
+    {
+        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<Self, FileError> {
-        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<P>(magic: &'a [u8], file_path: P) -> Result<Self, FileError>
+    where
+        P: AsRef<Path>,
+    {
+        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<P>(magic: &'a [u8], db_path: P) -> Result<Self, FileError>
+    /// [`open`]: Store::open
+    /// [`create_new`]: Store::create_new
+    pub fn open_or_create_new<P>(magic: &'a [u8], file_path: P) -> Result<Self, FileError>
     where
         P: AsRef<Path>,
     {
-        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::<TestChangeSet>::new(&TEST_MAGIC_BYTES, file.reopen().unwrap())
-            .expect("must open");
+        let mut db =
+            Store::<TestChangeSet>::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::<TestChangeSet>::new(&TEST_MAGIC_BYTES, file.reopen().unwrap()) {
+        match Store::<TestChangeSet>::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::<TestChangeSet>::new(&TEST_MAGIC_BYTES, file.reopen().unwrap()) {
+        match Store::<TestChangeSet>::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::<TestChangeSet>::new(&TEST_MAGIC_BYTES, file.reopen().unwrap())
-            .expect("should open");
+        let mut store =
+            Store::<TestChangeSet>::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),
index 3649980346ad1e6de793974d8e1c1cc0a395f9b7..0b5d9cd37c841ccca38149701480cdec2b0d64fd 100644 (file)
@@ -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)),
index da0c5e5dd3835f0facf150f0710f97b9e0cd829c..471caccf742b3edcde4b14cd897beab1b7e099f7 100644 (file)
@@ -18,7 +18,7 @@ use bdk_file_store::Store;
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let db_path = std::env::temp_dir().join("bdk-electrum-example");
-    let mut db = Store::<bdk::wallet::ChangeSet>::new_from_path(DB_MAGIC.as_bytes(), db_path)?;
+    let mut db = Store::<bdk::wallet::ChangeSet>::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/*)";
 
index e44db9d8d65eed1950b706eb3b84bd6964695135..72a9911d957953e7a0a14b2775a97f56484c12fb 100644 (file)
@@ -17,7 +17,7 @@ const PARALLEL_REQUESTS: usize = 5;
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
     let db_path = std::env::temp_dir().join("bdk-esplora-async-example");
-    let mut db = Store::<bdk::wallet::ChangeSet>::new_from_path(DB_MAGIC.as_bytes(), db_path)?;
+    let mut db = Store::<bdk::wallet::ChangeSet>::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/*)";
 
index ec1076597aa1a7fcf686224ba58a70dafd573b32..aa434b639fa8b0d79518ff539c36b45938515d7b 100644 (file)
@@ -16,7 +16,7 @@ use bdk_file_store::Store;
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let db_path = std::env::temp_dir().join("bdk-esplora-example");
-    let mut db = Store::<bdk::wallet::ChangeSet>::new_from_path(DB_MAGIC.as_bytes(), db_path)?;
+    let mut db = Store::<bdk::wallet::ChangeSet>::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/*)";