flashdb_rs/
storage.rs

1use crate::error::Error;
2use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
3use lru::LruCache;
4use std::fs::{File, OpenOptions};
5use std::io::prelude::{Read as StdRead, Seek as StdSeek, Write as StdWrite};
6use std::io::ErrorKind;
7use std::num::NonZeroUsize;
8use std::path::{Path, PathBuf};
9
10/// 定义文件存储策略
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum FileStrategy {
13    /// **单文件模式**: 整个数据库存储在一个大文件中。
14    Single,
15    /// **多文件模式**: 每个扇区存储在一个独立的文件中 (例如 `kv_db.fdb.0`, `kv_db.fdb.1`)。
16    /// 这兼容原始 C 库的文件模式。
17    Multi,
18}
19
20/// 一个基于 `std::fs::File` 的 `NorFlash` 实现,用于桌面环境。
21///
22/// 通过 LRU 缓存高效管理文件句柄。
23pub struct StdStorage {
24    strategy: FileStrategy,
25    db_name: String,
26    base_path: PathBuf,
27    sec_size: u32,
28    capacity: u32,
29    file_cache: LruCache<u32, File>,
30}
31
32impl StdStorage {
33    /// 创建一个新的 `StdStorage` 实例。
34    pub fn new<P: AsRef<Path>>(
35        path: P,
36        db_name: &str,
37        sec_size: u32,
38        capacity: u32,
39        strategy: FileStrategy,
40    ) -> Result<Self, std::io::Error> {
41        let base_path = path.as_ref().to_path_buf();
42        if strategy == FileStrategy::Multi {
43            std::fs::create_dir_all(&base_path)?;
44        } else {
45            // 单文件模式,确保父目录存在
46            if let Some(parent) = base_path.parent() {
47                std::fs::create_dir_all(parent)?;
48            }
49        }
50        Ok(Self {
51            strategy,
52            db_name: db_name.to_string(),
53            sec_size,
54            capacity,
55            base_path,
56            file_cache: LruCache::new(NonZeroUsize::new(8).unwrap()),
57        })
58    }
59
60    /// 根据地址获取对应的文件句柄和文件内偏移量。
61    fn get_file_and_offset(&mut self, addr: u32) -> Result<(&mut File, u64), std::io::Error> {
62        let (sector_index, offset_in_file) = match self.strategy {
63            FileStrategy::Single => (0, addr as u64),
64            FileStrategy::Multi => {
65                let sector_index = addr / self.sec_size;
66                let offset = (addr % self.sec_size) as u64;
67                (sector_index, offset)
68            }
69        };
70
71        if !self.file_cache.contains(&sector_index) {
72            let file_path = match self.strategy {
73                FileStrategy::Single => self.base_path.clone(),
74                FileStrategy::Multi => self
75                    .base_path
76                    .join(format!("{}.fdb.{}", self.db_name, sector_index)),
77            };
78            let file = OpenOptions::new()
79                .read(true)
80                .write(true)
81                .create(true)
82                .open(&file_path)?;
83            self.file_cache.put(sector_index, file);
84        }
85
86        let file = self.file_cache.get_mut(&sector_index).unwrap();
87        Ok((file, offset_in_file))
88    }
89}
90
91impl ErrorType for StdStorage {
92    type Error = Error;
93}
94
95impl ReadNorFlash for StdStorage {
96    const READ_SIZE: usize = 1;
97
98    fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
99        let (file, file_offset) = self.get_file_and_offset(offset)?;
100        file.seek(std::io::SeekFrom::Start(file_offset))?;
101        match file.read_exact(bytes) {
102            Ok(_) => Ok(()),
103            Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
104                // Flash 存储在未写入区域读取时通常返回0xFF
105                bytes.fill(0xFF);
106                Ok(())
107            }
108            Err(e) => Err(e.into()),
109        }
110    }
111
112    fn capacity(&self) -> usize {
113        self.capacity as usize
114    }
115}
116
117impl NorFlash for StdStorage {
118    const WRITE_SIZE: usize = 1;
119    const ERASE_SIZE: usize = 4096; // 这是一个典型值,我们将 sec_size 作为擦除大小
120
121    fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
122        let size = to - from;
123        // 擦除操作是基于绝对地址的
124        let (sector_index, offset) = match self.strategy {
125            FileStrategy::Single => (0, from as u64),
126            FileStrategy::Multi => {
127                if from % self.sec_size != 0 || size != self.sec_size {
128                    return Err(Error::InvalidArgument);
129                }
130                (from / self.sec_size, 0)
131            }
132        };
133
134        self.file_cache.pop(&sector_index);
135        let file_path = match self.strategy {
136            FileStrategy::Single => self.base_path.clone(),
137            FileStrategy::Multi => self
138                .base_path
139                .join(format!("{}.fdb.{}", self.db_name, sector_index)),
140        };
141
142        let mut file = OpenOptions::new().write(true).create(true).open(&file_path)?;
143
144        if self.strategy == FileStrategy::Multi {
145            file.set_len(0)?;
146        }
147
148        file.seek(std::io::SeekFrom::Start(offset))?;
149        // 模拟擦除,填充 0xFF
150        let buf = vec![0xFF; size as usize];
151        file.write_all(&buf)?;
152        file.flush()?;
153        Ok(())
154    }
155
156    fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
157        let (file, file_offset) = self.get_file_and_offset(offset)?;
158        file.seek(std::io::SeekFrom::Start(file_offset))?;
159        file.write_all(bytes)?;
160        file.flush()?;
161        Ok(())
162    }
163}