use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::fs::File;
use camino::Utf8Path;
use libpijul::change::ChangeHeader;
use libpijul::changestore::ChangeStore;
use libpijul::pristine::SerializedHash;
use libpijul::pristine::sanakirja::{MutTxn, SanakirjaError};
use libpijul::{ArcTxn, ChannelRef, TxnTExt};
#[derive(Clone, Debug)]
pub enum AuthorSource {
Local(pijul_identity::Complete),
Remote(pijul_identity::Complete),
Unknown(String),
}
#[derive(Debug, thiserror::Error)]
pub enum GetAuthorsError<C: std::error::Error + 'static> {
#[error("Unable to get log of changes: {0}")]
Log(SanakirjaError),
#[error("Error while iterating log: {0}")]
LogEntry(SanakirjaError),
#[error("Failed to get change header for {hash:?}: {error}")]
ChangeHeader { hash: SerializedHash, error: C },
}
pub struct Authors {
authors: HashMap<String, AuthorSource>,
}
impl Authors {
pub fn new<C>(
dot_directory: &Utf8Path,
transaction: &ArcTxn<MutTxn<()>>,
channel: &ChannelRef<MutTxn<()>>,
change_store: &C,
) -> Result<Self, GetAuthorsError<C::Error>>
where
C: ChangeStore + Clone + Send + 'static,
{
let mut authors = Self {
authors: HashMap::new(),
};
authors.insert_local_identities();
authors.insert_repository_identities(dot_directory, transaction, channel, change_store)?;
Ok(authors)
}
fn insert_local_identities(&mut self) {
match pijul_identity::Complete::load_all() {
Ok(local_identities) => {
for local_identity in local_identities {
let public_key_signature = local_identity.public_key.key.clone();
match self.authors.entry(public_key_signature.clone()) {
Entry::Occupied(occupied_entry) => {
tracing::error!(
message = "Duplicate local identities found",
public_key_signature,
previous_author = ?occupied_entry.get(),
);
}
Entry::Vacant(vacant_entry) => {
vacant_entry.insert(AuthorSource::Local(local_identity));
}
}
}
}
Err(error) => tracing::error!(message = "Unable to load local identities", ?error),
}
}
fn insert_repository_identities<C>(
&mut self,
dot_directory: &Utf8Path,
transaction: &ArcTxn<MutTxn<()>>,
channel: &ChannelRef<MutTxn<()>>,
change_store: &C,
) -> Result<(), GetAuthorsError<C::Error>>
where
C: ChangeStore + Clone + Send + 'static,
{
let repository_identities_path = dot_directory.join("identities");
let read_transaction = transaction.read();
let log_entries = read_transaction
.log(&*channel.read(), 0)
.map_err(GetAuthorsError::Log)?;
for log_entry in log_entries {
let (_index, (serialized_hash, _merkle)) =
log_entry.map_err(GetAuthorsError::LogEntry)?;
let change_header =
change_store
.get_header(&serialized_hash.into())
.map_err(|error| GetAuthorsError::ChangeHeader {
hash: *serialized_hash,
error,
})?;
for serialized_author in &change_header.authors {
match serialized_author.0.get("key") {
Some(public_key_signature) => {
if let Entry::Vacant(vacant_entry) =
self.authors.entry(public_key_signature.to_owned())
{
let identity_path =
repository_identities_path.join(public_key_signature);
let author_source = match File::open(&identity_path) {
Ok(identity_file) => match serde_json::from_reader(identity_file) {
Ok(remote_identity) => AuthorSource::Remote(remote_identity),
Err(error) => {
tracing::error!(
message = "Failed to deserialize identity file",
?identity_path,
?error
);
AuthorSource::Unknown(public_key_signature.to_owned())
}
},
Err(error) => {
tracing::error!(
message = "Failed to open identity file",
?identity_path,
?error
);
AuthorSource::Unknown(public_key_signature.to_owned())
}
};
vacant_entry.insert(author_source);
}
}
None => {
tracing::warn!(message = "Author missing `key` field", ?serialized_author);
}
}
}
}
Ok(())
}
pub fn authors_for_change(&self, change_header: &ChangeHeader) -> Vec<&AuthorSource> {
let mut authors = Vec::new();
for serialized_author in &change_header.authors {
match serialized_author.0.get("key") {
Some(public_key_signature) => match self.authors.get(public_key_signature) {
Some(author) => {
authors.push(author);
}
None => tracing::error!(
message = "No matching author for key",
public_key_signature,
?change_header
),
},
None => {
tracing::warn!(message = "Author missing `key` field", ?serialized_author);
}
}
}
authors
}
}