feat(maintenance): add a maintenance notification during backend migration
This commit is contained in:
@@ -2028,6 +2028,7 @@ impl AppServiceFactory {
|
||||
crate::infrastructure::services::webdav_dead_property_store::create_dead_property_store(pool.clone()),
|
||||
authorization: authorization.clone(),
|
||||
migration_readonly: migration_readonly.clone(),
|
||||
migration_progress: Arc::new(std::sync::RwLock::new(None)),
|
||||
drive_repo: drive_repo.clone(),
|
||||
drive_management_service: Arc::new(
|
||||
crate::application::services::drive_management_service::DriveManagementService::new(
|
||||
@@ -2254,6 +2255,7 @@ impl AppServiceFactory {
|
||||
self.storage_path.clone(),
|
||||
app_state.migration_readonly.clone(),
|
||||
app_state.core.blob_backend_hot_swap.clone(),
|
||||
app_state.migration_progress.clone(),
|
||||
),
|
||||
)
|
||||
.register_recoverable_job(&app_state.core.job_registry, &job_store_provider_dyn)
|
||||
@@ -2785,6 +2787,14 @@ pub struct AppState {
|
||||
/// memory in sync. See `docs/plan/storage-multi-entry.md`
|
||||
/// §"Read-only mode".
|
||||
pub migration_readonly: Arc<std::sync::atomic::AtomicBool>,
|
||||
/// Live progress snapshot for the storage-migration handler.
|
||||
/// `Some(_)` while a migration is running; `None` otherwise.
|
||||
/// Updated by the handler on every batch checkpoint (cheap
|
||||
/// in-memory write, no DB read on the request path). The
|
||||
/// server-status header middleware reads it to inform every
|
||||
/// user's session banner about maintenance progress without
|
||||
/// polling. See `MigrationProgress` for the field shape.
|
||||
pub migration_progress: Arc<std::sync::RwLock<Option<crate::common::migration_progress::MigrationProgress>>>,
|
||||
/// Drive entity repository — `GET /api/drives`, the personal-drive
|
||||
/// lifecycle hook, and (post-D2) shared-drive creation flow all read
|
||||
/// through this. Backing table is `storage.drives`; membership is
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//! Shared in-memory snapshot of the running storage-migration.
|
||||
//!
|
||||
//! Consumed by the server-status middleware to build the
|
||||
//! `X-Server-Status` response header on every authenticated
|
||||
//! request. That header lets every logged-in user's session banner
|
||||
//! show current maintenance progress without any polling —
|
||||
//! the state travels back on the piggyback of whatever API call
|
||||
//! the user was going to make anyway.
|
||||
//!
|
||||
//! Written by the migration handler on each batch checkpoint (a
|
||||
//! cheap `RwLock::write` + a small struct copy — no DB access on
|
||||
//! the request path). Cleared on `RunOutcome::Completed` /
|
||||
//! `Paused` / `Failed`. `None` means "no migration is running";
|
||||
//! middleware omits the header entirely in that case.
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// One snapshot of a running migration. Every field is a scalar so
|
||||
/// the whole struct copies cheaply under the `RwLock::write` guard.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct MigrationProgress {
|
||||
/// The entry name blobs are being copied INTO. Used by the
|
||||
/// user-facing banner text so admins/users know what the
|
||||
/// server is switching to.
|
||||
pub target_name: String,
|
||||
/// Blobs migrated so far this run. Starts at 0 on a Fresh
|
||||
/// open; on Resume the checkpointed value is loaded from the
|
||||
/// run row's `stats.scanned_count`.
|
||||
pub migrated_blobs: u64,
|
||||
/// Total blobs in the current DB snapshot. Captured once at
|
||||
/// run start via `SELECT COUNT(*) FROM storage.blobs`. Doesn't
|
||||
/// change during the run — new uploads are refused while
|
||||
/// read-only is engaged, so the denominator stays honest.
|
||||
pub total_blobs: u64,
|
||||
/// Convenience: `migrated_blobs * 100 / total_blobs`, clamped
|
||||
/// to 0..=100. Middleware could compute it but it's tiny and
|
||||
/// makes the JSON payload obvious.
|
||||
pub percent: u8,
|
||||
}
|
||||
|
||||
impl MigrationProgress {
|
||||
pub fn new(target_name: String, total_blobs: u64) -> Self {
|
||||
Self {
|
||||
target_name,
|
||||
migrated_blobs: 0,
|
||||
total_blobs,
|
||||
percent: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the counter + recompute `percent`. Called by the
|
||||
/// migration handler after each batch checkpoint.
|
||||
pub fn bump(&mut self, migrated_delta: u64) {
|
||||
self.migrated_blobs = self.migrated_blobs.saturating_add(migrated_delta);
|
||||
self.recompute_percent();
|
||||
}
|
||||
|
||||
fn recompute_percent(&mut self) {
|
||||
self.percent = if self.total_blobs == 0 {
|
||||
0
|
||||
} else {
|
||||
((self.migrated_blobs.min(self.total_blobs) as u128 * 100) / self.total_blobs as u128)
|
||||
as u8
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub mod di;
|
||||
pub mod errors;
|
||||
pub mod fmt;
|
||||
pub mod locale;
|
||||
pub mod migration_progress;
|
||||
pub mod mime_detect;
|
||||
pub mod runtime;
|
||||
pub mod stubs;
|
||||
|
||||
Reference in New Issue
Block a user