#settings #file-storage #bincode #fs #filesystem #single-file

bstorage

一个用于处理应用程序配置文件的轻量级库

3 个不稳定版本

0.2.1 2024年7月16日
0.2.0 2024年7月14日
0.1.0 2024年7月14日

#440 in 解析器实现

Download history 364/week @ 2024-07-11 51/week @ 2024-07-18 7/week @ 2024-07-25 2/week @ 2024-08-01

每月424次下载
用于 fshasher

Apache-2.0

57KB
907

LICENSE Crates.io

bstorage 是一个用于以二进制形式存储数据的轻量级库。

bstorage 创建一个文件存储,并允许将任何数据写入其中。对数据的唯一要求是实现 serde::Deserializeserde::Serialize

use bstorage::Storage;
use serde::{Deserialize, Serialize};
use std::env::temp_dir;
use uuid::Uuid;

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct MyRecord {
    field_a: String,
    field_b: Option<u8>,
}

// Set storage path
let storage_path = temp_dir().join(Uuid::new_v4().to_string());
// Create storage or open existed one
let mut storage = Storage::create(storage_path).expect("Storage created");
let my_record = MyRecord {
    field_a: "Hello World!".to_owned(),
    field_b: Some(255),
};
// Save record into storage
storage
    .set("my_record", &my_record)
    .expect("Record is saved");
// Read record from storage
let recovered: MyRecord = storage
    .get("my_record")
    .expect("Record is read")
    .expect("Record exists");
assert_eq!(my_record, recovered)

何时使用和何时不使用

bstorage 是一个不错的选择,用于

  • 保存应用程序设置
  • 保存临时数据
  • 其他用途

bstorage 不适合以下情况

  • 如果您在寻找类似轻量级数据库的东西;bstorage 不是一个数据库
  • 如果您需要在保存数据的数组中进行快速搜索而不复制数据(同样,bstorage 不是一个数据库)
  • 用户直接访问数据(例如,toml,json 和其他文本格式)
  • 需要保存大量数据(1 GB 或更多)

工作原理

bstorage 使用 bincode 包进行数据的序列化和反序列化。每个记录作为目录中单独的文件保存,该目录是在创建/打开存储时指定的。因此,记录的数量将等同于存储中的文件数量。

将每个记录存储为单独文件的战略是由确保向存储写入数据时性能达到最大化的需求所驱动的。如果所有数据(所有记录)都存储在一个文件中,那么看似简单的“更新一条记录”的任务将不会简单。在文件系统级别,数据以块的形式存储,并且“干净”地替换文件内容的一部分将需要干预文件系统的操作,这在大多数情况下是不必要的复杂。最简单、最可靠和最有效的方法是覆盖整个文件。然而,这种方法会导致存储大小问题。对于大型存储,覆盖整个存储变得非常昂贵。

这就是为什么 bstorage 为每个记录创建一个单独的文件,允许以最快的速度更新每个记录,而无需覆盖整个存储。

如何转移存储

可以通过复制存储目录的全部内容来转移存储。然而,在某些情况下,这可能会相当不便,尤其是在数据需要通过网络传输时。

bstorage 包含一个 Bundle 特性,允许将整个存储“打包”成一个单独的文件,然后将其“解包”回其“正常”状态。

use bstorage::{Bundle, Storage};
use serde::{Deserialize, Serialize};
use std::{
    env::temp_dir,
    fs::{remove_dir_all, remove_file},
};
use uuid::Uuid;

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct MyRecord {
    field_a: String,
    field_b: Option<u8>,
}

// Set storage path
let storage_path = temp_dir().join(Uuid::new_v4().to_string());
// Create storage or open existed one
let mut storage = Storage::create(&storage_path).expect("Storage created");
let my_record = MyRecord {
    field_a: "Hello World!".to_owned(),
    field_b: Some(255),
};
// Save record into storage
storage
    .set("my_record", &my_record)
    .expect("Record is saved");
