4 use crate::api_schema
::*;
5 use crate::api_schema
::router
::*;
6 //use crate::server::rest::*;
7 use serde_json
::{json, Value}
;
8 use std
::collections
::{HashSet, HashMap}
;
9 use chrono
::{DateTime, Datelike, Local}
;
10 use std
::path
::PathBuf
;
13 //use hyper::StatusCode;
14 //use hyper::rt::{Future, Stream};
16 use crate::config
::datastore
;
19 use crate::server
::WorkerTask
;
24 fn group_backups(backup_list
: Vec
<BackupInfo
>) -> HashMap
<String
, Vec
<BackupInfo
>> {
26 let mut group_hash
= HashMap
::new();
28 for info
in backup_list
{
29 let group_id
= info
.backup_dir
.group().group_path().to_str().unwrap().to_owned();
30 let time_list
= group_hash
.entry(group_id
).or_insert(vec
![]);
37 fn mark_selections
<F
: Fn(DateTime
<Local
>, &BackupInfo
) -> String
> (
38 mark
: &mut HashSet
<PathBuf
>,
39 list
: &Vec
<BackupInfo
>,
43 let mut hash
= HashSet
::new();
45 let local_time
= info
.backup_dir
.backup_time().with_timezone(&Local
);
46 if hash
.len() >= keep
as usize { break; }
47 let backup_id
= info
.backup_dir
.relative_path();
48 let sel_id
: String
= select_id(local_time
, &info
);
49 if !hash
.contains(&sel_id
) {
51 //println!(" KEEP ID {} {}", backup_id, local_time.format("%c"));
52 mark
.insert(backup_id
);
60 _rpcenv
: &mut RpcEnvironment
,
61 ) -> Result
<Value
, Error
> {
63 let store
= param
["store"].as_str().unwrap();
65 let datastore
= DataStore
::lookup_datastore(store
)?
;
67 let backup_list
= datastore
.list_backups()?
;
69 let group_hash
= group_backups(backup_list
);
71 let mut groups
= vec
![];
73 for (_group_id
, mut list
) in group_hash
{
75 BackupInfo
::sort_list(&mut list
, false);
78 let group
= info
.backup_dir
.group();
81 "backup-type": group
.backup_type(),
82 "backup-id": group
.backup_id(),
83 "last-backup": info
.backup_dir
.backup_time().timestamp(),
84 "backup-count": list
.len() as u64,
92 fn list_snapshot_files (
95 _rpcenv
: &mut RpcEnvironment
,
96 ) -> Result
<Value
, Error
> {
98 let store
= tools
::required_string_param(¶m
, "store")?
;
99 let backup_type
= tools
::required_string_param(¶m
, "backup-type")?
;
100 let backup_id
= tools
::required_string_param(¶m
, "backup-id")?
;
101 let backup_time
= tools
::required_integer_param(¶m
, "backup-time")?
;
103 let snapshot
= BackupDir
::new(backup_type
, backup_id
, backup_time
);
105 let datastore
= DataStore
::lookup_datastore(store
)?
;
107 let files
= datastore
.list_files(&snapshot
)?
;
112 fn delete_snapshots (
115 _rpcenv
: &mut RpcEnvironment
,
116 ) -> Result
<Value
, Error
> {
118 let store
= tools
::required_string_param(¶m
, "store")?
;
119 let backup_type
= tools
::required_string_param(¶m
, "backup-type")?
;
120 let backup_id
= tools
::required_string_param(¶m
, "backup-id")?
;
121 let backup_time
= tools
::required_integer_param(¶m
, "backup-time")?
;
123 let snapshot
= BackupDir
::new(backup_type
, backup_id
, backup_time
);
125 let datastore
= DataStore
::lookup_datastore(store
)?
;
127 datastore
.remove_backup_dir(&snapshot
)?
;
135 _rpcenv
: &mut RpcEnvironment
,
136 ) -> Result
<Value
, Error
> {
138 let store
= tools
::required_string_param(¶m
, "store")?
;
139 let backup_type
= tools
::required_string_param(¶m
, "backup-type")?
;
140 let backup_id
= tools
::required_string_param(¶m
, "backup-id")?
;
142 let group
= BackupGroup
::new(backup_type
, backup_id
);
144 let datastore
= DataStore
::lookup_datastore(store
)?
;
146 let backup_list
= datastore
.list_backups()?
;
148 let mut group_hash
= group_backups(backup_list
);
150 let group_id
= group
.group_path().to_str().unwrap().to_owned();
152 let group_snapshots
= match group_hash
.get_mut(&group_id
) {
155 BackupInfo
::sort_list(data
, false);
158 None
=> bail
!("Backup group '{}' does not exists.", group_id
),
161 let mut snapshots
= vec
![];
163 for info
in group_snapshots
{
165 let group
= info
.backup_dir
.group();
167 snapshots
.push(json
!({
168 "backup-type": group
.backup_type(),
169 "backup-id": group
.backup_id(),
170 "backup-time": info
.backup_dir
.backup_time().timestamp(),
181 _rpcenv
: &mut RpcEnvironment
,
182 ) -> Result
<Value
, Error
> {
184 let store
= param
["store"].as_str().unwrap();
186 let datastore
= DataStore
::lookup_datastore(store
)?
;
188 println
!("Starting prune on store {}", store
);
190 let backup_list
= datastore
.list_backups()?
;
192 let group_hash
= group_backups(backup_list
);
194 for (_group_id
, mut list
) in group_hash
{
196 let mut mark
= HashSet
::new();
198 BackupInfo
::sort_list(&mut list
, false);
200 if let Some(keep_last
) = param
["keep-last"].as_u64() {
201 list
.iter().take(keep_last
as usize).for_each(|info
| {
202 mark
.insert(info
.backup_dir
.relative_path());
206 if let Some(keep_daily
) = param
["keep-daily"].as_u64() {
207 mark_selections(&mut mark
, &list
, keep_daily
as usize, |local_time
, _info
| {
208 format
!("{}/{}/{}", local_time
.year(), local_time
.month(), local_time
.day())
212 if let Some(keep_weekly
) = param
["keep-weekly"].as_u64() {
213 mark_selections(&mut mark
, &list
, keep_weekly
as usize, |local_time
, _info
| {
214 format
!("{}/{}", local_time
.year(), local_time
.iso_week().week())
218 if let Some(keep_monthly
) = param
["keep-monthly"].as_u64() {
219 mark_selections(&mut mark
, &list
, keep_monthly
as usize, |local_time
, _info
| {
220 format
!("{}/{}", local_time
.year(), local_time
.month())
224 if let Some(keep_yearly
) = param
["keep-yearly"].as_u64() {
225 mark_selections(&mut mark
, &list
, keep_yearly
as usize, |local_time
, _info
| {
226 format
!("{}/{}", local_time
.year(), local_time
.year())
230 let mut remove_list
: Vec
<BackupInfo
> = list
.into_iter()
231 .filter(|info
| !mark
.contains(&info
.backup_dir
.relative_path())).collect();
233 BackupInfo
::sort_list(&mut remove_list
, true);
235 for info
in remove_list
{
236 datastore
.remove_backup_dir(&info
.backup_dir
)?
;
243 pub fn add_common_prune_prameters(schema
: ObjectSchema
) -> ObjectSchema
{
248 IntegerSchema
::new("Number of backups to keep.")
253 IntegerSchema
::new("Number of daily backups to keep.")
258 IntegerSchema
::new("Number of weekly backups to keep.")
263 IntegerSchema
::new("Number of monthly backups to keep.")
268 IntegerSchema
::new("Number of yearly backups to keep.")
273 fn api_method_prune() -> ApiMethod
{
276 add_common_prune_prameters(
277 ObjectSchema
::new("Prune the datastore.")
280 StringSchema
::new("Datastore name.")
286 fn start_garbage_collection(
289 rpcenv
: &mut RpcEnvironment
,
290 ) -> Result
<Value
, Error
> {
292 let store
= param
["store"].as_str().unwrap().to_string();
294 let datastore
= DataStore
::lookup_datastore(&store
)?
;
296 println
!("Starting garbage collection on store {}", store
);
298 let to_stdout
= if rpcenv
.env_type() == RpcEnvironmentType
::CLI { true }
else { false }
;
300 let upid_str
= WorkerTask
::new_thread(
301 "garbage_collection", Some(store
.clone()), "root@pam", to_stdout
, move |worker
|
303 worker
.log(format
!("starting garbage collection on store {}", store
));
304 datastore
.garbage_collection()
310 pub fn api_method_start_garbage_collection() -> ApiMethod
{
312 start_garbage_collection
,
313 ObjectSchema
::new("Start garbage collection.")
314 .required("store", StringSchema
::new("Datastore name."))
318 fn garbage_collection_status(
321 _rpcenv
: &mut RpcEnvironment
,
322 ) -> Result
<Value
, Error
> {
324 let store
= param
["store"].as_str().unwrap();
326 println
!("Garbage collection status on store {}", store
);
332 pub fn api_method_garbage_collection_status() -> ApiMethod
{
334 garbage_collection_status
,
335 ObjectSchema
::new("Garbage collection status.")
336 .required("store", StringSchema
::new("Datastore name."))
343 _rpcenv
: &mut RpcEnvironment
,
344 ) -> Result
<Value
, Error
> {
346 //let config = datastore::config()?;
348 let store
= param
["store"].as_str().unwrap();
350 let datastore
= DataStore
::lookup_datastore(store
)?
;
352 let mut list
= vec
![];
354 for info
in datastore
.list_backups()?
{
356 "backup-type": info
.backup_dir
.group().backup_type(),
357 "backup-id": info
.backup_dir
.group().backup_id(),
358 "backup-time": info
.backup_dir
.backup_time().timestamp(),
363 let result
= json
!(list
);
368 fn get_datastore_list(
371 _rpcenv
: &mut RpcEnvironment
,
372 ) -> Result
<Value
, Error
> {
374 let config
= datastore
::config()?
;
376 Ok(config
.convert_to_array("store"))
380 pub fn router() -> Router
{
382 let store_schema
: Arc
<Schema
> = Arc
::new(
383 StringSchema
::new("Datastore name.").into()
386 let datastore_info
= Router
::new()
389 {"subdir": "backups" }
,
392 {"subdir": "groups" }
,
393 {"subdir": "snapshots" }
,
394 {"subdir": "status" }
,
395 {"subdir": "prune" }
,
397 ObjectSchema
::new("Directory index.")
398 .required("store", store_schema
.clone()))
405 ObjectSchema
::new("List backups.")
406 .required("store", store_schema
.clone()))))
410 .download(pxar
::api_method_download_pxar())
411 .upload(pxar
::api_method_upload_pxar()))
415 .upgrade(upload
::api_method_upgrade_upload()))
419 .get(api_method_garbage_collection_status())
420 .post(api_method_start_garbage_collection()))
427 ObjectSchema
::new("List snapshot files.")
428 .required("store", store_schema
.clone())
429 .required("backup-type", StringSchema
::new("Backup type."))
430 .required("backup-id", StringSchema
::new("Backup ID."))
431 .required("backup-time", IntegerSchema
::new("Backup time (Unix epoch.)")
432 .minimum(1547797308))
441 ObjectSchema
::new("List backup groups.")
442 .required("store", store_schema
.clone()))))
449 ObjectSchema
::new("List backup groups.")
450 .required("store", store_schema
.clone())
451 .required("backup-type", StringSchema
::new("Backup type."))
452 .required("backup-id", StringSchema
::new("Backup ID."))
458 ObjectSchema
::new("Delete backup snapshot.")
459 .required("store", store_schema
.clone())
460 .required("backup-type", StringSchema
::new("Backup type."))
461 .required("backup-id", StringSchema
::new("Backup ID."))
462 .required("backup-time", IntegerSchema
::new("Backup time (Unix epoch.)")
463 .minimum(1547797308))
470 .post(api_method_prune()));
474 let route
= Router
::new()
477 ObjectSchema
::new("Directory index.")))
478 .match_all("store", datastore_info
);