use crate::comment::Comment;
use crate::graph::NodeContents;
use crate::helper::{get_current_da_time, get_current_time};
use crate::{Channel, Node, Result, UrbitAPIError};
pub struct Notebook<'a> {
pub channel: &'a mut Channel,
}
#[derive(Clone, Debug)]
pub struct Note {
pub title: String,
pub author: String,
pub time_sent: String,
pub contents: String,
pub comments: Vec<Comment>,
pub index: String,
}
#[derive(Clone, Debug)]
struct NotebookIndex<'a> {
pub index: &'a str,
pub index_split: Vec<&'a str>,
}
impl Note {
pub fn new(
title: &str,
author: &str,
time_sent: &str,
contents: &str,
comments: &Vec<Comment>,
index: &str,
) -> Note {
Note {
title: title.to_string(),
author: author.to_string(),
time_sent: time_sent.to_string(),
contents: contents.to_string(),
comments: comments.clone(),
index: index.to_string(),
}
}
pub fn from_node(node: &Node, revision: Option<String>) -> Result<Note> {
let mut comments: Vec<Comment> = vec![];
let comments_node = node
.children
.iter()
.find(|c| c.index_tail() == "2")
.ok_or(UrbitAPIError::InvalidNoteGraphNode(node.to_json().dump()))?;
let content_node = node
.children
.iter()
.find(|c| c.index_tail() == "1")
.ok_or(UrbitAPIError::InvalidNoteGraphNode(node.to_json().dump()))?;
for comment_node in &comments_node.children {
let mut latest_comment_revision_node = comment_node.children[0].clone();
for revision_node in &comment_node.children {
if revision_node.index_tail() > latest_comment_revision_node.index_tail() {
latest_comment_revision_node = revision_node.clone();
}
}
comments.push(Comment::from_node(&latest_comment_revision_node));
}
let mut fetched_revision_node = content_node.children[0].clone();
match revision {
Some(idx) => {
for revision_node in &content_node.children {
if revision_node.index == idx {
fetched_revision_node = revision_node.clone();
}
}
}
None => {
for revision_node in &content_node.children {
if revision_node.index_tail() > fetched_revision_node.index_tail() {
fetched_revision_node = revision_node.clone();
}
}
}
}
let title = format!("{}", fetched_revision_node.contents.content_list[0]["text"]);
let contents = format!("{}", fetched_revision_node.contents.content_list[1]["text"]);
let author = fetched_revision_node.author.clone();
let time_sent = fetched_revision_node.time_sent_formatted();
Ok(Note::new(
&title,
&author,
&time_sent,
&contents,
&comments,
&fetched_revision_node.index,
))
}
pub fn content_as_markdown(&self) -> Vec<String> {
let formatted_string = self.contents.clone();
formatted_string
.split("\\n")
.map(|l| l.to_string())
.collect()
}
}
impl<'a> Notebook<'a> {
pub fn export_notebook(
&mut self,
notebook_ship: &str,
notebook_name: &str,
) -> Result<Vec<Note>> {
let graph = &self
.channel
.graph_store()
.get_graph(notebook_ship, notebook_name)?;
let mut notes = vec![];
for node in &graph.nodes {
let note = Note::from_node(node, None)?;
notes.push(note);
}
Ok(notes)
}
pub fn fetch_note(
&mut self,
notebook_ship: &str,
notebook_name: &str,
note_index: &str,
) -> Result<Note> {
let index = NotebookIndex::new(note_index);
if !index.is_valid() {
return Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
note_index.to_string(),
));
}
let note_root_index = index.note_root_index();
let node =
&self
.channel
.graph_store()
.get_node(notebook_ship, notebook_name, ¬e_root_index)?;
let revision = match index.is_note_revision() {
true => Some(note_index.to_string()),
false => None,
};
return Ok(Note::from_node(node, revision)?);
}
pub fn fetch_note_with_comment_index(
&mut self,
notebook_ship: &str,
notebook_name: &str,
comment_index: &str,
) -> Result<Note> {
self.fetch_note(notebook_ship, notebook_name, comment_index)
}
pub fn fetch_note_latest_revision_index(
&mut self,
notebook_ship: &str,
notebook_name: &str,
note_index: &str,
) -> Result<String> {
let index = NotebookIndex::new(note_index);
if !index.is_valid() {
return Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
note_index.to_string(),
));
}
let note_root_index = index.note_root_index();
let node =
&self
.channel
.graph_store()
.get_node(notebook_ship, notebook_name, ¬e_root_index)?;
for pnode in &node.children {
if pnode.index_tail() == "1" {
let mut latestindex = NotebookIndex::new(&pnode.children[0].index);
for rev in &pnode.children {
let revindex = NotebookIndex::new(&rev.index);
if revindex.index_tail() > latestindex.index_tail() {
latestindex = revindex.clone();
}
}
return Ok(latestindex.index.to_string());
}
}
Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
note_index.to_string(),
))
}
pub fn fetch_comment(
&mut self,
notebook_ship: &str,
notebook_name: &str,
comment_index: &str,
) -> Result<Comment> {
let index = NotebookIndex::new(comment_index);
if !index.is_valid_comment_index() {
return Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
comment_index.to_string(),
));
}
let comment_root_index = index.comment_root_index()?;
let node = &self.channel.graph_store().get_node(
notebook_ship,
notebook_name,
&comment_root_index,
)?;
if index.is_comment_root() {
let mut newest = node.children[0].clone();
for rnode in &node.children {
if rnode.index_tail() > newest.index_tail() {
newest = rnode.clone();
}
}
return Ok(Comment::from_node(&newest));
} else {
for rnode in &node.children {
if rnode.index == comment_index {
return Ok(Comment::from_node(&rnode));
}
}
}
Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
comment_index.to_string(),
))
}
pub fn fetch_comment_latest_revision_index(
&mut self,
notebook_ship: &str,
notebook_name: &str,
comment_index: &str,
) -> Result<String> {
let index = NotebookIndex::new(comment_index);
if !index.is_valid_comment_index() {
return Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
comment_index.to_string(),
));
}
let comment_root_index = index.comment_root_index()?;
let node = &self.channel.graph_store().get_node(
notebook_ship,
notebook_name,
&comment_root_index,
)?;
if node.children.len() > 0 {
let mut newestindex = NotebookIndex::new(&node.children[0].index);
for rnode in &node.children {
let revindex = NotebookIndex::new(&rnode.index);
if revindex.index_tail() > newestindex.index_tail() {
newestindex = revindex.clone();
}
}
return Ok(newestindex.index.to_string());
}
Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
comment_index.to_string(),
))
}
pub fn add_note(
&mut self,
notebook_ship: &str,
notebook_name: &str,
title: &str,
body: &str,
) -> Result<String> {
let mut gs = self.channel.graph_store();
let node_root = gs.new_node(&NodeContents::new());
let unix_time = node_root.time_sent;
let index = NotebookIndex::new(&node_root.index);
let node_root = node_root
.add_child(&gs.new_node_specified(
&index.note_content_node_index(),
unix_time,
&NodeContents::new(),
))
.add_child(&gs.new_node_specified(
&index.note_comments_node_index(),
unix_time,
&NodeContents::new(),
))
.add_child(&gs.new_node_specified(
&index.note_revision_index(1),
unix_time,
&NodeContents::new().add_text(title).add_text(body),
));
if let Ok(_) = gs.add_node(notebook_ship, notebook_name, &node_root) {
Ok(index.note_revision_index(1))
} else {
Err(UrbitAPIError::FailedToCreateNote(
node_root.to_json().dump(),
))
}
}
pub fn update_note(
&mut self,
notebook_ship: &str,
notebook_name: &str,
note_index: &str,
title: &str,
body: &str,
) -> Result<String> {
let note_latest_index =
self.fetch_note_latest_revision_index(notebook_ship, notebook_name, note_index)?;
let index = NotebookIndex::new(¬e_latest_index);
let note_new_index = index.next_revision_index()?;
let mut gs = self.channel.graph_store();
let unix_time = get_current_time();
let node = gs.new_node_specified(
¬e_new_index,
unix_time,
&NodeContents::new().add_text(title).add_text(body),
);
if let Ok(_) = gs.add_node(notebook_ship, notebook_name, &node) {
Ok(node.index.clone())
} else {
Err(UrbitAPIError::FailedToCreateNote(node.to_json().dump()))
}
}
pub fn add_comment(
&mut self,
notebook_ship: &str,
notebook_name: &str,
note_index: &str,
comment: &NodeContents,
) -> Result<String> {
let index = NotebookIndex::new(note_index);
if !index.is_valid() {
return Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
note_index.to_string(),
));
}
let mut gs = self.channel.graph_store();
let unix_time = get_current_time();
let cmt_root_node = gs.new_node_specified(
&index.new_comment_root_index(),
unix_time,
&NodeContents::new(),
);
let index = NotebookIndex::new(&cmt_root_node.index);
let cmt_rev_index = index.comment_revision_index(1)?;
let cmt_rev_node = gs.new_node_specified(&cmt_rev_index, unix_time, comment);
let cmt_root_node = cmt_root_node.add_child(&cmt_rev_node);
if let Ok(_) = gs.add_node(notebook_ship, notebook_name, &cmt_root_node) {
Ok(cmt_rev_index.clone())
} else {
Err(UrbitAPIError::FailedToCreateComment(
cmt_root_node.to_json().dump(),
))
}
}
pub fn update_comment(
&mut self,
notebook_ship: &str,
notebook_name: &str,
comment_index: &str,
comment: &NodeContents,
) -> Result<String> {
let cmt_latest_index =
self.fetch_comment_latest_revision_index(notebook_ship, notebook_name, comment_index)?;
let index = NotebookIndex::new(&cmt_latest_index);
let cmt_new_index = index.next_revision_index()?;
let mut gs = self.channel.graph_store();
let unix_time = get_current_time();
let node = gs.new_node_specified(&cmt_new_index, unix_time, comment);
if let Ok(_) = gs.add_node(notebook_ship, notebook_name, &node) {
Ok(node.index.clone())
} else {
Err(UrbitAPIError::FailedToCreateComment(node.to_json().dump()))
}
}
}
impl<'a> NotebookIndex<'a> {
pub fn new(idx: &str) -> NotebookIndex {
NotebookIndex {
index: idx,
index_split: idx.split("/").collect(),
}
}
pub fn is_valid(&self) -> bool {
(self.index_split.len() >= 2) && (self.index_split[0].len() == 0)
}
pub fn is_note_root(&self) -> bool {
(self.index_split.len() == 2) && (self.index_split[0].len() == 0)
}
pub fn is_note_revision(&self) -> bool {
(self.index_split.len() == 4)
&& (self.index_split[0].len() == 0)
&& (self.index_split[2] == "1")
}
pub fn is_valid_comment_index(&self) -> bool {
(self.index_split.len() >= 4)
&& (self.index_split[0].len() == 0)
&& (self.index_split[2] == "2")
}
pub fn is_comment_root(&self) -> bool {
(self.index_split.len() == 4)
&& (self.index_split[0].len() == 0)
&& (self.index_split[2] == "2")
}
pub fn is_comment_revision(&self) -> bool {
(self.index_split.len() == 5)
&& (self.index_split[0].len() == 0)
&& (self.index_split[2] == "2")
}
pub fn note_root_index(&self) -> String {
format!("/{}", self.index_split[1])
}
pub fn note_content_node_index(&self) -> String {
format!("/{}/1", self.index_split[1])
}
pub fn note_comments_node_index(&self) -> String {
format!("/{}/2", self.index_split[1])
}
pub fn comment_root_index(&self) -> Result<String> {
if self.is_valid_comment_index() {
Ok(format!(
"/{}/2/{}",
self.index_split[1], self.index_split[3]
))
} else {
Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
self.index.to_string(),
))
}
}
pub fn new_comment_root_index(&self) -> String {
format!("/{}/2/{}", self.index_split[1], get_current_da_time())
}
pub fn index_tail(&self) -> &str {
self.index_split[self.index_split.len() - 1]
}
pub fn revision(&self) -> Result<u64> {
if self.is_note_revision() {
if let Ok(r) = self.index_split[3].parse::<u64>() {
return Ok(r);
}
} else if self.is_comment_revision() {
if let Ok(r) = self.index_split[4].parse::<u64>() {
return Ok(r);
}
}
Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
self.index.to_string(),
))
}
pub fn next_revision_index(&self) -> Result<String> {
let rev = self.revision()?;
let newrev = rev + 1;
if self.index_split.len() == 5 {
Ok(format!(
"/{}/2/{}/{}",
self.index_split[1],
self.index_split[3],
&newrev.to_string()
))
} else {
Ok(format!(
"/{}/1/{}",
self.index_split[1],
&newrev.to_string()
))
}
}
pub fn note_revision_index(&self, revision: u64) -> String {
format!("/{}/1/{}", self.index_split[1], revision.to_string())
}
pub fn comment_revision_index(&self, revision: u64) -> Result<String> {
if self.is_valid_comment_index() {
Ok(format!(
"/{}/2/{}/{}",
self.index_split[1],
self.index_split[3],
revision.to_string()
))
} else {
Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
self.index.to_string(),
))
}
}
}