// Pack storage into file
let packed = temp_dir().join(Uuid::new_v4().to_string());
storage.pack(&packed).expect("Storage packed");
// Remove origin storage
remove_dir_all(storage_path).expect("Origin storage has been removed");
drop(storage);
// Unpack storage
let storage =
    Storage::unpack(&packed).expect("Storage unpacked");
// Remove bundle file
remove_file(packed).expect("Bundle file removed");
// Read record from unpacked storage
let recovered: MyRecord = storage
    .get("my_record")
    .expect("Record is read")
    .expect("Record exists");
assert_eq!(my_record, recovered)

在存储中搜索记录

要实现存储中记录的搜索功能,您应使用 Search 特性,它提供对两个方法的访问:find 和 filter。

use bstorage::{Search, Storage, E};
use serde::{Deserialize, Serialize};
use std::{env::temp_dir, fs::remove_dir_all};
use uuid::Uuid;

#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
struct A {
    a: u8,
    b: String,
}

#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
struct B {
    c: u32,
    d: Option<bool>,
}

let mut storage = Storage::create(temp_dir().join(Uuid::new_v4().to_string())).unwrap();
let a = [
    A {
        a: 0,
        b: String::from("one"),
    },
    A {
        a: 1,
        b: String::from("two"),
    },
    A {
        a: 2,
        b: String::from("three"),
    },
];
let b = [
    B {
        c: 0,
        d: Some(true),
    },
    B {
        c: 1,
        d: Some(false),
    },
    B {
        c: 2,
        d: Some(true),
    },
];
let mut i = 0;
for a in a.iter() {
    storage.set(i.to_string(), a).unwrap();
    i += 1;
}
for b in b.iter() {
    storage.set(i.to_string(), b).unwrap();
    i += 1;
}
let (_key, found) = storage.find(|v: &A| &a[0] == v).unwrap().expect("Record found");
assert_eq!(found, a[found.a as usize]);
let (_key, found) = storage.find(|v: &B| &b[0] == v).unwrap().expect("Record found");
assert_eq!(found, b[found.c as usize]);
assert!(storage.find(|v: &A| v.a > 254).unwrap().is_none());
storage.clear().unwrap();
remove_dir_all(storage.cwd()).unwrap();

过滤的示例

use bstorage::{Search, Storage, E};
use serde::{Deserialize, Serialize};
use std::{env::temp_dir, fs::remove_dir_all};
use uuid::Uuid;

#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
struct A {
    a: u8,
    b: String,
}

#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
struct B {
    c: u32,
    d: Option<bool>,
}

let mut storage = Storage::create(temp_dir().join(Uuid::new_v4().to_string())).unwrap();
let a = [
    A {
        a: 0,
        b: String::from("one"),
    },
    A {
        a: 1,
        b: String::from("two"),
    },
    A {
        a: 2,
        b: String::from("three"),
    },
];
let b = [
    B {
        c: 0,
        d: Some(true),
    },
    B {
        c: 1,
        d: Some(false),
    },
    B {
        c: 2,
        d: Some(true),
    },
];
let mut i = 0;
for a in a.iter() {
    storage.set(i.to_string(), a).unwrap();
    i += 1;
}
for b in b.iter() {
    storage.set(i.to_string(), b).unwrap();
    i += 1;
}
let found = storage.filter(|v: &A| v.a < 2).unwrap();
assert_eq!(found.len(), 2);
for (_key, found) in found.into_iter() {
    assert_eq!(found, a[found.a as usize]);
}
let found = storage.filter(|v: &B| v.c < 2).unwrap();
assert_eq!(found.len(), 2);
for (_key, found) in found.into_iter() {
    assert_eq!(found, b[found.c as usize]);
}
assert_eq!(storage.filter(|v: &A| v.a > 254).unwrap().len(), 0);
storage.clear().unwrap();
remove_dir_all(storage.cwd()).unwrap();

贡献

欢迎贡献!请阅读简短的 贡献指南

依赖项

~1.3–2MB
~42K SLoC