Compare commits

...

18 Commits

Author SHA1 Message Date
fa5aac34ba chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 44s
Continuous integration / Rustfmt (push) Successful in 31s
Continuous integration / Trunk (push) Successful in 55s
Continuous integration / build (push) Successful in 51s
Continuous integration / Disallow unused dependencies (push) Successful in 2m7s
2025-04-24 12:03:13 -07:00
b58556254e notmuch: log any stderr output 2025-04-24 12:02:55 -07:00
e365ced7dd server: more concise slice of ids 2025-04-24 12:02:40 -07:00
93d569fb14 chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 44s
Continuous integration / Rustfmt (push) Successful in 31s
Continuous integration / Trunk (push) Successful in 51s
Continuous integration / build (push) Successful in 52s
Continuous integration / Disallow unused dependencies (push) Successful in 2m4s
2025-04-24 09:04:42 -07:00
f86a5f464d server: properly limit index 2025-04-24 09:04:22 -07:00
956c20b156 chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 38s
Continuous integration / Trunk (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 1m13s
Continuous integration / Rustfmt (push) Successful in 30s
Continuous integration / Disallow unused dependencies (push) Successful in 56s
Continuous integration / build (push) Successful in 1m34s
2025-04-24 08:56:56 -07:00
1eb498712b server: prevent out of bounds index at end of processing 2025-04-24 08:56:19 -07:00
f12979c0be chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 45s
Continuous integration / Rustfmt (push) Successful in 31s
Continuous integration / build (push) Successful in 52s
Continuous integration / Disallow unused dependencies (push) Successful in 55s
Continuous integration / Trunk (push) Successful in 7m10s
2025-04-23 18:59:16 -07:00
4665f34e54 server: label_unprocessed handle case where files cannot be found from message-id 2025-04-23 18:57:54 -07:00
bbdc35061c chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 45s
Continuous integration / Trunk (push) Successful in 37s
Continuous integration / Rustfmt (push) Successful in 39s
Continuous integration / build (push) Successful in 52s
Continuous integration / Disallow unused dependencies (push) Successful in 2m9s
2025-04-23 15:25:34 -07:00
f11f0b4d23 server: migrate all use of log to tracing 2025-04-23 15:25:11 -07:00
c7c47e4a73 chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 44s
Continuous integration / Trunk (push) Successful in 38s
Continuous integration / Rustfmt (push) Successful in 39s
Continuous integration / build (push) Successful in 51s
Continuous integration / Disallow unused dependencies (push) Successful in 2m5s
2025-04-23 14:57:39 -07:00
c3835522b2 server: add Letterbox/Bad label to unparsable emails, and consider them processed 2025-04-23 14:57:13 -07:00
dfa80f9046 chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 55s
Continuous integration / Trunk (push) Successful in 52s
Continuous integration / Rustfmt (push) Successful in 39s
Continuous integration / Disallow unused dependencies (push) Successful in 58s
Continuous integration / build (push) Successful in 1m32s
2025-04-23 14:41:25 -07:00
b8dfdabf8d server: more tracing and logging 2025-04-23 14:41:11 -07:00
bbcf52b006 chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 43s
Continuous integration / Rustfmt (push) Successful in 31s
Continuous integration / Trunk (push) Successful in 51s
Continuous integration / build (push) Successful in 51s
Continuous integration / Disallow unused dependencies (push) Successful in 2m5s
2025-04-23 11:38:48 -07:00
f92c05cd28 server: return ids processed from send_refresh_websocket_handler 2025-04-23 11:38:30 -07:00
885bbe0a8c chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 40s
Continuous integration / Test Suite (push) Successful in 45s
Continuous integration / Rustfmt (push) Successful in 31s
Continuous integration / Trunk (push) Successful in 52s
Continuous integration / build (push) Successful in 55s
Continuous integration / Disallow unused dependencies (push) Successful in 2m5s
2025-04-23 11:09:19 -07:00
10 changed files with 63 additions and 38 deletions

17
Cargo.lock generated
View File

@@ -3034,7 +3034,7 @@ dependencies = [
[[package]] [[package]]
name = "letterbox-notmuch" name = "letterbox-notmuch"
version = "0.17.14" version = "0.17.23"
dependencies = [ dependencies = [
"itertools", "itertools",
"log", "log",
@@ -3049,7 +3049,7 @@ dependencies = [
[[package]] [[package]]
name = "letterbox-procmail2notmuch" name = "letterbox-procmail2notmuch"
version = "0.17.14" version = "0.17.23"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -3062,7 +3062,7 @@ dependencies = [
[[package]] [[package]]
name = "letterbox-server" name = "letterbox-server"
version = "0.17.14" version = "0.17.23"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"anyhow", "anyhow",
@@ -3080,10 +3080,9 @@ dependencies = [
"futures 0.3.31", "futures 0.3.31",
"headers", "headers",
"html-escape", "html-escape",
"letterbox-notmuch 0.17.14", "letterbox-notmuch 0.17.23",
"letterbox-shared 0.17.14", "letterbox-shared 0.17.23",
"linkify", "linkify",
"log",
"lol_html", "lol_html",
"mailparse", "mailparse",
"maplit", "maplit",
@@ -3121,10 +3120,10 @@ dependencies = [
[[package]] [[package]]
name = "letterbox-shared" name = "letterbox-shared"
version = "0.17.14" version = "0.17.23"
dependencies = [ dependencies = [
"build-info", "build-info",
"letterbox-notmuch 0.17.14", "letterbox-notmuch 0.17.23",
"regex", "regex",
"serde", "serde",
"sqlx", "sqlx",
@@ -3134,7 +3133,7 @@ dependencies = [
[[package]] [[package]]
name = "letterbox-web" name = "letterbox-web"
version = "0.17.14" version = "0.17.23"
dependencies = [ dependencies = [
"build-info", "build-info",
"build-info-build", "build-info-build",

View File

@@ -8,7 +8,7 @@ authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2021" edition = "2021"
license = "UNLICENSED" license = "UNLICENSED"
publish = ["xinu"] publish = ["xinu"]
version = "0.17.14" version = "0.17.23"
repository = "https://git.z.xinu.tv/wathiede/letterbox" repository = "https://git.z.xinu.tv/wathiede/letterbox"
[profile.dev] [profile.dev]

View File

@@ -214,9 +214,8 @@ use std::{
process::Command, process::Command,
}; };
use log::{error, info};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::instrument; use tracing::{error, info, instrument, warn};
/// # Number of seconds since the Epoch /// # Number of seconds since the Epoch
pub type UnixTime = isize; pub type UnixTime = isize;
@@ -718,6 +717,13 @@ impl Notmuch {
cmd.args(args); cmd.args(args);
info!("{:?}", &cmd); info!("{:?}", &cmd);
let out = cmd.output()?; let out = cmd.output()?;
if !out.stderr.is_empty() {
warn!(
"{:?}: STDERR:\n{}",
&cmd,
String::from_utf8_lossy(&out.stderr)
);
}
Ok(out.stdout) Ok(out.stdout)
} }
} }

View File

@@ -27,10 +27,9 @@ css-inline = "0.14.4"
futures = "0.3.31" futures = "0.3.31"
headers = "0.4.0" headers = "0.4.0"
html-escape = "0.2.13" html-escape = "0.2.13"
letterbox-notmuch = { path = "../notmuch", version = "0.17.14", registry = "xinu" } letterbox-notmuch = { path = "../notmuch", version = "0.17.23", registry = "xinu" }
letterbox-shared = { path = "../shared", version = "0.17.14", registry = "xinu" } letterbox-shared = { path = "../shared", version = "0.17.23", registry = "xinu" }
linkify = "0.10.0" linkify = "0.10.0"
log = "0.4.27"
lol_html = "2.3.0" lol_html = "2.3.0"
mailparse = "0.16.1" mailparse = "0.16.1"
maplit = "1.0.2" maplit = "1.0.2"

View File

@@ -200,15 +200,21 @@ async fn send_refresh_websocket_handler(
None => Some(10), None => Some(10),
}; };
if let Err(err) = label_unprocessed(&nm, &pool, false, limit, "tag:unprocessed").await { let mut ids = None;
error!("Failed to label_unprocessed: {err:?}"); match label_unprocessed(&nm, &pool, false, limit, "tag:unprocessed").await {
Ok(i) => ids = Some(i),
Err(err) => error!("Failed to label_unprocessed: {err:?}"),
}; };
connection_tracker connection_tracker
.lock() .lock()
.await .await
.send_message_all(WebsocketMessage::RefreshMessages) .send_message_all(WebsocketMessage::RefreshMessages)
.await; .await;
"refresh triggered" if let Some(ids) = ids {
format!("{ids:?}")
} else {
"refresh triggered".to_string()
}
} }
async fn watch_new( async fn watch_new(

View File

@@ -9,11 +9,10 @@ use async_graphql::{
use cacher::FilesystemCacher; use cacher::FilesystemCacher;
use futures::stream; use futures::stream;
use letterbox_notmuch::Notmuch; use letterbox_notmuch::Notmuch;
use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
use tokio::join; use tokio::join;
use tracing::instrument; use tracing::{info, instrument};
#[cfg(feature = "tantivy")] #[cfg(feature = "tantivy")]
use crate::tantivy::TantivyConnection; use crate::tantivy::TantivyConnection;

View File

@@ -21,7 +21,6 @@ use cacher::{Cacher, FilesystemCacher};
use css_inline::{CSSInliner, InlineError, InlineOptions}; use css_inline::{CSSInliner, InlineError, InlineOptions};
pub use error::ServerError; pub use error::ServerError;
use linkify::{LinkFinder, LinkKind}; use linkify::{LinkFinder, LinkKind};
use log::{debug, error, info, warn};
use lol_html::{ use lol_html::{
element, errors::RewritingError, html_content::ContentType, rewrite_str, text, element, errors::RewritingError, html_content::ContentType, rewrite_str, text,
RewriteStrSettings, RewriteStrSettings,
@@ -32,6 +31,7 @@ use reqwest::StatusCode;
use scraper::{Html, Selector}; use scraper::{Html, Selector};
use sqlx::types::time::PrimitiveDateTime; use sqlx::types::time::PrimitiveDateTime;
use thiserror::Error; use thiserror::Error;
use tracing::{debug, error, info, warn};
use url::Url; use url::Url;
use crate::{ use crate::{

View File

@@ -3,11 +3,10 @@ use std::collections::HashMap;
use cacher::FilesystemCacher; use cacher::FilesystemCacher;
use futures::{stream::FuturesUnordered, StreamExt}; use futures::{stream::FuturesUnordered, StreamExt};
use letterbox_shared::compute_color; use letterbox_shared::compute_color;
use log::{error, info};
use maplit::hashmap; use maplit::hashmap;
use scraper::Selector; use scraper::Selector;
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
use tracing::instrument; use tracing::{error, info, instrument};
use url::Url; use url::Url;
use crate::{ use crate::{

View File

@@ -5,11 +5,10 @@ use std::{
use letterbox_notmuch::Notmuch; use letterbox_notmuch::Notmuch;
use letterbox_shared::{compute_color, Rule}; use letterbox_shared::{compute_color, Rule};
use log::{error, info, warn};
use mailparse::{parse_content_type, parse_mail, MailHeader, MailHeaderMap, ParsedMail}; use mailparse::{parse_content_type, parse_mail, MailHeader, MailHeaderMap, ParsedMail};
use memmap::MmapOptions; use memmap::MmapOptions;
use sqlx::{types::Json, PgPool}; use sqlx::{types::Json, PgPool};
use tracing::instrument; use tracing::{error, info, info_span, instrument, warn};
use crate::{ use crate::{
compute_offset_limit, compute_offset_limit,
@@ -946,7 +945,7 @@ pub async fn label_unprocessed(
dryrun: bool, dryrun: bool,
limit: Option<usize>, limit: Option<usize>,
query: &str, query: &str,
) -> Result<(), ServerError> { ) -> Result<Box<[String]>, ServerError> {
use futures::StreamExt; use futures::StreamExt;
let ids = nm.message_ids(query)?; let ids = nm.message_ids(query)?;
info!( info!(
@@ -977,22 +976,38 @@ pub async fn label_unprocessed(
*/ */
info!("Loaded {} rules", rules.len()); info!("Loaded {} rules", rules.len());
let ids = if let Some(limit) = limit { let limit = limit.unwrap_or(ids.len());
&ids[..limit] let limit = limit.min(ids.len());
} else { let ids = &ids[..limit];
&ids[..]
};
let mut add_mutations = HashMap::new(); let mut add_mutations = HashMap::new();
let mut rm_mutations = HashMap::new(); let mut rm_mutations = HashMap::new();
for id in ids { for id in ids {
let id = format!("id:{id}"); let id = format!("id:{id}");
let files = nm.files(&id)?; let files = nm.files(&id)?;
// Only process the first file path is multiple files have the same id // Only process the first file path is multiple files have the same id
let path = files.iter().next().unwrap(); let Some(path) = files.iter().next() else {
error!("No files for message-ID {id}");
let t = "Letterbox/Bad";
nm.tag_add(t, &id)?;
let t = "unprocessed";
nm.tag_remove(t, &id)?;
continue;
};
let file = File::open(&path)?; let file = File::open(&path)?;
info!("mmaping {path}"); info!("parsing {path}");
let mmap = unsafe { MmapOptions::new().map(&file)? }; let mmap = unsafe { MmapOptions::new().map(&file)? };
let m = parse_mail(&mmap)?; let m = match info_span!("parse_mail", path = path).in_scope(|| parse_mail(&mmap)) {
Ok(m) => m,
Err(err) => {
error!("Failed to parse {path}: {err}");
let t = "Letterbox/Bad";
nm.tag_add(t, &id)?;
let t = "unprocessed";
nm.tag_remove(t, &id)?;
continue;
}
};
let (matched_rule, add_tags) = find_tags(&rules, &m.headers); let (matched_rule, add_tags) = find_tags(&rules, &m.headers);
if matched_rule { if matched_rule {
if dryrun { if dryrun {
@@ -1055,7 +1070,8 @@ pub async fn label_unprocessed(
info!(" {tag}: {}", ids.len()); info!(" {tag}: {}", ids.len());
if !dryrun { if !dryrun {
let ids: Vec<_> = ids.iter().map(|s| s.as_str()).collect(); let ids: Vec<_> = ids.iter().map(|s| s.as_str()).collect();
nm.tags_add(tag, &ids)?; info_span!("tags_add", tag = tag, count = ids.len())
.in_scope(|| nm.tags_add(tag, &ids))?;
} }
} }
info!("Removing {} distinct labels", rm_mutations.len()); info!("Removing {} distinct labels", rm_mutations.len());
@@ -1063,11 +1079,12 @@ pub async fn label_unprocessed(
info!(" {tag}: {}", ids.len()); info!(" {tag}: {}", ids.len());
if !dryrun { if !dryrun {
let ids: Vec<_> = ids.iter().map(|s| s.as_str()).collect(); let ids: Vec<_> = ids.iter().map(|s| s.as_str()).collect();
nm.tags_remove(tag, &ids)?; info_span!("tags_remove", tag = tag, count = ids.len())
.in_scope(|| nm.tags_remove(tag, &ids))?;
} }
} }
Ok(()) Ok(ids.into())
} }
fn find_tags<'a, 'b>(rules: &'a [Rule], headers: &'b [MailHeader]) -> (bool, HashSet<&'a str>) { fn find_tags<'a, 'b>(rules: &'a [Rule], headers: &'b [MailHeader]) -> (bool, HashSet<&'a str>) {
let mut matched_rule = false; let mut matched_rule = false;

View File

@@ -12,7 +12,7 @@ version.workspace = true
[dependencies] [dependencies]
build-info = "0.0.40" build-info = "0.0.40"
letterbox-notmuch = { path = "../notmuch", version = "0.17.14", registry = "xinu" } letterbox-notmuch = { path = "../notmuch", version = "0.17.23", registry = "xinu" }
regex = "1.11.1" regex = "1.11.1"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
sqlx = "0.8.5" sqlx = "0.8.5"