feat(backup): 实例备份第1轮——独立备份库迁移、settings 仓库路径字段、备份进度事件
This commit is contained in:
1 parent
95a3e00b03
commit
e91ba2ae74
11 files changed
+298
-1
No files matched your search
@@ -0,0 +1,76 @@
|
|||||||
|
CREATE TABLE repository_meta (
|
||||||
|
id INTEGER PRIMARY KEY CHECK (id = 0),
|
||||||
|
schema_name TEXT NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO repository_meta (id, schema_name, created_at)
|
||||||
|
VALUES (0, 'axolotl-instance-backups', unixepoch('subsec') * 1000);
|
||||||
|
|
||||||
|
CREATE TABLE instance_backup_configs (
|
||||||
|
instance_id TEXT PRIMARY KEY,
|
||||||
|
instance_name TEXT NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
modified_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE backup_selections (
|
||||||
|
instance_id TEXT NOT NULL REFERENCES instance_backup_configs(instance_id) ON DELETE CASCADE,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (instance_id, path)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE backup_snapshots (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
instance_id TEXT NOT NULL REFERENCES instance_backup_configs(instance_id) ON DELETE CASCADE,
|
||||||
|
instance_name TEXT NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
file_count INTEGER NOT NULL,
|
||||||
|
symlink_count INTEGER NOT NULL,
|
||||||
|
logical_size INTEGER NOT NULL,
|
||||||
|
added_size INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX backup_snapshots_instance_created
|
||||||
|
ON backup_snapshots(instance_id, created_at DESC);
|
||||||
|
|
||||||
|
CREATE TABLE snapshot_roots (
|
||||||
|
snapshot_id TEXT NOT NULL REFERENCES backup_snapshots(id) ON DELETE CASCADE,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
existed INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (snapshot_id, path)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE backup_objects (
|
||||||
|
hash TEXT PRIMARY KEY,
|
||||||
|
size INTEGER NOT NULL,
|
||||||
|
ref_count INTEGER NOT NULL CHECK (ref_count >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE snapshot_entries (
|
||||||
|
snapshot_id TEXT NOT NULL REFERENCES backup_snapshots(id) ON DELETE CASCADE,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
kind TEXT NOT NULL CHECK (kind IN ('file', 'directory', 'symlink')),
|
||||||
|
object_hash TEXT REFERENCES backup_objects(hash),
|
||||||
|
size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
modified_at INTEGER,
|
||||||
|
unix_mode INTEGER,
|
||||||
|
link_target TEXT,
|
||||||
|
link_is_directory INTEGER,
|
||||||
|
PRIMARY KEY (snapshot_id, path),
|
||||||
|
CHECK ((kind = 'file' AND object_hash IS NOT NULL) OR (kind != 'file' AND object_hash IS NULL)),
|
||||||
|
CHECK ((kind = 'symlink' AND link_target IS NOT NULL) OR (kind != 'symlink' AND link_target IS NULL))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX snapshot_entries_object_hash
|
||||||
|
ON snapshot_entries(object_hash) WHERE object_hash IS NOT NULL;
|
||||||
|
|
||||||
|
CREATE TABLE object_gc_queue (
|
||||||
|
hash TEXT PRIMARY KEY,
|
||||||
|
queued_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE pending_instance_deletions (
|
||||||
|
instance_id TEXT PRIMARY KEY,
|
||||||
|
marked_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
ALTER TABLE backup_snapshots
|
||||||
|
ADD COLUMN scope_mode TEXT NOT NULL DEFAULT 'included_roots'
|
||||||
|
CHECK (scope_mode IN ('included_roots', 'excluded_paths'));
|
||||||
|
|
||||||
|
DROP TABLE backup_selections;
|
||||||
|
|
||||||
|
CREATE TABLE backup_exclusions (
|
||||||
|
instance_id TEXT NOT NULL REFERENCES instance_backup_configs(instance_id) ON DELETE CASCADE,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
kind TEXT NOT NULL CHECK (kind IN ('file', 'directory')),
|
||||||
|
PRIMARY KEY (instance_id, path)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE snapshot_exclusions (
|
||||||
|
snapshot_id TEXT NOT NULL REFERENCES backup_snapshots(id) ON DELETE CASCADE,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
kind TEXT NOT NULL CHECK (kind IN ('file', 'directory')),
|
||||||
|
PRIMARY KEY (snapshot_id, path)
|
||||||
|
);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
CREATE TABLE backup_operations (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
operation_type TEXT NOT NULL
|
||||||
|
CHECK (operation_type IN ('create', 'restore')),
|
||||||
|
instance_id TEXT NOT NULL,
|
||||||
|
snapshot_id TEXT,
|
||||||
|
state TEXT NOT NULL
|
||||||
|
CHECK (state IN (
|
||||||
|
'queued', 'scanning', 'hashing', 'saving', 'validating',
|
||||||
|
'materializing', 'applying', 'completed', 'cancelled', 'failed', 'interrupted'
|
||||||
|
)),
|
||||||
|
processed_bytes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
total_bytes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
cancellable INTEGER NOT NULL DEFAULT 1,
|
||||||
|
error TEXT,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL,
|
||||||
|
finished_at INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX backup_operations_instance_created
|
||||||
|
ON backup_operations(instance_id, created_at DESC);
|
||||||
|
|
||||||
|
CREATE INDEX backup_operations_active
|
||||||
|
ON backup_operations(state, updated_at DESC)
|
||||||
|
WHERE state NOT IN ('completed', 'cancelled', 'failed', 'interrupted');
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE backup_operations
|
||||||
|
ADD COLUMN cancel_requested INTEGER NOT NULL DEFAULT 0;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
PRAGMA foreign_keys = OFF;
|
||||||
|
|
||||||
|
CREATE TABLE backup_operations_new (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
operation_type TEXT NOT NULL
|
||||||
|
CHECK (operation_type IN ('create', 'restore', 'repository_move')),
|
||||||
|
instance_id TEXT NOT NULL,
|
||||||
|
snapshot_id TEXT,
|
||||||
|
state TEXT NOT NULL
|
||||||
|
CHECK (state IN (
|
||||||
|
'queued', 'scanning', 'hashing', 'saving', 'validating',
|
||||||
|
'materializing', 'applying', 'completed', 'cancelled', 'failed', 'interrupted'
|
||||||
|
)),
|
||||||
|
processed_bytes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
total_bytes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
cancellable INTEGER NOT NULL DEFAULT 1,
|
||||||
|
error TEXT,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL,
|
||||||
|
finished_at INTEGER,
|
||||||
|
cancel_requested INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO backup_operations_new (
|
||||||
|
id, operation_type, instance_id, snapshot_id, state,
|
||||||
|
processed_bytes, total_bytes, cancellable, error,
|
||||||
|
created_at, updated_at, finished_at, cancel_requested
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id, operation_type, instance_id, snapshot_id, state,
|
||||||
|
processed_bytes, total_bytes, cancellable, error,
|
||||||
|
created_at, updated_at, finished_at, cancel_requested
|
||||||
|
FROM backup_operations;
|
||||||
|
|
||||||
|
DROP TABLE backup_operations;
|
||||||
|
ALTER TABLE backup_operations_new RENAME TO backup_operations;
|
||||||
|
|
||||||
|
CREATE INDEX backup_operations_instance_created
|
||||||
|
ON backup_operations(instance_id, created_at DESC);
|
||||||
|
|
||||||
|
CREATE INDEX backup_operations_active
|
||||||
|
ON backup_operations(state, updated_at DESC)
|
||||||
|
WHERE state NOT IN ('completed', 'cancelled', 'failed', 'interrupted');
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
PRAGMA foreign_keys = OFF;
|
||||||
|
|
||||||
|
CREATE TABLE backup_operations_new (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
operation_type TEXT NOT NULL
|
||||||
|
CHECK (operation_type IN ('create', 'restore', 'repository_move', 'restore_preview')),
|
||||||
|
instance_id TEXT NOT NULL,
|
||||||
|
snapshot_id TEXT,
|
||||||
|
state TEXT NOT NULL
|
||||||
|
CHECK (state IN (
|
||||||
|
'queued', 'scanning', 'hashing', 'saving', 'validating',
|
||||||
|
'materializing', 'applying', 'completed', 'cancelled', 'failed', 'interrupted'
|
||||||
|
)),
|
||||||
|
processed_bytes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
total_bytes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
cancellable INTEGER NOT NULL DEFAULT 1,
|
||||||
|
error TEXT,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL,
|
||||||
|
finished_at INTEGER,
|
||||||
|
cancel_requested INTEGER NOT NULL DEFAULT 0,
|
||||||
|
preview_added_files INTEGER,
|
||||||
|
preview_modified_files INTEGER,
|
||||||
|
preview_deleted_files INTEGER,
|
||||||
|
preview_plan_token TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO backup_operations_new (
|
||||||
|
id, operation_type, instance_id, snapshot_id, state,
|
||||||
|
processed_bytes, total_bytes, cancellable, error,
|
||||||
|
created_at, updated_at, finished_at, cancel_requested
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id, operation_type, instance_id, snapshot_id, state,
|
||||||
|
processed_bytes, total_bytes, cancellable, error,
|
||||||
|
created_at, updated_at, finished_at, cancel_requested
|
||||||
|
FROM backup_operations;
|
||||||
|
|
||||||
|
DROP TABLE backup_operations;
|
||||||
|
ALTER TABLE backup_operations_new RENAME TO backup_operations;
|
||||||
|
|
||||||
|
CREATE INDEX backup_operations_instance_created
|
||||||
|
ON backup_operations(instance_id, created_at DESC);
|
||||||
|
|
||||||
|
CREATE INDEX backup_operations_active
|
||||||
|
ON backup_operations(state, updated_at DESC)
|
||||||
|
WHERE state NOT IN ('completed', 'cancelled', 'failed', 'interrupted');
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE settings ADD COLUMN backup_repository_path TEXT;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
CREATE TABLE pending_backup_repository_cleanups (
|
||||||
|
path TEXT PRIMARY KEY,
|
||||||
|
created_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use super::{FriendPayload, LoadingBarId};
|
use super::{FriendPayload, LoadingBarId};
|
||||||
use crate::event::{
|
use crate::event::{
|
||||||
CommandPayload, EventError, InstanceBulkUpdateProgressPayload,
|
CommandPayload, EventError, InstanceBackupProgressPayload,
|
||||||
|
InstanceBulkUpdateProgressPayload,
|
||||||
InstancePayloadType, LoadingBar, LoadingBarType, ProcessPayloadType,
|
InstancePayloadType, LoadingBar, LoadingBarType, ProcessPayloadType,
|
||||||
ServerPayloadType,
|
ServerPayloadType,
|
||||||
};
|
};
|
||||||
@@ -358,6 +359,21 @@ pub async fn emit_instance_bulk_update_progress(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub async fn emit_instance_backup_progress(
|
||||||
|
payload: InstanceBackupProgressPayload,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
{
|
||||||
|
let event_state = crate::EventState::get()?;
|
||||||
|
event_state
|
||||||
|
.app
|
||||||
|
.emit("instance_backup_progress", payload)
|
||||||
|
.map_err(EventError::from)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// emit_command(CommandPayload::Something { something })
|
// emit_command(CommandPayload::Something { something })
|
||||||
// ie: installing a pack, opening an .mrpack, etc
|
// ie: installing a pack, opening an .mrpack, etc
|
||||||
// Generally used for url deep links and file opens that we want to handle in the frontend
|
// Generally used for url deep links and file opens that we want to handle in the frontend
|
||||||
|
|||||||
@@ -248,6 +248,57 @@ pub enum InstanceBulkUpdateProgressStage {
|
|||||||
Finishing,
|
Finishing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct InstanceBackupProgressPayload {
|
||||||
|
pub operation_id: Uuid,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub instance_id: Option<String>,
|
||||||
|
pub operation_type: InstanceBackupOperationType,
|
||||||
|
pub stage: InstanceBackupProgressStage,
|
||||||
|
pub processed_bytes: u64,
|
||||||
|
pub total_bytes: u64,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub final_state: Option<InstanceBackupOperationFinalState>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub snapshot_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone, Copy)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum InstanceBackupOperationType {
|
||||||
|
Create,
|
||||||
|
Restore,
|
||||||
|
Delete,
|
||||||
|
RepositoryMove,
|
||||||
|
RestorePreview,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone, Copy)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum InstanceBackupOperationFinalState {
|
||||||
|
Completed,
|
||||||
|
Cancelled,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum InstanceBackupProgressStage {
|
||||||
|
Scanning,
|
||||||
|
Hashing,
|
||||||
|
Saving,
|
||||||
|
Validating,
|
||||||
|
Copying,
|
||||||
|
Restoring,
|
||||||
|
Deleting,
|
||||||
|
Completed,
|
||||||
|
Cancelled,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
#[serde(tag = "event")]
|
#[serde(tag = "event")]
|
||||||
pub enum CommandPayload {
|
pub enum CommandPayload {
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ pub struct Settings {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ui_font: Option<String>,
|
pub ui_font: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub backup_repository_path: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
pub mono_font: Option<String>,
|
pub mono_font: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub close_behavior: String,
|
pub close_behavior: String,
|
||||||
@@ -294,6 +296,7 @@ impl Settings {
|
|||||||
json(terracotta_public_nodes) terracotta_public_nodes,
|
json(terracotta_public_nodes) terracotta_public_nodes,
|
||||||
ui_scale,
|
ui_scale,
|
||||||
ui_font,
|
ui_font,
|
||||||
|
backup_repository_path,
|
||||||
mono_font,
|
mono_font,
|
||||||
version
|
version
|
||||||
FROM settings
|
FROM settings
|
||||||
@@ -369,6 +372,7 @@ impl Settings {
|
|||||||
sidebar_instance_count: res.sidebar_instance_count as u32,
|
sidebar_instance_count: res.sidebar_instance_count as u32,
|
||||||
ui_scale: res.ui_scale,
|
ui_scale: res.ui_scale,
|
||||||
ui_font: res.ui_font,
|
ui_font: res.ui_font,
|
||||||
|
backup_repository_path: res.backup_repository_path,
|
||||||
mono_font: res.mono_font,
|
mono_font: res.mono_font,
|
||||||
close_behavior,
|
close_behavior,
|
||||||
auto_hide_downloads_button: res.auto_hide_downloads_button == 1,
|
auto_hide_downloads_button: res.auto_hide_downloads_button == 1,
|
||||||
@@ -677,6 +681,10 @@ impl Settings {
|
|||||||
.bind(&self.ui_font)
|
.bind(&self.ui_font)
|
||||||
.execute(exec)
|
.execute(exec)
|
||||||
.await?;
|
.await?;
|
||||||
|
sqlx::query("UPDATE settings SET backup_repository_path = ? WHERE id = 0")
|
||||||
|
.bind(&self.backup_repository_path)
|
||||||
|
.execute(exec)
|
||||||
|
.await?;
|
||||||
sqlx::query("UPDATE settings SET mono_font = ? WHERE id = 0")
|
sqlx::query("UPDATE settings SET mono_font = ? WHERE id = 0")
|
||||||
.bind(&self.mono_font)
|
.bind(&self.mono_font)
|
||||||
.execute(exec)
|
.execute(exec)
|
||||||
|
|||||||
Reference in new issue
Block a user