atagen

macltok

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