use crate::permissions::Perm;
use crate::Config;
use axum::{
debug_handler,
extract::{Query, State},
response::{IntoResponse, Response},
Json,
};
use axum_extra::extract::SignedCookieJar;
use libpijul::{
pristine::{ChangeId, Position, Vertex},
vertex_buffer::VertexBuffer,
Base32, ChannelTxnT, TxnT, TxnTExt,
};
use serde_derive::*;
use tracing::*;
#[derive(Debug, Serialize, Deserialize)]
pub struct PosQuery {
channel: Option<String>,
pos: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct Tree {
owner: String,
repo: String,
channel: String,
channels: Vec<String>,
can_delete: bool,
inode: Inode,
#[serde(skip_serializing_if = "Option::is_none")]
hled: Option<String>,
token: String,
login: Option<String>,
}
#[derive(Debug, Serialize)]
pub enum Inode {
File {
path: Vec<super::PathElement>,
file: Option<String>,
},
Directory {
path: Vec<super::PathElement>,
children: Vec<Directory>,
},
}
#[derive(Debug, Serialize)]
pub struct Directory {
is_dir: bool,
meta: libpijul::pristine::InodeMetadata,
name: String,
pos: String,
}
#[debug_handler]
pub async fn tree(
State(config): State<Config>,
jar: SignedCookieJar,
tree: crate::repository::RepoPath,
token: axum_csrf::CsrfToken,
pos: Query<PosQuery>,
) -> Result<Response, crate::Error> {
debug!("tree {:?}", tree);
let (uid, login) = crate::get_user_login_(&jar, &config).await?;
let mut db = config.db.get().await?;
let (id, _) = super::repository_id(&mut db, &tree.owner, &tree.repo, uid, Perm::READ).await?;
let c = super::channel_spec_id(id, tree.channel.as_deref().unwrap_or("main"));
debug!("channel {:?}", c);
let locks = config.repo_locks.clone();
let repo_ = locks.get(&id).await.unwrap();
let channels = repo_.channels().await;
let channel = tree.channel.unwrap_or_else(|| "main".to_string());
let channel_ = channel.clone();
let inode = tokio::task::spawn_blocking(move || {
let pristine = repo_.pristine.blocking_read();
let txn = pristine.txn_begin()?;
let channel = if let Some(channel) = txn.load_channel(&c)? {
channel
} else if txn.channels("")?.is_empty() && (pos.channel.as_deref() == Some("main") || pos.channel.is_none()) {
return Ok(Inode::Directory {
path: Vec::new(),
children: Vec::new(),
});
} else {
debug!("channel not found {:?}", c);
if tracing::enabled!(tracing::Level::DEBUG) {
for c in txn.channels("")? {
let c = c.read();
debug!("channel: {:?}", c.name);
}
}
return Err(crate::Error::ChannelNotFound { channel: channel_ });
};
let ch = channel.read();
let pos = pos
.pos
.as_ref()
.map(String::as_bytes)
.and_then(Position::from_base32);
let pos = pos.unwrap_or(Position::ROOT);
if !txn.is_alive(&*ch, &pos.inode_vertex())? {
debug!("not alive {:?}", pos.inode_vertex());
return Err(crate::Error::InodeNotFound);
}
let current_path = super::current_path(&txn, &*ch, &repo_.changes, pos)?;
debug!("current_path = {:?}", current_path);
let is_dir = if let Some(path) = current_path.last() {
debug!("last path = {:?}", path);
path.meta.is_dir()
} else {
true
};
if is_dir {
let mut children = Vec::new();
for x in libpijul::fs::iter_graph_children(&txn, &repo_.changes, txn.graph(&*ch), pos)?
{
let (pos, _, meta, name) = x?;
children.push(Directory {
is_dir: meta.is_dir(),
meta,
pos: pos.to_base32(),
name,
})
}
children.sort_by(|a, b| (!a.is_dir, &a.name).cmp(&(!b.is_dir, &b.name)));
Ok(Inode::Directory {
path: current_path,
children,
})
} else {
let mut out = RawVertexBuf { out: Vec::new() };
let txn = libpijul::ArcTxn::new(txn);
libpijul::output::output_file(&repo_.changes, &txn, &channel, pos, &mut out)
.map_err(|_| crate::Error::Txn)?;
Ok(Inode::File {
path: current_path,
file: String::from_utf8(out.out).ok(),
})
}
})
.await??;
let t = Tree {
owner: tree.owner,
repo: tree.repo,
channel,
channels,
can_delete: true,
inode,
hled: None,
token: token.authenticity_token()?,
login,
};
debug!("{:?}", t);
Ok((token, Json(t)).into_response())
}
#[derive(Debug)]
struct RawVertexBuf {
out: Vec<u8>,
}
impl VertexBuffer for RawVertexBuf {
fn output_line<E, C: FnOnce(&mut [u8]) -> Result<(), E>>(
&mut self,
v: Vertex<ChangeId>,
contents: C,
) -> Result<(), E> {
let len = self.out.len();
self.out.resize(len + (v.end - v.start), 0);
contents(&mut self.out[len..])?;
Ok(())
}
fn output_conflict_marker<T>(
&mut self,
marker: &str,
_: usize,
_: Option<(&T, &[&libpijul::Hash])>,
) -> Result<(), std::io::Error> {
self.out.extend(marker.as_bytes());
Ok(())
}
}