atagen

macltok

public
Star0
Watch0
c9f0fd0ee1cccdd0048f121ba1c8ab1f12482f5d
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/engine/repair.rs

c9f0fd0ee1ccatagen2026-10-25 06:16 UTC1
use matrix_sdk::{Room, event_cache::HistoryGapFillOutcome};
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC2
use tokio::sync::watch;
80381e06a573atagen2026-10-25 03:02 UTC3
80381e06a573atagen2026-10-25 03:02 UTC4
use super::{
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC5
    executor::{JoinError, JoinHandle, spawn},
80381e06a573atagen2026-10-25 03:02 UTC6
    timeline::SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC7
};
80381e06a573atagen2026-10-25 03:02 UTC8
use crate::{
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC9
    CommandOutput, Error, ErrorKind, HistoryContinuity, HistoryRepairBudget, HistoryRepairCommand,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC10
    HistoryRepairPhase, HistoryRepairPolicy, HistoryRepairReport, HistoryRepairStop,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC11
    HistoryRepairStrategy, RecentHistoryState, transport::Responder,
80381e06a573atagen2026-10-25 03:02 UTC12
};
80381e06a573atagen2026-10-25 03:02 UTC13
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC14
const MAX_GAP_PAGES: u32 = 4;
80381e06a573atagen2026-10-25 03:02 UTC15
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC16
struct Running {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC17
    task: JoinHandle<CompletionResult>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC18
    cancel: watch::Sender<bool>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC19
    responders: Vec<PendingResponder>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC20
    recent: bool,
80381e06a573atagen2026-10-25 03:02 UTC21
}
80381e06a573atagen2026-10-25 03:02 UTC22
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC23
struct PendingResponder {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC24
    responder: Responder,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC25
    cancel_ack: bool,
80381e06a573atagen2026-10-25 03:02 UTC26
}
80381e06a573atagen2026-10-25 03:02 UTC27
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC28
pub(super) struct Completion {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC29
    pub(super) report: HistoryRepairReport,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC30
    pub(super) recent_state: Option<RecentHistoryState>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC31
    pub(super) result: Result<CommandOutput, Error>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC32
    responders: Vec<PendingResponder>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC33
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC34
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC35
impl Completion {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC36
    pub(super) fn resolve(self, result: Result<CommandOutput, Error>, cancellation: Result<(), Error>) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC37
        for responder in self.responders {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC38
            if responder.cancel_ack {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC39
                responder.responder.done(cancellation.clone());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC40
            } else {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC41
                responder.responder.send(result.clone());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC42
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC43
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC44
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC45
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC46
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC47
struct CompletionResult {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC48
    report: HistoryRepairReport,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC49
    recent_state: Option<RecentHistoryState>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC50
    result: Result<CommandOutput, Error>,
80381e06a573atagen2026-10-25 03:02 UTC51
}
80381e06a573atagen2026-10-25 03:02 UTC52
80381e06a573atagen2026-10-25 03:02 UTC53
pub(super) struct RepairController {
80381e06a573atagen2026-10-25 03:02 UTC54
    room: Room,
80381e06a573atagen2026-10-25 03:02 UTC55
    context: SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC56
    running: Option<Running>,
80381e06a573atagen2026-10-25 03:02 UTC57
}
80381e06a573atagen2026-10-25 03:02 UTC58
80381e06a573atagen2026-10-25 03:02 UTC59
impl RepairController {
80381e06a573atagen2026-10-25 03:02 UTC60
    pub(super) fn new(room: Room, context: SessionContext) -> Self {
80381e06a573atagen2026-10-25 03:02 UTC61
        Self {
80381e06a573atagen2026-10-25 03:02 UTC62
            room,
80381e06a573atagen2026-10-25 03:02 UTC63
            context,
80381e06a573atagen2026-10-25 03:02 UTC64
            running: None,
80381e06a573atagen2026-10-25 03:02 UTC65
        }
80381e06a573atagen2026-10-25 03:02 UTC66
    }
80381e06a573atagen2026-10-25 03:02 UTC67
80381e06a573atagen2026-10-25 03:02 UTC68
    pub(super) fn is_running(&self) -> bool {
80381e06a573atagen2026-10-25 03:02 UTC69
        self.running.is_some()
80381e06a573atagen2026-10-25 03:02 UTC70
    }
80381e06a573atagen2026-10-25 03:02 UTC71
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC72
    pub(super) async fn start_recent(&mut self) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC73
        self.start_recent_with(HistoryRepairPolicy::default(), None, true, Vec::new())
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC74
            .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC75
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC76
80381e06a573atagen2026-10-25 03:02 UTC77
    pub(super) async fn execute(&mut self, command: HistoryRepairCommand, reply: Responder) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC78
        match command {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC79
            HistoryRepairCommand::Cancel { .. } => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC80
                if let Some(running) = self.running.as_mut() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC81
                    running.responders.push(PendingResponder {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC82
                        responder: reply,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC83
                        cancel_ack: true,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC84
                    });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC85
                    running.cancel.send_replace(true);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC86
                } else {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC87
                    reply.done(Ok(()));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC88
                }
80381e06a573atagen2026-10-25 03:02 UTC89
            }
80381e06a573atagen2026-10-25 03:02 UTC90
            HistoryRepairCommand::Start { policy, budget, .. } => {
80381e06a573atagen2026-10-25 03:02 UTC91
                if let Err(error) = validate_budget(&budget) {
80381e06a573atagen2026-10-25 03:02 UTC92
                    reply.done(Err(error));
80381e06a573atagen2026-10-25 03:02 UTC93
                    return;
80381e06a573atagen2026-10-25 03:02 UTC94
                }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC95
                let policy = policy.unwrap_or_default();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC96
                self.start_recent_with(policy, Some(reply), true, Vec::new())
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC97
                    .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC98
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC99
            HistoryRepairCommand::StartRecent { .. } => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC100
                self.start_recent_with(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC101
                    HistoryRepairPolicy::default(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC102
                    Some(reply),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC103
                    true,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC104
                    Vec::new(),
80381e06a573atagen2026-10-25 03:02 UTC105
                )
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC106
                .await;
80381e06a573atagen2026-10-25 03:02 UTC107
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC108
            HistoryRepairCommand::FillGap { gap_id, .. } => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC109
                self.start_gap_fill(gap_id, reply).await;
80381e06a573atagen2026-10-25 03:02 UTC110
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC111
            HistoryRepairCommand::Continue { .. } => reply.done(Err(Error::new(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC112
                ErrorKind::Invalid,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC113
                "continue requires a history gap ID, use FillGap",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC114
            ))),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC115
            HistoryRepairCommand::GetPolicy => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC116
                reply.send(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC117
                    self.context
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC118
                        .mentions
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC119
                        .repair_policy()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC120
                        .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC121
                        .map(CommandOutput::HistoryRepairPolicy),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC122
                );
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC123
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC124
            HistoryRepairCommand::SetPolicy(policy) => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC125
                reply.done(self.context.mentions.set_repair_policy(policy).await);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC126
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC127
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC128
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC129
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC130
    async fn start_recent_with(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC131
        &mut self,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC132
        policy: HistoryRepairPolicy,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC133
        responder: Option<Responder>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC134
        publish_recent: bool,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC135
        mut responders: Vec<PendingResponder>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC136
    ) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC137
        if let Some(running) = &mut self.running {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC138
            if let Some(responder) = responder {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC139
                if running.recent {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC140
                    running.responders.push(PendingResponder { responder, cancel_ack: false });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC141
                } else {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC142
                    responder.done(Err(room_busy()));
80381e06a573atagen2026-10-25 03:02 UTC143
                }
80381e06a573atagen2026-10-25 03:02 UTC144
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC145
            return;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC146
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC147
        if let Some(responder) = responder {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC148
            responders.push(PendingResponder {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC149
                responder,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC150
                cancel_ack: false,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC151
            });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC152
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC153
        if publish_recent
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC154
            && self
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC155
                .context
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC156
                .events
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC157
                .send(crate::Event::RecentHistoryChanged {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC158
                    room_id: self.room.room_id().to_owned(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC159
                    state: RecentHistoryState::Loading,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC160
                })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC161
                .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC162
                .is_err()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC163
        {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC164
            for responder in responders {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC165
                responder.responder.done(Err(Error::interrupted()));
80381e06a573atagen2026-10-25 03:02 UTC166
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC167
            return;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC168
        }
80381e06a573atagen2026-10-25 03:02 UTC169
        let (cancel, receiver) = watch::channel(false);
80381e06a573atagen2026-10-25 03:02 UTC170
        let room = self.room.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC171
        let room_id = self.room.room_id().to_owned();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC172
        let task = spawn(async move { load_recent(room, room_id, policy, receiver).await });
80381e06a573atagen2026-10-25 03:02 UTC173
        self.running = Some(Running {
80381e06a573atagen2026-10-25 03:02 UTC174
            task,
80381e06a573atagen2026-10-25 03:02 UTC175
            cancel,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC176
            responders,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC177
            recent: true,
80381e06a573atagen2026-10-25 03:02 UTC178
        });
80381e06a573atagen2026-10-25 03:02 UTC179
    }
80381e06a573atagen2026-10-25 03:02 UTC180
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC181
    async fn start_gap_fill(&mut self, gap_id: ruma::OwnedTransactionId, responder: Responder) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC182
        if self.is_running() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC183
            responder.done(Err(room_busy()));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC184
            return;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC185
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC186
        let room = self.room.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC187
        let room_id = self.room.room_id().to_owned();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC188
        let mut progress = report(room_id.clone(), HistoryRepairPolicy::default());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC189
        progress.gap_id = Some(gap_id.clone());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC190
        progress.strategy = HistoryRepairStrategy::Bridge;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC191
        progress.phase = HistoryRepairPhase::Bridging;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC192
        if self.context.events.send(crate::Event::HistoryRepairChanged(progress)).await.is_err() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC193
            responder.done(Err(Error::interrupted()));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC194
            return;
80381e06a573atagen2026-10-25 03:02 UTC195
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC196
        let (cancel, receiver) = watch::channel(false);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC197
        let task = spawn(async move { fill_gap(room, room_id, gap_id, receiver).await });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC198
        self.running = Some(Running {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC199
            task,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC200
            cancel,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC201
            responders: vec![PendingResponder {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC202
                responder,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC203
                cancel_ack: false,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC204
            }],
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC205
            recent: false,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC206
        });
80381e06a573atagen2026-10-25 03:02 UTC207
    }
80381e06a573atagen2026-10-25 03:02 UTC208
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC209
    pub(super) async fn next_completion(&mut self) -> Option<Completion> {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC210
        let running = self.running.as_mut()?;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC211
        let result = (&mut running.task).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC212
        Some(self.finish_task(result))
80381e06a573atagen2026-10-25 03:02 UTC213
    }
80381e06a573atagen2026-10-25 03:02 UTC214
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC215
    pub(super) async fn shutdown(&mut self) -> Option<Completion> {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC216
        let running = self.running.as_mut()?;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC217
        running.cancel.send_replace(true);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC218
        let result = (&mut running.task).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC219
        Some(self.finish_task(result))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC220
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC221
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC222
    fn finish_task(&mut self, result: Result<CompletionResult, JoinError>) -> Completion {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC223
        let running = self.running.take().expect("repair task exists");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC224
        let result = match result {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC225
            Ok(result) => result,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC226
            Err(_) => CompletionResult {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC227
                report: failed_report(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC228
                    self.room.room_id().to_owned(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC229
                    HistoryRepairPolicy::default(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC230
                    Error::interrupted(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC231
                ),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC232
                recent_state: running.recent.then(|| {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC233
                    RecentHistoryState::Failed(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC234
                        "recent history operation was interrupted".to_owned(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC235
                    )
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC236
                }),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC237
                result: Err(Error::interrupted()),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC238
            },
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC239
        };
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC240
        Completion {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC241
            report: result.report,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC242
            recent_state: result.recent_state,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC243
            result: result.result,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC244
            responders: running.responders,
80381e06a573atagen2026-10-25 03:02 UTC245
        }
80381e06a573atagen2026-10-25 03:02 UTC246
    }
80381e06a573atagen2026-10-25 03:02 UTC247
}
80381e06a573atagen2026-10-25 03:02 UTC248
80381e06a573atagen2026-10-25 03:02 UTC249
fn validate_budget(budget: &HistoryRepairBudget) -> Result<(), Error> {
80381e06a573atagen2026-10-25 03:02 UTC250
    if !(1..=100).contains(&budget.max_pages)
80381e06a573atagen2026-10-25 03:02 UTC251
        || !(50..=10_000).contains(&budget.max_events)
80381e06a573atagen2026-10-25 03:02 UTC252
        || !(1..=60_000).contains(&budget.max_duration_ms)
80381e06a573atagen2026-10-25 03:02 UTC253
    {
80381e06a573atagen2026-10-25 03:02 UTC254
        return Err(Error::new(
80381e06a573atagen2026-10-25 03:02 UTC255
            ErrorKind::Invalid,
80381e06a573atagen2026-10-25 03:02 UTC256
            "repair budget requires 1 to 100 pages, 50 to 10000 events, and 1 to 60000 milliseconds",
80381e06a573atagen2026-10-25 03:02 UTC257
        ));
80381e06a573atagen2026-10-25 03:02 UTC258
    }
80381e06a573atagen2026-10-25 03:02 UTC259
    Ok(())
80381e06a573atagen2026-10-25 03:02 UTC260
}
80381e06a573atagen2026-10-25 03:02 UTC261
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC262
async fn load_recent(
80381e06a573atagen2026-10-25 03:02 UTC263
    room: Room,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC264
    room_id: ruma::OwnedRoomId,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC265
    policy: HistoryRepairPolicy,
80381e06a573atagen2026-10-25 03:02 UTC266
    mut cancel: watch::Receiver<bool>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC267
) -> CompletionResult {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC268
    let base = report(room_id.clone(), policy);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC269
    let outcome = tokio::select! {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC270
        biased;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC271
        _ = cancel.changed() => Err(Error::interrupted()),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC272
        result = async {
80381e06a573atagen2026-10-25 03:02 UTC273
            let (cache, _handles) = room.event_cache().await.map_err(cache_error)?;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC274
            cache.load_recent_history().await.map_err(cache_error)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC275
        } => result,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC276
    };
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC277
    match outcome {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC278
        Ok(outcome) => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC279
            let mut report = base;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC280
            report.pages = outcome.pages;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC281
            report.events = outcome.events.min(u32::MAX as usize) as u32;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC282
            report.recent_available = true;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC283
            report.phase = HistoryRepairPhase::Ready;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC284
            match outcome.gap.map(|gap| gap.status) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC285
                None => report.continuity = HistoryContinuity::AccessibleStart,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC286
                Some(matrix_sdk::event_cache::HistoryGapStatus::Joined) => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC287
                    report.continuity = HistoryContinuity::Verified
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC288
                }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC289
                Some(matrix_sdk::event_cache::HistoryGapStatus::Unavailable) => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC290
                    report.continuity = HistoryContinuity::AccessibleStart;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC291
                    report.stop = Some(HistoryRepairStop::HistoryUnavailable);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC292
                    report.phase = HistoryRepairPhase::Paused;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC293
                }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC294
                Some(matrix_sdk::event_cache::HistoryGapStatus::Paused) => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC295
                    report.stop = Some(HistoryRepairStop::CursorStalled);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC296
                    report.phase = HistoryRepairPhase::Paused;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC297
                }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC298
                Some(matrix_sdk::event_cache::HistoryGapStatus::Failed { message }) => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC299
                    let error = Error::new(ErrorKind::Network, message.clone());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC300
                    report.failure = Some(error.clone());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC301
                    report.phase = HistoryRepairPhase::Paused;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC302
                }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC303
                Some(matrix_sdk::event_cache::HistoryGapStatus::Idle) => {}
80381e06a573atagen2026-10-25 03:02 UTC304
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC305
            CompletionResult {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC306
                report: report.clone(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC307
                recent_state: Some(RecentHistoryState::Loaded),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC308
                result: Ok(CommandOutput::HistoryRepair(report)),
80381e06a573atagen2026-10-25 03:02 UTC309
            }
80381e06a573atagen2026-10-25 03:02 UTC310
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC311
        Err(error) => CompletionResult {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC312
            report: failed_report(room_id, base.requested_policy, error.clone()),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC313
            recent_state: Some(RecentHistoryState::Failed(error.message.clone())),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC314
            result: Err(error),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC315
        },
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC316
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC317
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC318
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC319
async fn fill_gap(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC320
    room: Room,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC321
    room_id: ruma::OwnedRoomId,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC322
    gap_id: ruma::OwnedTransactionId,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC323
    mut cancel: watch::Receiver<bool>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC324
) -> CompletionResult {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC325
    let mut report = report(room_id, HistoryRepairPolicy::default());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC326
    report.gap_id = Some(gap_id.clone());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC327
    report.strategy = HistoryRepairStrategy::Bridge;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC328
    let outcome = async {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC329
        let (cache, _handles) = room.event_cache().await.map_err(cache_error)?;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC330
        let mut last_gap = None;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC331
        for _ in 0..MAX_GAP_PAGES {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC332
            let next = tokio::select! {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC333
                biased;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC334
                _ = cancel.changed() => return Err(Error::interrupted()),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC335
                result = cache.fill_history_gap_once(gap_id.clone()) => result.map_err(cache_error)?,
80381e06a573atagen2026-10-25 03:02 UTC336
            };
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC337
            match next {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC338
                HistoryGapFillOutcome::NotFound => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC339
                    return Err(Error::new(ErrorKind::EventNotFound, "history gap was not found"));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC340
                }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC341
                HistoryGapFillOutcome::Busy => return Err(room_busy()),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC342
                HistoryGapFillOutcome::Updated { gap, events } => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC343
                    report.pages += 1;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC344
                    report.events = report.events.saturating_add(events.min(u32::MAX as usize) as u32);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC345
                    match gap.status.clone() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC346
                        matrix_sdk::event_cache::HistoryGapStatus::Joined => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC347
                            report.continuity = HistoryContinuity::Verified;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC348
                            last_gap = Some(gap);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC349
                            break;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC350
                        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC351
                        matrix_sdk::event_cache::HistoryGapStatus::Unavailable => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC352
                            report.continuity = HistoryContinuity::AccessibleStart;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC353
                            report.stop = Some(HistoryRepairStop::HistoryUnavailable);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC354
                            last_gap = Some(gap);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC355
                            break;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC356
                        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC357
                        matrix_sdk::event_cache::HistoryGapStatus::Paused => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC358
                            report.stop = Some(HistoryRepairStop::CursorStalled);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC359
                            last_gap = Some(gap);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC360
                            break;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC361
                        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC362
                        matrix_sdk::event_cache::HistoryGapStatus::Failed { message } => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC363
                            return Err(Error::new(ErrorKind::Network, message));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC364
                        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC365
                        matrix_sdk::event_cache::HistoryGapStatus::Idle => last_gap = Some(gap),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC366
                    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC367
                }
80381e06a573atagen2026-10-25 03:02 UTC368
            }
80381e06a573atagen2026-10-25 03:02 UTC369
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC370
        if report.pages == MAX_GAP_PAGES
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC371
            && report.continuity == HistoryContinuity::Unresolved
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC372
            && report.stop.is_none()
80381e06a573atagen2026-10-25 03:02 UTC373
        {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC374
            report.stop = Some(HistoryRepairStop::Budget);
80381e06a573atagen2026-10-25 03:02 UTC375
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC376
        Ok::<_, Error>(last_gap)
80381e06a573atagen2026-10-25 03:02 UTC377
    }
80381e06a573atagen2026-10-25 03:02 UTC378
    .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC379
    let command_result = match outcome {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC380
        Ok(_) => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC381
            report.recent_available = true;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC382
            report.phase = if report.stop.is_some() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC383
                HistoryRepairPhase::Paused
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC384
            } else {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC385
                HistoryRepairPhase::Ready
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC386
            };
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC387
            Ok(CommandOutput::Done)
80381e06a573atagen2026-10-25 03:02 UTC388
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC389
        Err(error) => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC390
            report.failure = Some(error.clone());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC391
            report.phase = HistoryRepairPhase::Paused;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC392
            Err(error)
80381e06a573atagen2026-10-25 03:02 UTC393
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC394
    };
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC395
    CompletionResult {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC396
        report,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC397
        recent_state: None,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC398
        result: command_result,
80381e06a573atagen2026-10-25 03:02 UTC399
    }
80381e06a573atagen2026-10-25 03:02 UTC400
}
80381e06a573atagen2026-10-25 03:02 UTC401
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC402
fn report(room_id: ruma::OwnedRoomId, policy: HistoryRepairPolicy) -> HistoryRepairReport {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC403
    HistoryRepairReport {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC404
        room_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC405
        gap_id: None,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC406
        requested_policy: policy,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC407
        strategy: HistoryRepairStrategy::Recent,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC408
        coverage_duration_ms: None,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC409
        threshold_evaluated: false,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC410
        pages: 0,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC411
        events: 0,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC412
        phase: HistoryRepairPhase::FetchingRecent,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC413
        continuity: HistoryContinuity::Unresolved,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC414
        recent_available: false,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC415
        stop: None,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC416
        failure: None,
80381e06a573atagen2026-10-25 03:02 UTC417
    }
80381e06a573atagen2026-10-25 03:02 UTC418
}
80381e06a573atagen2026-10-25 03:02 UTC419
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC420
fn failed_report(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC421
    room_id: ruma::OwnedRoomId,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC422
    policy: HistoryRepairPolicy,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC423
    error: Error,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC424
) -> HistoryRepairReport {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC425
    let mut report = report(room_id, policy);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC426
    report.phase = HistoryRepairPhase::Paused;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC427
    report.failure = Some(error);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC428
    report
80381e06a573atagen2026-10-25 03:02 UTC429
}
80381e06a573atagen2026-10-25 03:02 UTC430
80381e06a573atagen2026-10-25 03:02 UTC431
fn cache_error(source: matrix_sdk::event_cache::EventCacheError) -> Error {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC432
    Error::with_source(ErrorKind::Store, "room history operation failed", source)
80381e06a573atagen2026-10-25 03:02 UTC433
}
80381e06a573atagen2026-10-25 03:02 UTC434
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC435
fn room_busy() -> Error {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC436
    Error::new(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC437
        ErrorKind::RoomBusy,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC438
        "room history operation is already running",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC439
    )
80381e06a573atagen2026-10-25 03:02 UTC440
}