148 lines
4.5 KiB
Rust
148 lines
4.5 KiB
Rust
use std::hash::{DefaultHasher, Hash, Hasher};
|
|
|
|
use async_graphql::{
|
|
connection::{self, Connection, Edge},
|
|
Context, EmptyMutation, EmptySubscription, Error, FieldResult, Object, Schema, SimpleObject,
|
|
};
|
|
use log::info;
|
|
use notmuch::Notmuch;
|
|
|
|
pub struct QueryRoot;
|
|
|
|
/// # Number of seconds since the Epoch
|
|
pub type UnixTime = isize;
|
|
|
|
/// # Thread ID, sans "thread:"
|
|
pub type ThreadId = String;
|
|
|
|
#[derive(Debug, SimpleObject)]
|
|
pub struct ThreadSummary {
|
|
pub thread: ThreadId,
|
|
pub timestamp: UnixTime,
|
|
/// user-friendly timestamp
|
|
pub date_relative: String,
|
|
/// number of matched messages
|
|
pub matched: isize,
|
|
/// total messages in thread
|
|
pub total: isize,
|
|
/// comma-separated names with | between matched and unmatched
|
|
pub authors: String,
|
|
pub subject: String,
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
#[derive(SimpleObject)]
|
|
struct Tag {
|
|
name: String,
|
|
fg_color: String,
|
|
bg_color: String,
|
|
}
|
|
|
|
#[Object]
|
|
impl QueryRoot {
|
|
async fn count<'ctx>(&self, ctx: &Context<'ctx>, query: String) -> Result<usize, Error> {
|
|
let nm = ctx.data_unchecked::<Notmuch>();
|
|
Ok(nm.count(&query)?)
|
|
}
|
|
|
|
async fn search<'ctx>(
|
|
&self,
|
|
ctx: &Context<'ctx>,
|
|
after: Option<String>,
|
|
before: Option<String>,
|
|
first: Option<i32>,
|
|
last: Option<i32>,
|
|
query: String,
|
|
) -> Result<Connection<usize, ThreadSummary>, Error> {
|
|
let nm = ctx.data_unchecked::<Notmuch>();
|
|
connection::query(
|
|
after,
|
|
before,
|
|
first,
|
|
last,
|
|
|after, before, first, last| async move {
|
|
info!("{after:?} {before:?} {first:?} {last:?} {query}");
|
|
let mut start = 0usize;
|
|
let total = nm.count(&query)?;
|
|
let page_size = first.unwrap_or(20);
|
|
|
|
if let Some(after) = after {
|
|
if after >= total {
|
|
return Ok(Connection::new(false, false));
|
|
}
|
|
start = after + 1;
|
|
}
|
|
let mut end = start + page_size;
|
|
if end > total {
|
|
end = total;
|
|
}
|
|
|
|
// TODO(wathiede): handle last/end.
|
|
if let Some(before) = before {
|
|
if before == 0 {
|
|
return Ok(Connection::new(false, false));
|
|
}
|
|
end = before;
|
|
}
|
|
|
|
let slice: Vec<ThreadSummary> = nm
|
|
.search(&query, start, first.unwrap_or(20))?
|
|
.0
|
|
.into_iter()
|
|
.map(|ts| ThreadSummary {
|
|
thread: ts.thread,
|
|
timestamp: ts.timestamp,
|
|
date_relative: ts.date_relative,
|
|
matched: ts.matched,
|
|
total: ts.total,
|
|
authors: ts.authors,
|
|
subject: ts.subject,
|
|
tags: ts.tags,
|
|
})
|
|
.collect();
|
|
//let mut slice = &res[..];
|
|
|
|
/*
|
|
if let Some(first) = first {
|
|
slice = &slice[..first.min(slice.len())];
|
|
end -= first.min(slice.len());
|
|
} else if let Some(last) = last {
|
|
slice = &slice[slice.len() - last.min(slice.len())..];
|
|
start = end - last.min(slice.len());
|
|
}
|
|
*/
|
|
|
|
let mut connection = Connection::new(start > 0, end < total);
|
|
connection.edges.extend(
|
|
slice
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(idx, item)| Edge::new(start + idx, item)),
|
|
);
|
|
Ok::<_, Error>(connection)
|
|
},
|
|
)
|
|
.await
|
|
}
|
|
|
|
async fn tags<'ctx>(&self, ctx: &Context<'ctx>) -> FieldResult<Vec<Tag>> {
|
|
let nm = ctx.data_unchecked::<Notmuch>();
|
|
Ok(nm
|
|
.tags()?
|
|
.into_iter()
|
|
.map(|tag| {
|
|
let mut hasher = DefaultHasher::new();
|
|
tag.hash(&mut hasher);
|
|
let hex = format!("#{:06x}", hasher.finish() % (1 << 24));
|
|
Tag {
|
|
name: tag,
|
|
fg_color: "white".to_string(),
|
|
bg_color: hex,
|
|
}
|
|
})
|
|
.collect())
|
|
}
|
|
}
|
|
|
|
pub type GraphqlSchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
|