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
;
23 fn group_backups(backup_list
: Vec
<BackupInfo
>) -> HashMap
<String
, Vec
<BackupInfo
>> {
25 let mut group_hash
= HashMap
::new();
27 for info
in backup_list
{
28 let group_id
= info
.backup_dir
.group().group_path().to_str().unwrap().to_owned();
29 let time_list
= group_hash
.entry(group_id
).or_insert(vec
![]);
36 fn mark_selections
<F
: Fn(DateTime
<Local
>, &BackupInfo
) -> String
> (
37 mark
: &mut HashSet
<PathBuf
>,
38 list
: &Vec
<BackupInfo
>,
42 let mut hash
= HashSet
::new();
44 let local_time
= info
.backup_dir
.backup_time().with_timezone(&Local
);
45 if hash
.len() >= keep
as usize { break; }
46 let backup_id
= info
.backup_dir
.relative_path();
47 let sel_id
: String
= select_id(local_time
, &info
);
48 if !hash
.contains(&sel_id
) {
50 //println!(" KEEP ID {} {}", backup_id, local_time.format("%c"));
51 mark
.insert(backup_id
);
59 _rpcenv
: &mut RpcEnvironment
,
60 ) -> Result
<Value
, Error
> {
62 let store
= param
["store"].as_str().unwrap();
64 let datastore
= DataStore
::lookup_datastore(store
)?
;
66 let backup_list
= datastore
.list_backups()?
;
68 let group_hash
= group_backups(backup_list
);
70 let mut groups
= vec
![];
72 for (_group_id
, mut list
) in group_hash
{
74 BackupInfo
::sort_list(&mut list
, false);
77 let group
= info
.backup_dir
.group();
80 "backup-type": group
.backup_type(),
81 "backup-id": group
.backup_id(),
82 "last-backup": info
.backup_dir
.backup_time().timestamp(),
83 "backup-count": list
.len() as u64,
91 fn list_snapshot_files (
94 _rpcenv
: &mut RpcEnvironment
,
95 ) -> Result
<Value
, Error
> {
97 let store
= tools
::required_string_param(¶m
, "store")?
;
98 let backup_type
= tools
::required_string_param(¶m
, "backup-type")?
;
99 let backup_id
= tools
::required_string_param(¶m
, "backup-id")?
;
100 let backup_time
= tools
::required_integer_param(¶m
, "backup-time")?
;
102 let snapshot
= BackupDir
::new(backup_type
, backup_id
, backup_time
);
104 let datastore
= DataStore
::lookup_datastore(store
)?
;
106 let files
= datastore
.list_files(&snapshot
)?
;
111 fn delete_snapshots (
114 _rpcenv
: &mut RpcEnvironment
,
115 ) -> Result
<Value
, Error
> {
117 let store
= tools
::required_string_param(¶m
, "store")?
;
118 let backup_type
= tools
::required_string_param(¶m
, "backup-type")?
;
119 let backup_id
= tools
::required_string_param(¶m
, "backup-id")?
;
120 let backup_time
= tools
::required_integer_param(¶m
, "backup-time")?
;
122 let snapshot
= BackupDir
::new(backup_type
, backup_id
, backup_time
);
124 let datastore
= DataStore
::lookup_datastore(store
)?
;
126 datastore
.remove_backup_dir(&snapshot
)?
;
134 _rpcenv
: &mut RpcEnvironment
,
135 ) -> Result
<Value
, Error
> {
137 let store
= tools
::required_string_param(¶m
, "store")?
;
138 let backup_type
= tools
::required_string_param(¶m
, "backup-type")?
;
139 let backup_id
= tools
::required_string_param(¶m
, "backup-id")?
;
141 let group
= BackupGroup
::new(backup_type
, backup_id
);
143 let datastore
= DataStore
::lookup_datastore(store
)?
;
145 let backup_list
= datastore
.list_backups()?
;
147 let mut group_hash
= group_backups(backup_list
);
149 let group_id
= group
.group_path().to_str().unwrap().to_owned();
151 let group_snapshots
= match group_hash
.get_mut(&group_id
) {
154 BackupInfo
::sort_list(data
, false);
157 None
=> bail
!("Backup group '{}' does not exists.", group_id
),
160 let mut snapshots
= vec
![];
162 for info
in group_snapshots
{
164 let group
= info
.backup_dir
.group();
166 snapshots
.push(json
!({
167 "backup-type": group
.backup_type(),
168 "backup-id": group
.backup_id(),
169 "backup-time": info
.backup_dir
.backup_time().timestamp(),
180 _rpcenv
: &mut RpcEnvironment
,
181 ) -> Result
<Value
, Error
> {
183 let store
= param
["store"].as_str().unwrap();
185 let datastore
= DataStore
::lookup_datastore(store
)?
;
187 println
!("Starting prune on store {}", store
);
189 let backup_list
= datastore
.list_backups()?
;
191 let group_hash
= group_backups(backup_list
);
193 for (_group_id
, mut list
) in group_hash
{
195 let mut mark
= HashSet
::new();
197 BackupInfo
::sort_list(&mut list
, false);
199 if let Some(keep_last
) = param
["keep-last"].as_u64() {
200 list
.iter().take(keep_last
as usize).for_each(|info
| {
201 mark
.insert(info
.backup_dir
.relative_path());
205 if let Some(keep_daily
) = param
["keep-daily"].as_u64() {
206 mark_selections(&mut mark
, &list
, keep_daily
as usize, |local_time
, _info
| {
207 format
!("{}/{}/{}", local_time
.year(), local_time
.month(), local_time
.day())
211 if let Some(keep_weekly
) = param
["keep-weekly"].as_u64() {
212 mark_selections(&mut mark
, &list
, keep_weekly
as usize, |local_time
, _info
| {
213 format
!("{}/{}", local_time
.year(), local_time
.iso_week().week())
217 if let Some(keep_monthly
) = param
["keep-monthly"].as_u64() {
218 mark_selections(&mut mark
, &list
, keep_monthly
as usize, |local_time
, _info
| {
219 format
!("{}/{}", local_time
.year(), local_time
.month())
223 if let Some(keep_yearly
) = param
["keep-yearly"].as_u64() {
224 mark_selections(&mut mark
, &list
, keep_yearly
as usize, |local_time
, _info
| {
225 format
!("{}/{}", local_time
.year(), local_time
.year())
229 let mut remove_list
: Vec
<BackupInfo
> = list
.into_iter()
230 .filter(|info
| !mark
.contains(&info
.backup_dir
.relative_path())).collect();
232 BackupInfo
::sort_list(&mut remove_list
, true);
234 for info
in remove_list
{
235 datastore
.remove_backup_dir(&info
.backup_dir
)?
;
242 pub fn add_common_prune_prameters(schema
: ObjectSchema
) -> ObjectSchema
{
247 IntegerSchema
::new("Number of backups to keep.")
252 IntegerSchema
::new("Number of daily backups to keep.")
257 IntegerSchema
::new("Number of weekly backups to keep.")
262 IntegerSchema
::new("Number of monthly backups to keep.")
267 IntegerSchema
::new("Number of yearly backups to keep.")
272 fn api_method_prune() -> ApiMethod
{
275 add_common_prune_prameters(
276 ObjectSchema
::new("Prune the datastore.")
279 StringSchema
::new("Datastore name.")
285 // this is just a test for mutability/mutex handling - will remove later
286 fn start_garbage_collection(
289 _rpcenv
: &mut RpcEnvironment
,
290 ) -> Result
<Value
, Error
> {
292 let store
= param
["store"].as_str().unwrap();
294 let datastore
= DataStore
::lookup_datastore(store
)?
;
296 println
!("Starting garbage collection on store {}", store
);
298 datastore
.garbage_collection()?
;
303 pub fn api_method_start_garbage_collection() -> ApiMethod
{
305 start_garbage_collection
,
306 ObjectSchema
::new("Start garbage collection.")
307 .required("store", StringSchema
::new("Datastore name."))
311 fn garbage_collection_status(
314 _rpcenv
: &mut RpcEnvironment
,
315 ) -> Result
<Value
, Error
> {
317 let store
= param
["store"].as_str().unwrap();
319 println
!("Garbage collection status on store {}", store
);
325 pub fn api_method_garbage_collection_status() -> ApiMethod
{
327 garbage_collection_status
,
328 ObjectSchema
::new("Garbage collection status.")
329 .required("store", StringSchema
::new("Datastore name."))
336 _rpcenv
: &mut RpcEnvironment
,
337 ) -> Result
<Value
, Error
> {
339 //let config = datastore::config()?;
341 let store
= param
["store"].as_str().unwrap();
343 let datastore
= DataStore
::lookup_datastore(store
)?
;
345 let mut list
= vec
![];
347 for info
in datastore
.list_backups()?
{
349 "backup-type": info
.backup_dir
.group().backup_type(),
350 "backup-id": info
.backup_dir
.group().backup_id(),
351 "backup-time": info
.backup_dir
.backup_time().timestamp(),
356 let result
= json
!(list
);
361 fn get_datastore_list(
364 _rpcenv
: &mut RpcEnvironment
,
365 ) -> Result
<Value
, Error
> {
367 let config
= datastore
::config()?
;
369 Ok(config
.convert_to_array("store"))
373 pub fn router() -> Router
{
375 let store_schema
: Arc
<Schema
> = Arc
::new(
376 StringSchema
::new("Datastore name.").into()
379 let datastore_info
= Router
::new()
382 {"subdir": "backups" }
,
383 {"subdir": "catar" }
,
385 {"subdir": "groups" }
,
386 {"subdir": "snapshots" }
,
387 {"subdir": "status" }
,
388 {"subdir": "prune" }
,
390 ObjectSchema
::new("Directory index.")
391 .required("store", store_schema
.clone()))
398 ObjectSchema
::new("List backups.")
399 .required("store", store_schema
.clone()))))
403 .download(catar
::api_method_download_catar())
404 .upload(catar
::api_method_upload_catar()))
408 .upgrade(upload
::api_method_upgrade_upload()))
412 .get(api_method_garbage_collection_status())
413 .post(api_method_start_garbage_collection()))
420 ObjectSchema
::new("List snapshot files.")
421 .required("store", store_schema
.clone())
422 .required("backup-type", StringSchema
::new("Backup type."))
423 .required("backup-id", StringSchema
::new("Backup ID."))
424 .required("backup-time", IntegerSchema
::new("Backup time (Unix epoch.)")
425 .minimum(1547797308))
434 ObjectSchema
::new("List backup groups.")
435 .required("store", store_schema
.clone()))))
442 ObjectSchema
::new("List backup groups.")
443 .required("store", store_schema
.clone())
444 .required("backup-type", StringSchema
::new("Backup type."))
445 .required("backup-id", StringSchema
::new("Backup ID."))
451 ObjectSchema
::new("Delete backup snapshot.")
452 .required("store", store_schema
.clone())
453 .required("backup-type", StringSchema
::new("Backup type."))
454 .required("backup-id", StringSchema
::new("Backup ID."))
455 .required("backup-time", IntegerSchema
::new("Backup time (Unix epoch.)")
456 .minimum(1547797308))
463 .post(api_method_prune()));
467 let route
= Router
::new()
470 ObjectSchema
::new("Directory index.")))
471 .match_all("store", datastore_info
);