yggdrasil/pages/admin/system/backup/
task.rs1use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::Arc;
4
5use crate::api::database::backup::{
6 create_backup, delete_backup, list_backups, restore_backup, BackupInfo,
7};
8use crate::api::database::tasks::{get_task_progress, TaskProgress, TaskStatus};
9use crate::utils::error::failure_message;
10use leptos::prelude::*;
11
12#[derive(Clone, PartialEq)]
14enum Operation {
15 Idle,
16 Starting,
17 Running(String),
18 Deleting,
19}
20
21#[derive(Clone, Copy)]
23pub(super) struct BackupState {
24 pub backups: RwSignal<Vec<BackupInfo>>,
25 pub loading: RwSignal<bool>,
26 pub importing: RwSignal<bool>,
27 pub error: RwSignal<Option<String>>,
28 pub progress: RwSignal<Option<TaskProgress>>,
29 operation: RwSignal<Operation>,
30 list_generation: StoredValue<u64>,
31}
32
33impl BackupState {
34 pub fn busy(self) -> bool {
36 self.operation.with(|op| *op != Operation::Idle) || self.importing.get()
37 }
38
39 pub fn busy_untracked(self) -> bool {
41 self.operation.with_untracked(|op| *op != Operation::Idle) || self.importing.get_untracked()
42 }
43
44 pub fn refresh(self) {
46 let _ = self.error.try_set(None);
47 let Some(generation) = self.list_generation.try_update_value(|g| {
48 *g += 1;
49 *g
50 }) else {
51 return;
52 };
53 let _ = self.loading.try_set(true);
54 leptos::task::spawn_local(async move {
55 let result = list_backups().await;
56 if self.list_generation.try_get_value() != Some(generation) {
57 return;
58 }
59 let _ = self.loading.try_set(false);
60 match result {
61 Ok(list) => {
62 let _ = self.backups.try_set(list);
63 }
64 Err(error) => {
65 let _ = self
66 .error
67 .try_set(Some(failure_message("读取备份列表失败", &error)));
68 }
69 }
70 });
71 }
72
73 pub fn start(self, restore: Option<String>) {
75 if self.busy_untracked() {
76 return;
77 }
78 self.operation.set(Operation::Starting);
79 self.progress.set(None);
80 self.error.set(None);
81 leptos::task::spawn_local(async move {
82 let restoring = restore.is_some();
83 let result = match restore {
84 Some(filename) => restore_backup(filename, true).await,
85 None => create_backup().await,
86 };
87 match result {
88 Ok(id) => {
89 let _ = self.operation.try_set(Operation::Running(id));
90 }
91 Err(error) => {
92 let action = if restoring {
93 "恢复失败"
94 } else {
95 "备份失败"
96 };
97 let _ = self.error.try_set(Some(failure_message(action, &error)));
98 let _ = self.operation.try_set(Operation::Idle);
99 }
100 }
101 });
102 }
103
104 pub fn delete(self, filename: String) {
106 if self.busy_untracked() {
107 return;
108 }
109 self.operation.set(Operation::Deleting);
110 self.error.set(None);
111 leptos::task::spawn_local(async move {
112 match delete_backup(filename).await {
113 Ok(()) => self.refresh(),
114 Err(error) => {
115 let _ = self
116 .error
117 .try_set(Some(failure_message("删除失败", &error)));
118 }
119 }
120 let _ = self.operation.try_set(Operation::Idle);
121 });
122 }
123}
124
125pub(super) fn use_backup_task() -> BackupState {
127 let state = BackupState {
128 backups: RwSignal::new(Vec::new()),
129 loading: RwSignal::new(false),
130 importing: RwSignal::new(false),
131 error: RwSignal::new(None),
132 progress: RwSignal::new(None),
133 operation: RwSignal::new(Operation::Idle),
134 list_generation: StoredValue::new(0),
135 };
136 Effect::new(move |_| state.refresh());
138 Effect::new(move |_| {
140 let Operation::Running(id) = state.operation.get() else {
141 return;
142 };
143 let alive = Arc::new(AtomicBool::new(true));
144 let stop = alive.clone();
145 on_cleanup(move || stop.store(false, Ordering::Relaxed));
146 leptos::task::spawn_local(async move {
147 loop {
148 crate::utils::time::sleep_ms(1500).await;
149 if !alive.load(Ordering::Relaxed) {
150 return;
151 }
152 let result = get_task_progress(id.clone()).await;
153 if !alive.load(Ordering::Relaxed) {
154 return;
155 }
156 match result {
157 Ok(progress) => {
158 let done = progress.status != TaskStatus::Running;
159 let _ = state.progress.try_set(Some(progress));
160 if done {
161 state.refresh();
162 let _ = state.operation.try_set(Operation::Idle);
163 return;
164 }
165 }
166 Err(error) => {
167 let _ = state
168 .error
169 .try_set(Some(failure_message("获取备份进度失败", &error)));
170 let _ = state.operation.try_set(Operation::Idle);
171 return;
172 }
173 }
174 }
175 });
176 });
177 state
178}