use std::collections::HashSet;
use std::fmt::Display;
use crate::canister::call::call_canister;
use crate::common::pages::{
page_find_with_reserve, page_find_with_reserve_and_filter, Page, PageData,
};
use crate::identity::{caller, CallerId, CanisterId};
use crate::times::schedulable::async_execute;
use crate::times::Timestamp;
#[derive(candid::CandidType, candid::Deserialize, Debug, PartialEq, Clone)]
pub enum RecordLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
#[derive(candid::CandidType, candid::Deserialize, Debug, Clone)]
pub struct Record {
pub id: u64, pub created: Timestamp, pub level: RecordLevel, pub caller: CallerId, pub topic: String, pub content: String, pub done: Timestamp, pub result: String, }
#[derive(candid::CandidType, candid::Deserialize, Debug, Clone)]
pub struct MigratedRecords {
pub topics: HashSet<String>,
pub next_id: u64,
pub data: Vec<Record>,
pub updated: Vec<(u64, Timestamp, String)>,
}
pub trait Recordable {
fn record_next_id(&self) -> u64;
fn record_topics(&self) -> HashSet<String>;
fn record_find_all(&self, topic: &Option<String>) -> Vec<Record>;
fn record_find_by_page(
&self,
topic: &Option<String>,
page: &Page,
max: u32,
) -> PageData<Record>;
fn record_collector_find(&self) -> Option<CanisterId>;
fn record_register(&self);
fn record_push(
&mut self,
level: RecordLevel,
caller: CallerId,
topic: String,
content: String,
) -> u64;
fn record_update(&mut self, record_id: u64, result: String);
fn record_trace(&mut self, topic: String, content: String) -> u64 {
self.record_push(RecordLevel::Trace, caller(), topic, content)
}
fn record_debug(&mut self, topic: String, content: String) -> u64 {
self.record_push(RecordLevel::Debug, caller(), topic, content)
}
fn record_info(&mut self, topic: String, content: String) -> u64 {
self.record_push(RecordLevel::Info, caller(), topic, content)
}
fn record_warn(&mut self, topic: String, content: String) -> u64 {
self.record_push(RecordLevel::Warn, caller(), topic, content)
}
fn record_error(&mut self, topic: String, content: String) -> u64 {
self.record_push(RecordLevel::Error, caller(), topic, content)
}
fn record_collector_replace(&mut self, collector: Option<CanisterId>);
fn record_migrate_data(&mut self, max: u32) -> Vec<Record>;
fn record_migrate_updated(&mut self) -> Vec<(u64, Timestamp, String)>;
fn record_migrate(&mut self, max: u32) -> MigratedRecords {
MigratedRecords {
topics: self.record_topics(),
next_id: self.record_next_id(),
data: self.record_migrate_data(max),
updated: self.record_migrate_updated(),
}
}
}
#[derive(candid::CandidType, candid::Deserialize, Debug, Default)]
pub struct Records {
collector: Option<CanisterId>, pub topics: HashSet<String>, pub next_id: u64, pub data: Vec<Record>,
pub updated: Vec<(u64, Timestamp, String)>, }
impl Records {
pub fn new() -> Self {
Records {
collector: None, topics: HashSet::new(), next_id: 0, data: Vec::new(),
updated: Vec::new(), }
}
}
impl Recordable for Records {
fn record_next_id(&self) -> u64 {
self.next_id
}
fn record_topics(&self) -> HashSet<String> {
self.topics.clone()
}
fn record_find_all(&self, topic: &Option<String>) -> Vec<Record> {
if let Some(topic) = topic {
let data: Vec<Record> = self
.data
.iter()
.filter(|d| &d.topic == topic)
.map(|r| r.clone())
.collect();
return data;
}
self.data.clone()
}
fn record_find_by_page(
&self,
topic: &Option<String>,
page: &Page,
max: u32,
) -> PageData<Record> {
if let Some(topic) = topic {
return page_find_with_reserve_and_filter(&self.data, page, max, |d| &d.topic == topic);
}
page_find_with_reserve(&self.data, page, max)
}
fn record_collector_find(&self) -> Option<CanisterId> {
self.collector
}
fn record_register(&self) {
if let Some(collector) = self.collector {
let _ = async_execute(move || {
ic_cdk::spawn(async move {
call_canister::<(), ()>(collector, "business_record_register", ())
.await
.unwrap();
})
});
}
}
fn record_push(
&mut self,
level: RecordLevel,
caller: CallerId,
topic: String,
content: String,
) -> u64 {
let id = self.next_id;
self.next_id += 1;
self.data.push(Record {
id,
created: crate::times::now(),
level,
caller,
topic: topic.clone(),
content,
done: 0,
result: String::default(),
});
self.topics.insert(topic);
id
}
fn record_update(&mut self, record_id: u64, result: String) {
let now = crate::times::now();
let mut i = self.data.len();
while 0 < i {
let record = &mut self.data[i - 1];
if record.id == record_id {
record.done = now;
record.result = result;
return;
}
i -= 1;
}
self.updated.push((record_id, now, result));
}
fn record_collector_replace(&mut self, collector: Option<CanisterId>) {
self.collector = collector;
self.record_register();
}
fn record_migrate_data(&mut self, max: u32) -> Vec<Record> {
if self.data.len() < max as usize {
std::mem::take(&mut self.data)
} else {
let (migrated, left) = self.data.split_at(max as usize);
let migrated = migrated.to_owned();
self.data = left.to_owned();
migrated
}
}
fn record_migrate_updated(&mut self) -> Vec<(u64, Timestamp, String)> {
std::mem::take(&mut self.updated)
}
}
pub fn record_option<T: Display>(value: &Option<T>) -> String {
if let Some(id) = value {
return id.to_string();
}
format!("None")
}