use anyhow::{Context, Result};
use rusty_leveldb::{DB, LdbIterator, Options};
use std::any::Any;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use crate::storage::database::{BatchWriter, Database, Tree};
pub fn dir_has_mixed_sst_formats(dir: &Path) -> bool {
let entries = match dir.read_dir() {
Ok(e) => e,
Err(_) => return false,
};
let mut has_ldb = false;
let mut has_sst_or_rocksdb_marker = false;
for entry in entries.flatten() {
let name = entry.file_name();
let n = name.to_string_lossy();
if n == "IDENTITY" || n.starts_with("OPTIONS-") {
has_sst_or_rocksdb_marker = true;
}
let ext = entry
.path()
.extension()
.and_then(|e| e.to_str())
.map(|s| s.to_owned())
.unwrap_or_default();
if ext == "ldb" {
has_ldb = true;
}
if ext == "sst" {
has_sst_or_rocksdb_marker = true;
}
}
has_ldb && has_sst_or_rocksdb_marker
}
pub fn dir_is_leveldb(dir: &Path) -> bool {
let entries = match dir.read_dir() {
Ok(e) => e,
Err(_) => return false,
};
let mut has_ldb = false;
for entry in entries.flatten() {
let name = entry.file_name();
let n = name.to_string_lossy();
if n == "IDENTITY" || n.starts_with("OPTIONS-") {
return false;
}
if entry
.path()
.extension()
.and_then(|ext| ext.to_str())
.map(|s| s == "ldb")
.unwrap_or(false)
{
has_ldb = true;
}
}
has_ldb
}
pub struct LevelDbDatabase {
path: PathBuf,
db: Arc<Mutex<DB>>,
}
impl LevelDbDatabase {
pub fn open(path: &Path) -> Result<Self> {
let mut opt = Options::default();
opt.create_if_missing = false;
opt.error_if_exists = false;
let db =
DB::open(path, opt).with_context(|| format!("Failed to open LevelDB at {path:?}"))?;
Ok(Self {
path: path.to_path_buf(),
db: Arc::new(Mutex::new(db)),
})
}
}
impl Database for LevelDbDatabase {
fn open_tree(&self, _name: &str) -> Result<Box<dyn Tree>> {
Ok(Box::new(LevelDbTree {
db: Arc::clone(&self.db),
}))
}
fn flush(&self) -> Result<()> {
Ok(()) }
fn as_any(&self) -> &dyn Any {
self
}
}
pub struct LevelDbTree {
db: Arc<Mutex<DB>>,
}
struct LevelDbChannelIter {
rx: std::sync::mpsc::Receiver<Result<(Vec<u8>, Vec<u8>)>>,
}
impl Iterator for LevelDbChannelIter {
type Item = Result<(Vec<u8>, Vec<u8>)>;
fn next(&mut self) -> Option<Self::Item> {
self.rx.recv().ok()
}
}
impl Tree for LevelDbTree {
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
let mut db = self.db.lock().unwrap();
Ok(db.get(key).map(|b| b.to_vec()))
}
fn iter(&self) -> Box<dyn Iterator<Item = Result<(Vec<u8>, Vec<u8>)>> + '_> {
let (tx, rx) = std::sync::mpsc::sync_channel::<Result<(Vec<u8>, Vec<u8>)>>(2048);
let db_arc = Arc::clone(&self.db);
std::thread::spawn(move || {
let mut db = match db_arc.lock() {
Ok(g) => g,
Err(e) => {
let _ = tx.send(Err(anyhow::anyhow!("LevelDB lock poisoned: {e}")));
return;
}
};
let mut iter = match db.new_iter() {
Ok(it) => it,
Err(status) => {
let _ = tx.send(Err(anyhow::anyhow!(
"LevelDB new_iter failed: {:?}",
status
)));
return;
}
};
iter.seek_to_first();
while iter.valid() {
if let Some((k, v)) = iter.current() {
if tx.send(Ok((k.to_vec(), v.to_vec()))).is_err() {
break; }
}
iter.advance();
}
});
Box::new(LevelDbChannelIter { rx })
}
fn contains_key(&self, key: &[u8]) -> Result<bool> {
Ok(self.get(key)?.is_some())
}
fn len(&self) -> Result<usize> {
let mut db = self.db.lock().unwrap();
let mut iter = db
.new_iter()
.map_err(|s| anyhow::anyhow!("LevelDB iter (len): {:?}", s))?;
let mut count = 0usize;
iter.seek_to_first();
while iter.valid() {
if iter.current().is_some() {
count += 1;
}
iter.advance();
}
Ok(count)
}
fn insert(&self, _key: &[u8], _value: &[u8]) -> Result<()> {
anyhow::bail!("LevelDbTree is read-only (Bitcoin Core chainstate source)")
}
fn remove(&self, _key: &[u8]) -> Result<()> {
anyhow::bail!("LevelDbTree is read-only")
}
fn clear(&self) -> Result<()> {
anyhow::bail!("LevelDbTree is read-only")
}
fn batch(&self) -> Result<Box<dyn BatchWriter + '_>> {
anyhow::bail!("LevelDbTree is read-only")
}
}