@@ -6,11 +6,12 @@ mod render;
66mod settings;
77mod sleep;
88
9+ use std:: path:: PathBuf ;
910use std:: sync:: Arc ;
1011use std:: time:: Duration ;
1112
1213use botloft_core:: ids:: BotId ;
13- use botloft_core:: protocol:: { BotState , Delivery , Message , SenderKind } ;
14+ use botloft_core:: protocol:: { BotState , Delivery , Message , SenderKind , Task } ;
1415use botloft_store:: { DeliveryOutcome , Store , StoreError } ;
1516use tokio:: sync:: Notify ;
1617use tracing:: { debug, warn} ;
@@ -69,36 +70,47 @@ pub async fn run(daemon: Arc<Daemon>) {
6970
7071/// What to do with a due delivery.
7172enum Step {
72- Send ( Rendered ) ,
73+ Send ( Box < Draft > ) ,
7374 /// The bot cannot take messages yet.
7475 Wait ,
7576 /// It can never be delivered.
7677 Drop ( & ' static str ) ,
7778}
7879
80+ /// One pass over the queue. The store is locked step by step, never
81+ /// while an attachment is read from disk.
7982fn cycle ( daemon : & Arc < Daemon > ) {
8083 let now = daemon. clock . now_ms ( ) ;
81- let store = daemon. store ( ) ;
82- match store. recover_leases ( now) {
83- Ok ( recovered) => {
84- for delivery in recovered {
85- debug ! ( delivery = %delivery. id, "a send was cut off; delivering again" ) ;
86- daemon. emit ( Event :: DeliveryChanged ( delivery) ) ;
84+ let due = {
85+ let store = daemon. store ( ) ;
86+ match store. recover_leases ( now) {
87+ Ok ( recovered) => {
88+ for delivery in recovered {
89+ debug ! ( delivery = %delivery. id, "a send was cut off; delivering again" ) ;
90+ daemon. emit ( Event :: DeliveryChanged ( delivery) ) ;
91+ }
8792 }
93+ Err ( err) => warn ! ( "courier could not recover leases: {err}" ) ,
8894 }
89- Err ( err) => warn ! ( "courier could not recover leases: {err}" ) ,
90- }
91- tasks:: expire_overdue ( daemon, & store, now) ;
92- let due = match store. due_deliveries ( now) {
93- Ok ( due) => due,
94- Err ( err) => {
95- warn ! ( "courier could not read the queue: {err}" ) ;
96- return ;
95+ tasks:: expire_overdue ( daemon, & store, now) ;
96+ match store. due_deliveries ( now) {
97+ Ok ( due) => due,
98+ Err ( err) => {
99+ warn ! ( "courier could not read the queue: {err}" ) ;
100+ return ;
101+ }
97102 }
98103 } ;
99104 for delivery in due {
100- let changed = match prepare ( daemon, & store, & delivery, now) {
101- Ok ( Step :: Send ( rendered) ) => send ( daemon, & store, & delivery, & rendered, now) ,
105+ let store = daemon. store ( ) ;
106+ let changed = match prepare ( daemon, & store, & delivery) {
107+ Ok ( Step :: Send ( draft) ) => {
108+ drop ( store) ;
109+ // Inline images are read here, with the store free.
110+ let rendered = draft. render ( now) ;
111+ // Claiming checks the delivery is still waiting.
112+ send ( daemon, & daemon. store ( ) , & delivery, & rendered, now)
113+ }
102114 Ok ( Step :: Wait ) => {
103115 let until = clock:: after ( now, WAIT_FOR_BOT ) ;
104116 finish ( & store, & delivery, DeliveryOutcome :: Defer { until } , now)
@@ -170,13 +182,8 @@ fn send(
170182 finish ( store, delivery, outcome, now)
171183}
172184
173- /// Decides whether the delivery can go now and renders it.
174- fn prepare (
175- daemon : & Daemon ,
176- store : & Store ,
177- delivery : & Delivery ,
178- now : i64 ,
179- ) -> Result < Step , StoreError > {
185+ /// Decides whether the delivery can go now and gathers what it needs.
186+ fn prepare ( daemon : & Daemon , store : & Store , delivery : & Delivery ) -> Result < Step , StoreError > {
180187 let Some ( message) = store. message ( & delivery. message_id ) ? else {
181188 return Ok ( Step :: Drop ( "the message no longer exists" ) ) ;
182189 } ;
@@ -204,18 +211,42 @@ fn prepare(
204211 ( SenderKind :: Bot , Some ( id) ) => store. bot ( id) ?. map ( |sender| sender. handle ) ,
205212 _ => None ,
206213 } ;
207- let workspace = daemon. paths . bot_workspace ( & crew. slug , & bot. slug ) ;
208214 let routine = routine_context ( store, & message) ?;
209- let context = Context {
210- crew_name : & crew. name ,
211- sender_handle : sender. as_deref ( ) ,
212- task : task. as_ref ( ) ,
213- workspace : & workspace,
214- routine : routine
215- . as_ref ( )
216- . map ( |( name, scheduled, zone) | ( name. as_str ( ) , scheduled. as_str ( ) , zone. as_str ( ) ) ) ,
217- } ;
218- Ok ( Step :: Send ( render ( & message, & context, now) ) )
215+ Ok ( Step :: Send ( Box :: new ( Draft {
216+ workspace : daemon. paths . bot_workspace ( & crew. slug , & bot. slug ) ,
217+ message,
218+ crew_name : crew. name ,
219+ sender_handle : sender,
220+ task,
221+ routine,
222+ } ) ) )
223+ }
224+
225+ /// A delivery about to go, read from the store.
226+ struct Draft {
227+ message : Message ,
228+ crew_name : String ,
229+ sender_handle : Option < String > ,
230+ task : Option < Task > ,
231+ workspace : PathBuf ,
232+ routine : Option < ( String , String , String ) > ,
233+ }
234+
235+ impl Draft {
236+ /// The line for the bot's stdin; reads the images it inlines.
237+ fn render ( & self , now : i64 ) -> Rendered {
238+ let context = Context {
239+ crew_name : & self . crew_name ,
240+ sender_handle : self . sender_handle . as_deref ( ) ,
241+ task : self . task . as_ref ( ) ,
242+ workspace : & self . workspace ,
243+ routine : self
244+ . routine
245+ . as_ref ( )
246+ . map ( |( name, scheduled, zone) | ( name. as_str ( ) , scheduled. as_str ( ) , zone. as_str ( ) ) ) ,
247+ } ;
248+ render ( & self . message , & context, now)
249+ }
219250}
220251
221252/// A routine's name, the local time the run is for and the zone.
0 commit comments