use super::{
features::Features,
pkg_key::PkgKey,
util::{decode, encode},
DocMeta, PkgWithFeatures,
};
use crate::{
database::util,
err,
local_registry::{PkgInfo, PkgNameVersion},
Result, WrapErr,
};
use serde::{Deserialize, Serialize};
use std::{
fs,
path::{Path, PathBuf},
time::{Instant, SystemTime},
};
use term_rustdoc::tree::CrateDoc;
#[derive(Debug, Deserialize, Serialize)]
pub struct CachedDocInfo {
pub pkg: PkgKey,
db_file: PathBuf,
meta: DocMeta,
}
impl CachedDocInfo {
pub fn new_with_default_feature(name_ver: PkgNameVersion, mut db_dir: PathBuf) -> Self {
let fname = name_ver.doc_db_file_name();
db_dir.push(&*fname);
let pkg = PkgKey::new_with_default_feature(name_ver);
CachedDocInfo {
pkg,
db_file: db_dir,
meta: DocMeta::new(),
}
}
pub fn new(name_ver: PkgNameVersion, features: Features, mut db_dir: PathBuf) -> Self {
let fname = name_ver.doc_db_file_name();
db_dir.push(&*fname);
let pkg = PkgKey::new(name_ver, features);
CachedDocInfo {
pkg,
db_file: db_dir,
meta: DocMeta::new(),
}
}
pub fn load_doc(&self) -> Result<CrateDoc> {
let now = Instant::now();
debug!(?self.pkg, "Start to load");
let db = redb::Database::open(&self.db_file)?;
let bytes = read_from_doc_db::<PkgKey, Vec<u8>>(&db, "host-parsed", &self.pkg)?;
let doc = util::decode_with_xz(&bytes)?;
info!(?self.pkg, "Loaded in {:.2}s", now.elapsed().as_secs_f32());
Ok(doc)
}
pub fn load_pkg_info_features(&self) -> Result<PkgWithFeatures> {
let db = redb::Database::create(&self.db_file)?;
let bytes = read_from_doc_db::<PkgKey, Vec<u8>>(&db, "host-pkg-info", &self.pkg)?;
let info = decode::<PkgInfo>(&bytes)?;
info!(?self.pkg, "PkgInfo is succeefully read from db file `{}`", self.db_file.display());
Ok(PkgWithFeatures {
features: self.pkg.features().clone(),
info,
})
}
pub fn save_doc(&self, json_path: &Path, pkg_info: PkgInfo) -> Result<()> {
let file = fs::File::open(json_path).wrap_err_with(|| {
format!(
"Failed to open compiled json doc under {}",
json_path.display()
)
})?;
let doc = CrateDoc::new(serde_json::from_reader(file)?);
let db = redb::Database::create(&self.db_file)?;
self.write_to_db(&db, "host-pkg-info", encode(&pkg_info)?)?;
info!(?self.pkg, "PkgInfo is succeefully saved");
let json_bytes = fs::read(json_path)?;
let compressed = util::xz_encode_on_bytes(&json_bytes)?;
self.write_to_db(&db, "host-json", compressed)?;
info!(?self.pkg, "raw json is succeefully saved");
self.write_to_db(&db, "host-parsed", util::encode_with_xz(doc)?)?;
info!(?self.pkg, "parsed data is succeefully saved");
self.write_self_to_db()?;
Ok(())
}
fn write_to_db(&self, db: &redb::Database, name: &str, value: Vec<u8>) -> Result<()> {
write_to_db::<PkgKey, Vec<u8>>(db, name, &self.pkg, &value)?;
Ok(())
}
fn write_self_to_db(&self) -> Result<()> {
let db = redb::Database::create(self.db_file.with_file_name("index.db"))?;
write_to_db::<PkgKey, CachedDocInfo>(&db, "CachedDocInfo", &self.pkg, self)?;
Ok(())
}
pub(super) fn meta_mut(&mut self) -> &mut DocMeta {
&mut self.meta
}
pub fn started_time(&self) -> SystemTime {
self.meta.started_time()
}
}
fn write_to_db<K, V>(
db: &redb::Database,
name: &str,
key: &K::SelfType<'_>,
value: &V::SelfType<'_>,
) -> Result<()>
where
K: 'static + redb::RedbKey,
V: 'static + redb::RedbValue,
{
let table = redb::TableDefinition::<K, V>::new(name);
let write_txn = db.begin_write()?;
{
let mut table = write_txn.open_table(table)?;
table.insert(key, value)?;
}
write_txn.commit()?;
Ok(())
}
fn read_from_doc_db<K, V>(db: &redb::Database, name: &str, key: &K) -> Result<V>
where
K: 'static + for<'a> redb::RedbKey<SelfType<'a> = K> + std::fmt::Debug,
V: 'static + for<'a> redb::RedbValue<SelfType<'a> = V>,
{
use redb::ReadableTable;
let table = redb::TableDefinition::<K, V>::new(name);
let read_txn = db.begin_read()?;
let value = read_txn
.open_table(table)?
.get(key)?
.ok_or_else(|| err!("Failed to get the key `{key:?}` from table `{name}`"))?
.value();
Ok(value)
}
impl redb::RedbValue for CachedDocInfo {
type SelfType<'a> = CachedDocInfo;
type AsBytes<'a> = Vec<u8>;
fn fixed_width() -> Option<usize> {
None
}
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
where
Self: 'a,
{
decode(data).unwrap()
}
fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
where
Self: 'a,
Self: 'b,
{
encode(value).unwrap()
}
fn type_name() -> redb::TypeName {
redb::TypeName::new("CachedDocInfo")
}
}