]>
Commit | Line | Data |
---|---|---|
1629d2ad DM |
1 | use failure::*; |
2 | ||
0fe5d605 | 3 | use crate::tools; |
0b05fd58 | 4 | use crate::tools::wrapped_reader_stream::*; |
e5064ba6 | 5 | use crate::backup::*; |
2085142e | 6 | //use crate::server::rest::*; |
ef2f2efb | 7 | use crate::api_schema::*; |
dc9a007b | 8 | use crate::api_schema::router::*; |
1629d2ad | 9 | |
e909522f | 10 | use chrono::{Local, TimeZone}; |
7ca80246 | 11 | |
2085142e DM |
12 | use serde_json::Value; |
13 | use std::io::Write; | |
1629d2ad | 14 | use futures::*; |
dd79265a | 15 | //use std::path::PathBuf; |
ff3d3100 | 16 | use std::sync::Arc; |
1629d2ad | 17 | |
83bdac1e DM |
18 | use hyper::Body; |
19 | use hyper::http::request::Parts; | |
20 | ||
1629d2ad | 21 | pub struct UploadCaTar { |
83bdac1e | 22 | stream: Body, |
93d5d779 | 23 | index: DynamicIndexWriter, |
1629d2ad DM |
24 | count: usize, |
25 | } | |
26 | ||
27 | impl 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 |
49 | fn 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(¶m, "store")?; |
49dc0740 | 58 | let mut archive_name = String::from(tools::required_string_param(¶m, "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(¶m, "backup-type")?; |
67 | let backup_id = tools::required_string_param(¶m, "backup-id")?; | |
68 | let backup_time = tools::required_integer_param(¶m, "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 |
106 | pub 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 |
127 | fn 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(¶m, "store")?; |
d5c34d98 DM |
136 | let mut archive_name = tools::required_string_param(¶m, "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(¶m, "backup-type")?; |
145 | let backup_id = tools::required_string_param(¶m, "backup-id")?; | |
146 | let backup_time = tools::required_integer_param(¶m, "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 | ||
170 | pub 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 | } |