1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
use std::io::{Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use bonsaidb_files::{BonsaiFiles, FileConfig, FilesSchema};
use bonsaidb_local::config::{Builder, StorageConfiguration};
use bonsaidb_local::Database;
#[cfg_attr(test, test)]
fn main() -> anyhow::Result<()> {
// Create a database for our files. If you would like to use these
// collections in an existing datasbase/schema, `BonsaiFiles` exposes a
// function `define_collections()` which can be called from your
// `Schema::define_collections()` implementation.
//
// Or, if you're using the Schema derive macro, you can add a parameter
// `include = [FilesSchema<BonsaiFiles>]` to use BonsaiFiles within your
// existing schema.
let database = Database::open::<FilesSchema<BonsaiFiles>>(StorageConfiguration::new(
"basic-files.bonsaidb",
))?;
// This crate provides a very basic path-based file storage. Documents can
// be up to 4GB in size, but must be loaded completely to access. Files
// stored using `bonsaidb-files` are broken into blocks and can be streamed
// and/or randomly accessed.
//
// The `BonsaiFiles` type implements `FileConfig` and defines a block size
// of 64kb.
let mut one_megabyte = Vec::with_capacity(1024 * 1024);
for i in 0..one_megabyte.capacity() / size_of::<u32>() {
// Each u32 in the file will be the current offset in the file.
let offset = u32::try_from(i * size_of::<u32>()).unwrap();
one_megabyte.extend(offset.to_be_bytes());
}
let mut file = BonsaiFiles::build("some-file")
.contents(&one_megabyte)
.create(&database)?;
// By default, files will be stored at the root level:
assert_eq!(file.path(), "/some-file");
// We can access this file's contents using `std::io::Read` and
// `std::io::Seek`.
let mut contents = file.contents()?;
assert_eq!(contents.len(), u64::try_from(one_megabyte.len()).unwrap());
contents.seek(SeekFrom::Start(1024))?;
let mut offset = [0; size_of::<u32>()];
contents.read_exact(&mut offset)?;
let offset = u32::from_be_bytes(offset);
assert_eq!(offset, 1024);
drop(contents);
// Files can be appended to, but existing contents cannot be modified.
// `File::append()` can be used to write data that you have in memory.
// Alternatively, a buffered writer can be used to write larger amounts of
// data using `std::io::Write`.
let mut writer = file.append_buffered();
let mut reader = &one_megabyte[..];
let bytes_written = std::io::copy(&mut reader, &mut writer)?;
assert_eq!(bytes_written, u64::try_from(one_megabyte.len()).unwrap());
writer.flush()?;
// The writer will attempt to flush on drop if there are any bytes remaining
// in the buffer. Any errors will be ignored, however, so it is safer to
// flush where you can handle the error.
drop(writer);
// Verify the file has the new contents.
let contents = file.contents()?;
assert_eq!(
contents.len(),
u64::try_from(one_megabyte.len()).unwrap() * 2
);
// Clean up the file.
file.delete()?;
Ok(())
}