diff --git a/src/backend/choose.rs b/src/backend/choose.rs index 043ecf7..ff174bf 100644 --- a/src/backend/choose.rs +++ b/src/backend/choose.rs @@ -17,7 +17,7 @@ impl ChooseBackend { pub fn from_url(url: &str) -> Result { Ok(match url.split_once(':') { Some(("rclone", path)) => Rclone(RcloneBackend::new(path)?), - Some(("rest", path)) => Rest(RestBackend::new(path)), + Some(("rest", path)) => Rest(RestBackend::new(path)?), Some(("local", path)) => Local(LocalBackend::new(path)?), Some((backend, _)) => bail!("backend {backend} is not supported!"), None => Local(LocalBackend::new(url)?), diff --git a/src/backend/rclone.rs b/src/backend/rclone.rs index daa0761..8faddbb 100644 --- a/src/backend/rclone.rs +++ b/src/backend/rclone.rs @@ -108,7 +108,7 @@ impl RcloneBackend { let url = "http://".to_string() + &user + ":" + &password + "@" + &url[7..]; debug!("using REST backend with url {url}."); - let rest = RestBackend::new(&url); + let rest = RestBackend::new(&url)?; Ok(Self { _child_data: Arc::new(ChildToKill(child)), rest, diff --git a/src/backend/rest.rs b/src/backend/rest.rs index 2826c36..ccde2eb 100644 --- a/src/backend/rest.rs +++ b/src/backend/rest.rs @@ -22,6 +22,7 @@ impl CheckError for Response { fn check_error(self) -> std::result::Result> { match self.error_for_status() { Ok(t) => Ok(t), + // Note: status() always give Some(_) as it is called from a Response Err(err) if err.status().unwrap().is_client_error() => Err(Error::Permanent(err)), Err(err) => Err(Error::Transient { err, @@ -58,17 +59,17 @@ fn notify(err: reqwest::Error, duration: Duration) { } impl RestBackend { - pub fn new(url: &str) -> Self { + pub fn new(url: &str) -> Result { let url = if url.ends_with('/') { - Url::parse(url).unwrap() + Url::parse(url)? } else { // add a trailing '/' if there is none let mut url = url.to_string(); url.push('/'); - Url::parse(&url).unwrap() + Url::parse(&url)? }; - Self { + Ok(Self { url, client: Client::new(), backoff: MaybeBackoff(Some( @@ -76,21 +77,21 @@ impl RestBackend { .with_max_elapsed_time(Some(Duration::from_secs(600))) .build(), )), - } + }) } - fn url(&self, tpe: FileType, id: &Id) -> String { - let hex_id = id.to_hex(); + fn url(&self, tpe: FileType, id: &Id) -> Result { let id_path = match tpe { FileType::Config => "config".to_string(), _ => { + let hex_id = id.to_hex(); let mut path = tpe.name().to_string(); path.push('/'); path.push_str(&hex_id); path } }; - self.url.join(&id_path).unwrap().into() + Ok(self.url.join(&id_path)?) } } @@ -119,28 +120,26 @@ impl ReadBackend for RestBackend { } fn list_with_size(&self, tpe: FileType) -> Result> { + let url = if tpe == FileType::Config { + self.url.join("config")? + } else { + let mut path = tpe.name().to_string(); + path.push('/'); + self.url.join(&path)? + }; + Ok(backoff::retry_notify( self.backoff.clone(), || { if tpe == FileType::Config { return Ok( - match self - .client - .head(self.url.join("config").unwrap()) - .send()? - .status() - .is_success() - { + match self.client.head(url.clone()).send()?.status().is_success() { true => vec![(Id::default(), 0)], false => Vec::new(), }, ); } - let mut path = tpe.name().to_string(); - path.push('/'); - let url = self.url.join(&path).unwrap(); - // format which is delivered by the REST-service #[derive(Deserialize)] struct ListEntry { @@ -150,7 +149,7 @@ impl ReadBackend for RestBackend { let list = self .client - .get(url) + .get(url.clone()) .header("Accept", "application/vnd.x.restic.rest.v2") .send()? .check_error()? @@ -162,12 +161,13 @@ impl ReadBackend for RestBackend { } fn read_full(&self, tpe: FileType, id: &Id) -> Result { + let url = self.url(tpe, id)?; Ok(backoff::retry_notify( self.backoff.clone(), || { Ok(self .client - .get(self.url(tpe, id)) + .get(url.clone()) .send()? .check_error()? .bytes()? @@ -188,12 +188,13 @@ impl ReadBackend for RestBackend { ) -> Result { let offset2 = offset + length - 1; let header_value = format!("bytes={}-{}", offset, offset2); + let url = self.url(tpe, id)?; Ok(backoff::retry_notify( self.backoff.clone(), || { Ok(self .client - .get(self.url(tpe, id)) + .get(url.clone()) .header("Range", header_value.clone()) .send()? .check_error()? @@ -208,13 +209,11 @@ impl ReadBackend for RestBackend { impl WriteBackend for RestBackend { fn create(&self) -> Result<()> { + let url = self.url.join("?create=true")?; Ok(backoff::retry_notify( self.backoff.clone(), || { - self.client - .post(self.url.join("?create=true").unwrap()) - .send()? - .check_error()?; + self.client.post(url.clone()).send()?.check_error()?; Ok(()) }, notify, @@ -223,10 +222,11 @@ impl WriteBackend for RestBackend { fn write_bytes(&self, tpe: FileType, id: &Id, _cacheable: bool, buf: Bytes) -> Result<()> { trace!("writing tpe: {:?}, id: {}", &tpe, &id); - let req_builder = self.client.post(self.url(tpe, id)).body(buf); + let req_builder = self.client.post(self.url(tpe, id)?).body(buf); Ok(backoff::retry_notify( self.backoff.clone(), || { + // Note: try_clone() always gives Some(_) as the body is Bytes which is clonable req_builder.try_clone().unwrap().send()?.check_error()?; Ok(()) }, @@ -236,13 +236,11 @@ impl WriteBackend for RestBackend { fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> Result<()> { trace!("removing tpe: {:?}, id: {}", &tpe, &id); + let url = self.url(tpe, id)?; Ok(backoff::retry_notify( self.backoff.clone(), || { - self.client - .delete(self.url(tpe, id)) - .send()? - .check_error()?; + self.client.delete(url.clone()).send()?.check_error()?; Ok(()) }, notify,