use std::fmt::Write;
use std::path::PathBuf;
use crate::models::{pijul::changestores::Changestore, users::User, projects::Project};
use crate::database::Database;
use libpijul::pristine::sanakirja::Pristine;
use libpijul::TxnT;
use libpijul::TxnTExt;
use libpijul::{Base32, Hash};
use libpijul::{MutTxnT, MutTxnTExt};
use anyhow::bail;
pub struct Repository<'r> {
full_path: PathBuf,
project: &'r Project,
}
const DEFAULT_CHANNEL: &str = "main";
const PRISTINE_DIR: &str = "pristine";
impl <'r>Repository<'r> {
fn pristine_dir(&self) -> PathBuf {
self.full_path.join(PRISTINE_DIR)
}
fn pristine(&self) -> Result<Pristine, anyhow::Error> {
match Pristine::new(self.pristine_dir().join("db")) {
Ok(p) => Ok(p),
Err(e) => bail!("Failed to open pristine: {}", e),
}
}
pub fn changestore(&self) -> Changestore {
Changestore::new(self.full_path.clone())
}
pub fn valid_change(&self, hash: Hash) -> bool {
libpijul::change::Change::deserialize(
&self.changestore().change_file(hash).to_string_lossy(),
Some(&hash),
)
.is_ok()
}
pub fn apply_change_to_channel(&self, chan: &str, change: Hash) -> Result<(), anyhow::Error> {
let mut txn = self.pristine()?.mut_txn_begin()?;
let chan = if let Some(chan) = txn.load_channel(chan)? {
chan
} else {
bail!("channel not found")
};
{
let mut write_channel = chan.write();
txn.apply_change(
&libpijul::changestore::filesystem::FileSystem::from_changes(
self.changestore().dir(),
),
&mut write_channel,
&change,
)?;
}
txn.commit()?;
Ok(())
}
fn new(project: &Project, full_path: PathBuf) -> Repository {
Repository {
project: project,
full_path: full_path.join(libpijul::DOT_DIR),
}
}
fn init(&self) -> Result<(), anyhow::Error> {
if std::fs::metadata(self.pristine_dir()).is_err() {
std::fs::create_dir_all(self.pristine_dir())?;
} else {
bail!("Already a repository on disk")
}
let mut txn = self.pristine()?.mut_txn_begin()?;
txn.open_or_create_channel(DEFAULT_CHANNEL)?;
txn.set_current_channel(DEFAULT_CHANNEL)?;
txn.commit()?;
Ok(())
}
pub fn init_or_open(project: &'r Project, root: &PathBuf) -> Result<Self, anyhow::Error> {
let repo = Self::new(project, root.join(project.repo_path()));
match repo.pristine() {
Ok(_) => Ok(repo),
Err(e) => {
println!("{}", e);
repo.init()?;
Ok(repo)
}
}
}
pub fn channel_remote_id(&self, channel: String, _id: Option<String>) -> Option<String> {
let txn = match self.pristine().ok()?.mut_txn_begin() {
Err(_) => return None,
Ok(txn) => txn,
};
let chan = match txn.load_channel(&channel) {
Ok(c) => c,
Err(_) => None,
};
match chan {
Some(c) => Some(c.read().id.to_string()),
None => {
println!("No chan found");
None
}
}
}
pub fn changelist(&self, channel: String, from: u64) -> Result<String, anyhow::Error> {
let txn = self.pristine()?.mut_txn_begin()?;
let chan = match txn.load_channel(&channel)? {
Some(c) => c,
None => bail!("failed to read channel transaction"),
};
let mut out: String = "".to_string();
for change in txn.log(&chan.read(), from)? {
let (offset, (hash, merkle)) = change?;
let h: Hash = hash.into();
let m: libpijul::Merkle = merkle.into();
write!(out, "{}.{}.{}\n", offset, h.to_base32(), m.to_base32())?;
}
Ok(out)
}
pub async fn owner(&self, db: &Database) -> anyhow::Result<User> {
self.project.owner(db).await
}
}