mod drafts;
mod keys;
mod relations;
mod tags;
use std::error::Error;
use std::fs;
use std::path::Path;
use heed::types::{SerdeRmp, Str};
use heed::{Database, Env, EnvOpenOptions, RoTxn, RwTxn};
use serde::{Deserialize, Serialize};
use nostr_sdk::prelude::*;
#[derive(Debug)]
pub struct Storage {
env: Env,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FollowList<'a> {
#[serde(borrow)]
pub npubv: Vec<&'a str>,
}
type FollowingDatabase<'a> = Database<Str, SerdeRmp<FollowList<'a>>>;
const FOLLOWING_KEY: &str = "following";
impl Storage {
pub fn new(path: &Path) -> Result<Storage, Box<dyn Error>> {
fs::create_dir_all(path)?;
let env = unsafe {
EnvOpenOptions::new()
.map_size(10 * 1024 * 1024)
.max_dbs(1000)
.open(path)?
};
Ok(Storage { env })
}
pub fn get_write_txn(
&self,
) -> Result<RwTxn<'_>, Box<dyn Error + Send + Sync>> {
Ok(self.env.write_txn()?)
}
pub fn get_read_txn(
&self,
) -> Result<RoTxn<'_>, Box<dyn Error + Send + Sync>> {
Ok(self.env.read_txn()?)
}
pub fn following_db(
&self,
) -> Result<FollowingDatabase<'_>, Box<dyn Error + Send + Sync>> {
let mut rtxn = self.get_read_txn()?;
let dbo: Option<FollowingDatabase> =
self.env.open_database(&mut rtxn, Some("following"))?;
if dbo.is_some() {
Ok(dbo.unwrap())
} else {
Err(Box::from("Error opening following db"))
}
}
pub fn create_following_db(
&self,
) -> Result<FollowingDatabase<'_>, Box<dyn Error + Send + Sync>> {
let mut wtxn = self.env.write_txn()?;
let db: FollowingDatabase =
self.env.create_database(&mut wtxn, Some("following"))?;
wtxn.commit()?;
Ok(db)
}
pub fn get_followed_list<'c>(
&'c self,
rtxn: &'c mut RoTxn,
) -> Result<Box<Vec<&'c str>>, Box<dyn Error + Send + Sync>> {
match self.following_db()?.get(rtxn, FOLLOWING_KEY)? {
Some(flist) => {
let mut v = Vec::new();
for val in flist.npubv {
v.push(val);
}
Ok(Box::new(v))
}
None => Err(Box::from("Error reading flist")),
}
}
pub fn get_followed_pubkeys<'c>(
&'c self,
rtxn: &'c mut RoTxn,
) -> Result<Vec<PublicKey>, Box<dyn Error + Send + Sync>> {
match self.following_db()?.get(rtxn, FOLLOWING_KEY)? {
Some(flist) => {
let mut v = Vec::new();
for val in flist.npubv {
if let Ok(pk) = PublicKey::parse(val) {
v.push(pk);
}
}
Ok(v)
}
None => Err(Box::from("Error reading flist")),
}
}
pub fn follow(
&self,
npub: &String,
) -> Result<bool, Box<dyn Error + Send + Sync>> {
let rtxn = self.env.read_txn()?;
let db = self.following_db()?;
let f: Option<FollowList> = db.get(&rtxn, FOLLOWING_KEY)?;
match f {
Some(mut flist) => {
let mut wtxn = self.env.write_txn()?;
let npubvs = npub.as_str();
if !flist.npubv.contains(&npubvs) {
flist.npubv.push(npub.as_str());
db.put(&mut wtxn, FOLLOWING_KEY, &flist)?;
wtxn.commit()?;
}
}
None => {
let mut wtxn = self.env.write_txn()?;
let list = FollowList {
npubv: vec![npub.as_str()],
};
db.put(&mut wtxn, FOLLOWING_KEY, &list)?;
wtxn.commit()?;
}
}
Ok(true)
}
pub fn unfollow(
&self,
npub: &String,
) -> Result<bool, Box<dyn Error + Send + Sync>> {
let rtxn = self.env.read_txn()?;
let db = self.following_db()?;
let f: Option<FollowList> = db.get(&rtxn, FOLLOWING_KEY)?;
match f {
Some(mut flist) => {
let mut wtxn = self.env.write_txn()?;
let npubvs = npub.as_str();
if flist.npubv.contains(&npubvs) {
flist.npubv.retain(|&x| x != npub);
db.put(&mut wtxn, FOLLOWING_KEY, &flist)?;
wtxn.commit()?;
}
}
None => {
let mut wtxn = self.env.write_txn()?;
let list = FollowList {
npubv: vec![npub.as_str()],
};
db.put(&mut wtxn, FOLLOWING_KEY, &list)?;
wtxn.commit()?;
}
}
Ok(true)
}
}