]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/admin/datastore/catar.rs
src/catar/decoder.rs: simplify public restore API
[proxmox-backup.git] / src / api2 / admin / datastore / catar.rs
CommitLineData
1629d2ad
DM
1use failure::*;
2
0fe5d605 3use crate::tools;
0b05fd58 4use crate::tools::wrapped_reader_stream::*;
e5064ba6 5use crate::backup::*;
2085142e 6//use crate::server::rest::*;
ef2f2efb 7use crate::api_schema::*;
dc9a007b 8use crate::api_schema::router::*;
1629d2ad 9
e909522f 10use chrono::{Local, TimeZone};
7ca80246 11
2085142e
DM
12use serde_json::Value;
13use std::io::Write;
1629d2ad 14use futures::*;
dd79265a 15//use std::path::PathBuf;
ff3d3100 16use std::sync::Arc;
1629d2ad 17
83bdac1e
DM
18use hyper::Body;
19use hyper::http::request::Parts;
20
1629d2ad 21pub struct UploadCaTar {
83bdac1e 22 stream: Body,
93d5d779 23 index: DynamicIndexWriter,
1629d2ad
DM
24 count: usize,
25}
26
27impl Future for UploadCaTar {
28 type Item = ();
29 type Error = failure::Error;
30
31 fn poll(&mut self) -> Poll<(), failure::Error> {
32 loop {
1629d2ad
DM
33 match try_ready!(self.stream.poll()) {
34 Some(chunk) => {
35 self.count += chunk.len();
1c7a88ae 36 if let Err(err) = self.index.write_all(&chunk) {
1629d2ad
DM
37 bail!("writing chunk failed - {}", err);
38 }
1629d2ad
DM
39 }
40 None => {
2085142e 41 self.index.close()?;
1629d2ad
DM
42 return Ok(Async::Ready(()))
43 }
44 }
45 }
46 }
47}
48
e82dad97
DM
49fn upload_catar(
50 parts: Parts,
51 req_body: Body,
52 param: Value,
53 _info: &ApiAsyncMethod,
54 _rpcenv: &mut RpcEnvironment,
55) -> Result<BoxFut, Error> {
1629d2ad 56
5a778d92 57 let store = tools::required_string_param(&param, "store")?;
49dc0740 58 let mut archive_name = String::from(tools::required_string_param(&param, "archive-name")?);
541a3022
DM
59
60 if !archive_name.ends_with(".catar") {
61 bail!("got wront file extension (expected '.catar')");
62 }
63
64 archive_name.push_str(".didx");
1629d2ad 65
e77a02ed
DM
66 let backup_type = tools::required_string_param(&param, "backup-type")?;
67 let backup_id = tools::required_string_param(&param, "backup-id")?;
68 let backup_time = tools::required_integer_param(&param, "backup-time")?;
ff3d3100 69
f02e6fc4 70 println!("Upload {}/{}/{}/{}/{}", store, backup_type, backup_id, backup_time, archive_name);
1629d2ad 71
83bdac1e
DM
72 let content_type = parts.headers.get(http::header::CONTENT_TYPE)
73 .ok_or(format_err!("missing content-type header"))?;
74
75 if content_type != "application/x-proxmox-backup-catar" {
76 bail!("got wrong content-type for catar archive upload");
77 }
78
247cdbce
DM
79 let chunk_size = param["chunk-size"].as_u64().unwrap_or(4096*1024);
80 verify_chunk_size(chunk_size)?;
0ee0ad5b
DM
81
82 let datastore = DataStore::lookup_datastore(store)?;
391d3107 83 let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
1629d2ad 84
b3483782 85 let (mut path, _new) = datastore.create_backup_dir(&backup_dir)?;
ff3d3100 86
541a3022 87 path.push(archive_name);
ff3d3100 88
247cdbce 89 let index = datastore.create_dynamic_writer(path, chunk_size as usize)?;
1629d2ad
DM
90
91 let upload = UploadCaTar { stream: req_body, index, count: 0};
92
2085142e 93 let resp = upload.and_then(|_| {
1629d2ad
DM
94
95 let response = http::Response::builder()
96 .status(200)
97 .body(hyper::Body::empty())
98 .unwrap();
99
100 Ok(response)
101 });
102
0ee0ad5b 103 Ok(Box::new(resp))
1629d2ad
DM
104}
105
50cfb695
DM
106pub fn api_method_upload_catar() -> ApiAsyncMethod {
107 ApiAsyncMethod::new(
1629d2ad
DM
108 upload_catar,
109 ObjectSchema::new("Upload .catar backup file.")
5a778d92 110 .required("store", StringSchema::new("Datastore name."))
49dc0740 111 .required("archive-name", StringSchema::new("Backup archive name."))
e77a02ed 112 .required("backup-type", StringSchema::new("Backup type.")
ff3d3100 113 .format(Arc::new(ApiStringFormat::Enum(vec!["ct".into(), "host".into()]))))
e77a02ed
DM
114 .required("backup-id", StringSchema::new("Backup ID."))
115 .required("backup-time", IntegerSchema::new("Backup time (Unix epoch.)")
ff3d3100 116 .minimum(1547797308))
247cdbce
DM
117 .optional(
118 "chunk-size",
119 IntegerSchema::new("Chunk size in bytes. Must be a power of 2.")
120 .minimum(64*1024)
121 .maximum(4096*1024)
122 .default(4096*1024)
123 )
1629d2ad
DM
124 )
125}
50cfb695 126
e82dad97
DM
127fn download_catar(
128 _parts: Parts,
129 _req_body: Body,
130 param: Value,
131 _info: &ApiAsyncMethod,
132 _rpcenv: &mut RpcEnvironment,
133) -> Result<BoxFut, Error> {
50cfb695 134
6a4c0916 135 let store = tools::required_string_param(&param, "store")?;
d5c34d98
DM
136 let mut archive_name = tools::required_string_param(&param, "archive-name")?.to_owned();
137
138 if !archive_name.ends_with(".catar") {
139 bail!("wrong archive extension");
140 } else {
141 archive_name.push_str(".didx");
142 }
6a4c0916 143
e77a02ed
DM
144 let backup_type = tools::required_string_param(&param, "backup-type")?;
145 let backup_id = tools::required_string_param(&param, "backup-id")?;
146 let backup_time = tools::required_integer_param(&param, "backup-time")?;
6a4c0916 147
dd79265a 148 println!("Download {} from {} ({}/{}/{}/{})", archive_name, store,
875fb1c0 149 backup_type, backup_id, Local.timestamp(backup_time, 0), archive_name);
6a4c0916
DM
150
151 let datastore = DataStore::lookup_datastore(store)?;
152
391d3107 153 let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
38b0dfa5
DM
154
155 let mut path = backup_dir.relative_path();
dd79265a 156 path.push(archive_name);
6a4c0916 157
93d5d779
DM
158 let index = datastore.open_dynamic_reader(path)?;
159 let reader = BufferedDynamicReader::new(index);
0b05fd58 160 let stream = WrappedReaderStream::new(reader);
6a4c0916 161
0b05fd58
DM
162 // fixme: set size, content type?
163 let response = http::Response::builder()
164 .status(200)
165 .body(Body::wrap_stream(stream))?;
166
167 Ok(Box::new(future::ok(response)))
50cfb695
DM
168}
169
170pub fn api_method_download_catar() -> ApiAsyncMethod {
171 ApiAsyncMethod::new(
172 download_catar,
173 ObjectSchema::new("Download .catar backup file.")
174 .required("store", StringSchema::new("Datastore name."))
49dc0740 175 .required("archive-name", StringSchema::new("Backup archive name."))
e77a02ed 176 .required("backup-type", StringSchema::new("Backup type.")
50cfb695 177 .format(Arc::new(ApiStringFormat::Enum(vec!["ct".into(), "host".into()]))))
e77a02ed
DM
178 .required("backup-id", StringSchema::new("Backup ID."))
179 .required("backup-time", IntegerSchema::new("Backup time (Unix epoch.)")
50cfb695
DM
180 .minimum(1547797308))
181
182 )
183}