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
;
22 fn group_backups(backup_list
: Vec
<BackupInfo
>) -> HashMap
<String
, Vec
<BackupInfo
>> {
24 let mut group_hash
= HashMap
::new();
26 for info
in backup_list
{
27 let group_id
= info
.backup_dir
.group().group_path().to_str().unwrap().to_owned();
28 let time_list
= group_hash
.entry(group_id
).or_insert(vec
![]);
35 fn mark_selections
<F
: Fn(DateTime
<Local
>, &BackupInfo
) -> String
> (
36 mark
: &mut HashSet
<PathBuf
>,
37 list
: &Vec
<BackupInfo
>,
41 let mut hash
= HashSet
::new();
43 let local_time
= info
.backup_dir
.backup_time().with_timezone(&Local
);
44 if hash
.len() >= keep
as usize { break; }
45 let backup_id
= info
.backup_dir
.relative_path();
46 let sel_id
: String
= select_id(local_time
, &info
);
47 if !hash
.contains(&sel_id
) {
49 //println!(" KEEP ID {} {}", backup_id, local_time.format("%c"));
50 mark
.insert(backup_id
);
58 _rpcenv
: &mut RpcEnvironment
,
59 ) -> Result
<Value
, Error
> {
61 let store
= param
["store"].as_str().unwrap();
63 let datastore
= DataStore
::lookup_datastore(store
)?
;
65 let backup_list
= datastore
.list_backups()?
;
67 let group_hash
= group_backups(backup_list
);
69 let mut groups
= vec
![];
71 for (_group_id
, mut list
) in group_hash
{
73 BackupInfo
::sort_list(&mut list
, false);
76 let group
= info
.backup_dir
.group();
79 "backup-type": group
.backup_type(),
80 "backup-id": group
.backup_id(),
81 "last-backup": info
.backup_dir
.backup_time().timestamp(),
82 "backup-count": list
.len() as u64,
93 _rpcenv
: &mut RpcEnvironment
,
94 ) -> Result
<Value
, Error
> {
96 let store
= tools
::required_string_param(¶m
, "store")?
;
97 let backup_type
= tools
::required_string_param(¶m
, "backup-type")?
;
98 let backup_id
= tools
::required_string_param(¶m
, "backup-id")?
;
99 let backup_time
= tools
::required_integer_param(¶m
, "backup-time")?
;
101 let snapshot
= BackupDir
::new(BackupGroup
::new(backup_type
, backup_id
), backup_time
);
103 let datastore
= DataStore
::lookup_datastore(store
)?
;
105 datastore
.remove_backup_dir(&snapshot
)?
;
113 _rpcenv
: &mut RpcEnvironment
,
114 ) -> Result
<Value
, Error
> {
116 let store
= tools
::required_string_param(¶m
, "store")?
;
117 let backup_type
= tools
::required_string_param(¶m
, "backup-type")?
;
118 let backup_id
= tools
::required_string_param(¶m
, "backup-id")?
;
120 let group
= BackupGroup
::new(backup_type
, backup_id
);
122 let datastore
= DataStore
::lookup_datastore(store
)?
;
124 let backup_list
= datastore
.list_backups()?
;
126 let mut group_hash
= group_backups(backup_list
);
128 let group_id
= group
.group_path().to_str().unwrap().to_owned();
130 let group_snapshots
= match group_hash
.get_mut(&group_id
) {
133 BackupInfo
::sort_list(data
, false);
136 None
=> bail
!("Backup group '{}' does not exists.", group_id
),
139 let mut snapshots
= vec
![];
141 for info
in group_snapshots
{
143 let group
= info
.backup_dir
.group();
145 snapshots
.push(json
!({
146 "backup-type": group
.backup_type(),
147 "backup-id": group
.backup_id(),
148 "backup-time": info
.backup_dir
.backup_time().timestamp(),
159 _rpcenv
: &mut RpcEnvironment
,
160 ) -> Result
<Value
, Error
> {
162 let store
= param
["store"].as_str().unwrap();
164 let datastore
= DataStore
::lookup_datastore(store
)?
;
166 println
!("Starting prune on store {}", store
);
168 let backup_list
= datastore
.list_backups()?
;
170 let group_hash
= group_backups(backup_list
);
172 for (_group_id
, mut list
) in group_hash
{
174 let mut mark
= HashSet
::new();
176 BackupInfo
::sort_list(&mut list
, false);
178 if let Some(keep_last
) = param
["keep-last"].as_u64() {
179 list
.iter().take(keep_last
as usize).for_each(|info
| {
180 mark
.insert(info
.backup_dir
.relative_path());
184 if let Some(keep_daily
) = param
["keep-daily"].as_u64() {
185 mark_selections(&mut mark
, &list
, keep_daily
as usize, |local_time
, _info
| {
186 format
!("{}/{}/{}", local_time
.year(), local_time
.month(), local_time
.day())
190 if let Some(keep_weekly
) = param
["keep-weekly"].as_u64() {
191 mark_selections(&mut mark
, &list
, keep_weekly
as usize, |local_time
, _info
| {
192 format
!("{}/{}", local_time
.year(), local_time
.iso_week().week())
196 if let Some(keep_monthly
) = param
["keep-monthly"].as_u64() {
197 mark_selections(&mut mark
, &list
, keep_monthly
as usize, |local_time
, _info
| {
198 format
!("{}/{}", local_time
.year(), local_time
.month())
202 if let Some(keep_yearly
) = param
["keep-yearly"].as_u64() {
203 mark_selections(&mut mark
, &list
, keep_yearly
as usize, |local_time
, _info
| {
204 format
!("{}/{}", local_time
.year(), local_time
.year())
208 let mut remove_list
: Vec
<BackupInfo
> = list
.into_iter()
209 .filter(|info
| !mark
.contains(&info
.backup_dir
.relative_path())).collect();
211 BackupInfo
::sort_list(&mut remove_list
, true);
213 for info
in remove_list
{
214 datastore
.remove_backup_dir(&info
.backup_dir
)?
;
221 pub fn add_common_prune_prameters(schema
: ObjectSchema
) -> ObjectSchema
{
226 IntegerSchema
::new("Number of backups to keep.")
231 IntegerSchema
::new("Number of daily backups to keep.")
236 IntegerSchema
::new("Number of weekly backups to keep.")
241 IntegerSchema
::new("Number of monthly backups to keep.")
246 IntegerSchema
::new("Number of yearly backups to keep.")
251 fn api_method_prune() -> ApiMethod
{
254 add_common_prune_prameters(
255 ObjectSchema
::new("Prune the datastore.")
258 StringSchema
::new("Datastore name.")
264 // this is just a test for mutability/mutex handling - will remove later
265 fn start_garbage_collection(
268 _rpcenv
: &mut RpcEnvironment
,
269 ) -> Result
<Value
, Error
> {
271 let store
= param
["store"].as_str().unwrap();
273 let datastore
= DataStore
::lookup_datastore(store
)?
;
275 println
!("Starting garbage collection on store {}", store
);
277 datastore
.garbage_collection()?
;
282 pub fn api_method_start_garbage_collection() -> ApiMethod
{
284 start_garbage_collection
,
285 ObjectSchema
::new("Start garbage collection.")
286 .required("store", StringSchema
::new("Datastore name."))
290 fn garbage_collection_status(
293 _rpcenv
: &mut RpcEnvironment
,
294 ) -> Result
<Value
, Error
> {
296 let store
= param
["store"].as_str().unwrap();
298 println
!("Garbage collection status on store {}", store
);
304 pub fn api_method_garbage_collection_status() -> ApiMethod
{
306 garbage_collection_status
,
307 ObjectSchema
::new("Garbage collection status.")
308 .required("store", StringSchema
::new("Datastore name."))
315 _rpcenv
: &mut RpcEnvironment
,
316 ) -> Result
<Value
, Error
> {
318 //let config = datastore::config()?;
320 let store
= param
["store"].as_str().unwrap();
322 let datastore
= DataStore
::lookup_datastore(store
)?
;
324 let mut list
= vec
![];
326 for info
in datastore
.list_backups()?
{
328 "backup-type": info
.backup_dir
.group().backup_type(),
329 "backup-id": info
.backup_dir
.group().backup_id(),
330 "backup-time": info
.backup_dir
.backup_time().timestamp(),
335 let result
= json
!(list
);
340 fn get_datastore_list(
343 _rpcenv
: &mut RpcEnvironment
,
344 ) -> Result
<Value
, Error
> {
346 let config
= datastore
::config()?
;
348 Ok(config
.convert_to_array("store"))
352 pub fn router() -> Router
{
354 let store_schema
: Arc
<Schema
> = Arc
::new(
355 StringSchema
::new("Datastore name.").into()
358 let datastore_info
= Router
::new()
361 {"subdir": "backups" }
,
362 {"subdir": "catar" }
,
364 {"subdir": "groups" }
,
365 {"subdir": "snapshots" }
,
366 {"subdir": "status" }
,
367 {"subdir": "prune" }
,
369 ObjectSchema
::new("Directory index.")
370 .required("store", store_schema
.clone()))
377 ObjectSchema
::new("List backups.")
378 .required("store", store_schema
.clone()))))
382 .download(catar
::api_method_download_catar())
383 .upload(catar
::api_method_upload_catar()))
387 .get(api_method_garbage_collection_status())
388 .post(api_method_start_garbage_collection()))
394 ObjectSchema
::new("List backup groups.")
395 .required("store", store_schema
.clone()))))
402 ObjectSchema
::new("List backup groups.")
403 .required("store", store_schema
.clone())
404 .required("backup-type", StringSchema
::new("Backup type."))
405 .required("backup-id", StringSchema
::new("Backup ID."))
411 ObjectSchema
::new("Delete backup snapshot.")
412 .required("store", store_schema
.clone())
413 .required("backup-type", StringSchema
::new("Backup type."))
414 .required("backup-id", StringSchema
::new("Backup ID."))
415 .required("backup-time", IntegerSchema
::new("Backup time (Unix epoch.)")
416 .minimum(1547797308))
423 .post(api_method_prune()));
427 let route
= Router
::new()
430 ObjectSchema
::new("Directory index.")))
431 .match_all("store", datastore_info
);