feat(file_store)!: have separate methods for creating and opening Store

This commit is contained in:
志宇 2023-10-30 11:02:50 +08:00
parent d294e2e318
commit 24994a3ed4
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
5 changed files with 64 additions and 38 deletions

View File

@ -51,20 +51,55 @@ impl<'a, C> Store<'a, C>
where where
C: Default + Append + serde::Serialize + serde::de::DeserializeOwned, 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 /// [`open`]: Store::open
/// returned. 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`].
/// ///
/// [`File`]: std::fs::File /// Use [`create_new`] to create a new `Store`.
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()]; 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 { if magic_buf != magic {
return Err(FileError::InvalidMagicBytes { return Err(FileError::InvalidMagicBytes {
got: magic_buf, got: magic_buf,
@ -74,35 +109,26 @@ where
Ok(Self { Ok(Self {
magic, magic,
db_file, db_file: f,
marker: Default::default(), marker: Default::default(),
}) })
} }
/// Creates or loads a store from `db_path`. /// Attempt to open existing [`Store`] file; create it if the file is non-existant.
/// ///
/// If no file exists there, it will be created. /// Internally, this calls either [`open`] or [`create_new`].
/// ///
/// Refer to [`new`] for documentation on the `magic` input. /// [`open`]: Store::open
/// /// [`create_new`]: Store::create_new
/// [`new`]: Self::new pub fn open_or_create_new<P>(magic: &'a [u8], file_path: P) -> Result<Self, FileError>
pub fn new_from_path<P>(magic: &'a [u8], db_path: P) -> Result<Self, FileError>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
let already_exists = db_path.as_ref().exists(); if file_path.as_ref().exists() {
Self::open(magic, file_path)
let mut db_file = OpenOptions::new() } else {
.read(true) Self::create_new(magic, file_path)
.write(true)
.create(true)
.open(db_path)?;
if !already_exists {
db_file.write_all(magic)?;
} }
Self::new(magic, db_file)
} }
/// Iterates over the stored changeset from first to last, changing the seek position at each /// 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(); let mut file = NamedTempFile::new().unwrap();
file.write_all(&TEST_MAGIC_BYTES).expect("should write"); file.write_all(&TEST_MAGIC_BYTES).expect("should write");
let mut db = Store::<TestChangeSet>::new(&TEST_MAGIC_BYTES, file.reopen().unwrap()) let mut db =
.expect("must open"); Store::<TestChangeSet>::open(&TEST_MAGIC_BYTES, file.path()).expect("must open");
assert!(db.is_empty().expect("must read")); assert!(db.is_empty().expect("must read"));
db.write_changes(&vec!["hello".to_string(), "world".to_string()]) db.write_changes(&vec!["hello".to_string(), "world".to_string()])
.expect("must write"); .expect("must write");
@ -209,7 +235,7 @@ mod test {
file.write_all(&TEST_MAGIC_BYTES[..TEST_MAGIC_BYTES_LEN - 1]) file.write_all(&TEST_MAGIC_BYTES[..TEST_MAGIC_BYTES_LEN - 1])
.expect("should write"); .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), Err(FileError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof),
unexpected => panic!("unexpected result: {:?}", unexpected), unexpected => panic!("unexpected result: {:?}", unexpected),
}; };
@ -223,7 +249,7 @@ mod test {
file.write_all(invalid_magic_bytes.as_bytes()) file.write_all(invalid_magic_bytes.as_bytes())
.expect("should write"); .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, .. }) => { Err(FileError::InvalidMagicBytes { got, .. }) => {
assert_eq!(got, invalid_magic_bytes.as_bytes()) assert_eq!(got, invalid_magic_bytes.as_bytes())
} }
@ -242,8 +268,8 @@ mod test {
let mut file = NamedTempFile::new().unwrap(); let mut file = NamedTempFile::new().unwrap();
file.write_all(&data).expect("should write"); file.write_all(&data).expect("should write");
let mut store = Store::<TestChangeSet>::new(&TEST_MAGIC_BYTES, file.reopen().unwrap()) let mut store =
.expect("should open"); Store::<TestChangeSet>::open(&TEST_MAGIC_BYTES, file.path()).expect("should open");
match store.iter_changesets().next() { match store.iter_changesets().next() {
Some(Err(IterError::Bincode(_))) => {} Some(Err(IterError::Bincode(_))) => {}
unexpected_res => panic!("unexpected result: {:?}", unexpected_res), unexpected_res => panic!("unexpected result: {:?}", unexpected_res),

View File

@ -681,7 +681,7 @@ where
index.add_keychain(Keychain::Internal, internal_descriptor); 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, Ok(db_backend) => db_backend,
// we cannot return `err` directly as it has lifetime `'m` // we cannot return `err` directly as it has lifetime `'m`
Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)), Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)),

View File

@ -18,7 +18,7 @@ use bdk_file_store::Store;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let db_path = std::env::temp_dir().join("bdk-electrum-example"); 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 external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";

View File

@ -17,7 +17,7 @@ const PARALLEL_REQUESTS: usize = 5;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db_path = std::env::temp_dir().join("bdk-esplora-async-example"); 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 external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";

View File

@ -16,7 +16,7 @@ use bdk_file_store::Store;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let db_path = std::env::temp_dir().join("bdk-esplora-example"); 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 external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";