mod cobs;
mod ops;
mod repos;
use std::str::FromStr;
use radicle::cob::{ObjectId, Op, TypeName};
use radicle::profile::{Aliases, Profile};
use radicle::{issue, patch};
use snafu::ResultExt;
use crate::models::entry::TimelineEntry;
use crate::storage::FeedStorage;
pub struct FeedProcessor<'a, S: FeedStorage> {
storage: &'a mut S,
profile: Profile,
}
impl<'a, S: FeedStorage> FeedProcessor<'a, S> {
pub fn new(storage: &'a mut S, profile: Profile) -> Result<Self, S::Error> {
Ok(Self { storage, profile })
}
pub fn process_repository(
&mut self,
repo: &radicle::storage::RepositoryInfo,
) -> Result<(), snafu::Whatever> {
let (git2_repo, patches, issues, aliases) = {
let repo_loader = repos::RepositoryLoader::new(&self.profile)?;
let (repo_handle, git2_repo) = repo_loader.open_repository(&repo.rid)?;
let patches = repo_loader.load_patches(&repo_handle)?;
let issues = repo_loader.load_issues(&repo_handle)?;
let aliases = self.profile.aliases();
(git2_repo, patches, issues, aliases)
};
for patch_id in patches {
self.process_single_cob::<patch::Action>(
&patch_id,
&patch::TYPENAME,
&git2_repo,
&repo.rid,
&aliases,
)?;
}
for issue_id in issues {
self.process_single_cob::<issue::Action>(
&issue_id,
&issue::TYPENAME,
&git2_repo,
&repo.rid,
&aliases,
)?;
}
Ok(())
}
fn process_single_cob<A>(
&mut self,
id: &ObjectId,
typename: &TypeName,
git2_repo: &radicle::git::raw::Repository,
rid: &radicle::prelude::RepoId,
aliases: &Aliases,
) -> Result<(), snafu::Whatever>
where
A: serde::Serialize + for<'de> serde::Deserialize<'de> + Clone,
{
let last_operation_id = self
.storage
.get_last_processed_operation(rid, &id, typename)
.whatever_context("Unable to get last processed operation")?
.and_then(|oid| {
let oid: radicle::git::Oid = radicle::git::Oid::from_str(&oid).unwrap();
Op::<A>::load(git2_repo, oid).map(|op| op.id()).ok()
});
let stream_processor = cobs::CobStreamProcessor::new(git2_repo, typename, id);
let stream_entries = stream_processor.fetch_entries_since::<A>(last_operation_id)?;
if stream_entries.is_empty() {
tracing::debug!("{id} No new operations to process");
return Ok(());
}
let repo_alias = self
.storage
.resolve_rid(rid)
.whatever_context("Unable to resolve repo alias")?;
let operation_builder = ops::OperationBuilder::new(&aliases, rid, typename, repo_alias);
let (operations, last_processed_id) = operation_builder.build_operations::<A, _>(
stream_entries,
last_operation_id,
self.storage,
)?;
self.storage
.insert_timeline_entry(&TimelineEntry {
repo: rid.to_string(),
node: self.profile.id().to_string(),
cob_id: id.to_string(),
typename: typename.to_string(),
last_operation_id: last_processed_id.map(|l| l.to_string()),
operations: operations
.iter()
.map(|op| op.operation_id.clone())
.collect(),
})
.whatever_context("Insert timeline entry failed")?;
if !operations.is_empty() {
tracing::debug!("Inserting {} new operations into storage", operations.len());
self.storage
.insert_batch(&operations)
.whatever_context("Failed insert batch")?;
}
Ok(())
}
pub fn get_stats(&mut self) -> Result<crate::storage::StorageStats, S::Error> {
self.storage.get_stats()
}
}