Abstract rate limiting away into ApiClient
This commit is contained in:
parent
f2eadbae2b
commit
fe363c0e26
8 changed files with 264 additions and 192 deletions
|
@ -64,8 +64,26 @@ impl ApiClient {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn rest(&self) -> &reqwest::Client {
|
||||
&self.rest
|
||||
pub async fn rest<'a, F, R>(&'a self, submitter: F) -> R::Output
|
||||
where
|
||||
F: Fn(&'a reqwest::Client) -> R,
|
||||
R: std::future::Future<Output = Result<reqwest::Response, anyhow::Error>>,
|
||||
{
|
||||
let _ = self.rest_semaphore.acquire().await.unwrap();
|
||||
let mut retries = 0;
|
||||
loop {
|
||||
let result: R::Output = submitter(&self.rest).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => break Ok(response),
|
||||
Err(_) if retries < 10 => {
|
||||
retries += 1;
|
||||
std::thread::sleep(retries * std::time::Duration::from_millis(500));
|
||||
continue;
|
||||
}
|
||||
err => break err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_url(&self) -> &Url {
|
||||
|
@ -91,20 +109,21 @@ impl ApiClient {
|
|||
{
|
||||
let report_method = reqwest::Method::from_bytes(b"REPORT").unwrap();
|
||||
let events_xml = self
|
||||
.rest()
|
||||
.request(
|
||||
report_method,
|
||||
// TODO extract into helper method
|
||||
self.caldav_base_url.join(&format!(
|
||||
"calendars/{}/{}",
|
||||
self.username(),
|
||||
calendar_name.to_lowercase().replace(" ", "-")
|
||||
))?,
|
||||
)
|
||||
.header("Prefer", "return-minimal")
|
||||
.header("Content-Type", "application/xml; charset=utf-8")
|
||||
.header("Depth", 1)
|
||||
.body(format!(
|
||||
.rest(|client| async {
|
||||
let response = client
|
||||
.request(
|
||||
report_method.clone(),
|
||||
// TODO extract into helper method
|
||||
self.caldav_base_url.join(&format!(
|
||||
"calendars/{}/{}",
|
||||
self.username(),
|
||||
calendar_name.to_lowercase().replace(" ", "-")
|
||||
))?,
|
||||
)
|
||||
.header("Prefer", "return-minimal")
|
||||
.header("Content-Type", "application/xml; charset=utf-8")
|
||||
.header("Depth", 1)
|
||||
.body(format!(
|
||||
"<c:calendar-query xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">
|
||||
<d:prop>
|
||||
<c:calendar-data />
|
||||
|
@ -135,7 +154,10 @@ impl ApiClient {
|
|||
.format(constants::ICAL_UTCTIME_FMT)
|
||||
.to_string()
|
||||
))
|
||||
.send()
|
||||
.send()
|
||||
.await;
|
||||
Ok(response?)
|
||||
})
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
|
|
@ -63,9 +63,12 @@ where
|
|||
// TODO code duplicated with schedule_csv::get_all_recipes
|
||||
let recipe_url = format!("apps/cookbook/api/recipes/{id}");
|
||||
let response = api_client
|
||||
.rest()
|
||||
.get(api_client.base_url().join(&recipe_url).unwrap())
|
||||
.send()
|
||||
.rest(|client| async {
|
||||
Ok(client
|
||||
.get(api_client.base_url().join(&recipe_url).unwrap())
|
||||
.send()
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
.expect(&format!("Cannot fetch recipe with id {}", id));
|
||||
|
||||
|
@ -119,9 +122,13 @@ async fn save_grocery_list(api_client: &ApiClient, filename: &str, contents: &st
|
|||
.and_then(|url| {
|
||||
futures::executor::block_on(async {
|
||||
let response = api_client
|
||||
.rest()
|
||||
.request(Method::from_bytes(b"MKCOL").unwrap(), url.clone())
|
||||
.send()
|
||||
.rest(|client| async {
|
||||
let r = client
|
||||
.request(Method::from_bytes(b"MKCOL").unwrap(), url.clone())
|
||||
.send()
|
||||
.await;
|
||||
Ok(r?)
|
||||
})
|
||||
.await;
|
||||
|
||||
match response.map(|r| r.status()) {
|
||||
|
@ -141,11 +148,15 @@ async fn save_grocery_list(api_client: &ApiClient, filename: &str, contents: &st
|
|||
let file_url = dav_base_url.join(filename).unwrap();
|
||||
log::info!("Saving grocery list to {}", &file_url);
|
||||
let response = api_client
|
||||
.rest()
|
||||
.put(file_url.clone())
|
||||
.header("Content-Type", "text/markdown; charset=utf-8")
|
||||
.body(contents.to_owned())
|
||||
.send()
|
||||
.rest(|client| async {
|
||||
let r = client
|
||||
.put(file_url.clone())
|
||||
.header("Content-Type", "text/markdown; charset=utf-8")
|
||||
.body(contents.to_owned())
|
||||
.send()
|
||||
.await;
|
||||
Ok(r?)
|
||||
})
|
||||
.await?;
|
||||
|
||||
match response.status() {
|
||||
|
|
|
@ -10,12 +10,16 @@ where
|
|||
{
|
||||
for url in urls {
|
||||
let response = api_client
|
||||
.rest()
|
||||
.post(api_client.base_url().join("apps/cookbook/import")?)
|
||||
.json(&serde_json::json!({
|
||||
"url": url.as_ref(),
|
||||
}))
|
||||
.send()
|
||||
.rest(|client| async {
|
||||
let r = client
|
||||
.post(api_client.base_url().join("apps/cookbook/import")?)
|
||||
.json(&serde_json::json!({
|
||||
"url": url.as_ref(),
|
||||
}))
|
||||
.send()
|
||||
.await;
|
||||
Ok(r?)
|
||||
})
|
||||
.await?;
|
||||
if ![StatusCode::OK, StatusCode::CONFLICT].contains(&response.status()) {
|
||||
anyhow::bail!(
|
||||
|
|
|
@ -4,23 +4,24 @@
|
|||
use {crate::api_client::ApiClient, crate::constants, anyhow::Result, reqwest::Method};
|
||||
|
||||
pub async fn with(api_client: &ApiClient, calendar_name: &str) -> Result<()> {
|
||||
let report_method = Method::from_bytes(b"REPORT")?;
|
||||
let report_method: Method = Method::from_bytes(b"REPORT")?;
|
||||
let events_xml = api_client
|
||||
.rest()
|
||||
.request(
|
||||
report_method,
|
||||
// TODO extract into helper method
|
||||
api_client.caldav_base_url().join(&format!(
|
||||
"calendars/{}/{}/",
|
||||
api_client.username(),
|
||||
calendar_name.to_lowercase().replace(" ", "-")
|
||||
))?,
|
||||
)
|
||||
.header("Prefer", "return-minimal")
|
||||
.header("Content-Type", "application/xml; charset=utf-8")
|
||||
.header("Depth", 1)
|
||||
.body(format!(
|
||||
"<c:calendar-query xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">
|
||||
.rest(|client| async {
|
||||
let response = client
|
||||
.request(
|
||||
report_method.clone(),
|
||||
// TODO extract into helper method
|
||||
api_client.caldav_base_url().join(&format!(
|
||||
"calendars/{}/{}/",
|
||||
api_client.username(),
|
||||
calendar_name.to_lowercase().replace(" ", "-")
|
||||
))?,
|
||||
)
|
||||
.header("Prefer", "return-minimal")
|
||||
.header("Content-Type", "application/xml; charset=utf-8")
|
||||
.header("Depth", 1)
|
||||
.body(format!(
|
||||
"<c:calendar-query xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">
|
||||
<d:prop>
|
||||
<d:getetag/>
|
||||
</d:prop>
|
||||
|
@ -33,9 +34,12 @@ pub async fn with(api_client: &ApiClient, calendar_name: &str) -> Result<()> {
|
|||
</c:filter>
|
||||
</c:calendar-query>
|
||||
",
|
||||
constants::CALENDAR_PROVIDER,
|
||||
))
|
||||
.send()
|
||||
constants::CALENDAR_PROVIDER,
|
||||
))
|
||||
.send()
|
||||
.await;
|
||||
Ok(response?)
|
||||
})
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
@ -52,9 +56,12 @@ pub async fn with(api_client: &ApiClient, calendar_name: &str) -> Result<()> {
|
|||
|
||||
for url in events_to_purge {
|
||||
api_client
|
||||
.rest()
|
||||
.delete(api_client.base_url().join(&url)?)
|
||||
.send()
|
||||
.rest(|client| async {
|
||||
Ok(client
|
||||
.delete(api_client.base_url().join(&url)?)
|
||||
.send()
|
||||
.await?)
|
||||
})
|
||||
.await?;
|
||||
log::debug!("Purged {}", &url);
|
||||
}
|
||||
|
|
|
@ -5,9 +5,13 @@ use {crate::api_client::ApiClient, crate::recipe, anyhow::Result};
|
|||
|
||||
pub async fn with(api_client: &ApiClient) -> Result<()> {
|
||||
let recipes = api_client
|
||||
.rest()
|
||||
.get(api_client.base_url().join("apps/cookbook/api/recipes")?)
|
||||
.send()
|
||||
.rest(|client| async {
|
||||
let response = client
|
||||
.get(api_client.base_url().join("apps/cookbook/api/recipes")?)
|
||||
.send()
|
||||
.await;
|
||||
Ok(response?)
|
||||
})
|
||||
.await?;
|
||||
println!("{:#?}", recipes.json::<Vec<recipe::Metadata>>().await?);
|
||||
todo!();
|
||||
|
|
|
@ -81,30 +81,53 @@ where
|
|||
async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<recipe::Recipe>>> {
|
||||
log::info!("Getting list of all recipes");
|
||||
let metadata = api_client
|
||||
.rest()
|
||||
.get(api_client.base_url().join("apps/cookbook/api/recipes")?)
|
||||
.send()
|
||||
.rest(|client| async {
|
||||
let response = client
|
||||
.get(api_client.base_url().join("apps/cookbook/api/recipes")?)
|
||||
.send()
|
||||
.await;
|
||||
Ok(response?)
|
||||
})
|
||||
.await?
|
||||
.json::<Vec<recipe::Metadata>>()
|
||||
.await?;
|
||||
|
||||
let recipes = metadata.iter().map(|rm| async {
|
||||
let response = api_client
|
||||
.rest()
|
||||
.get(
|
||||
api_client
|
||||
.base_url()
|
||||
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id))
|
||||
.unwrap(),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.expect(&format!(
|
||||
"Cannot fetch recipe {} with id {}",
|
||||
rm.name, rm.id
|
||||
));
|
||||
// TODO: wrap this, heavily refactor this mess
|
||||
let _ = api_client.rest_semaphore.acquire().await.unwrap();
|
||||
let mut retries = 0;
|
||||
let response = loop {
|
||||
let result = api_client
|
||||
.rest(|client| async {
|
||||
let response = client
|
||||
.get(
|
||||
api_client
|
||||
.base_url()
|
||||
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id))
|
||||
.unwrap(),
|
||||
)
|
||||
.send()
|
||||
.await;
|
||||
Ok(response?)
|
||||
})
|
||||
.await;
|
||||
|
||||
response.json::<recipe::Recipe>().await.map(|r| Rc::new(r))
|
||||
match result {
|
||||
Ok(response) => break response,
|
||||
Err(_) if retries < 10 => {
|
||||
retries += 1;
|
||||
std::thread::sleep(retries * std::time::Duration::from_millis(500));
|
||||
continue;
|
||||
}
|
||||
_ => bail!("Cannot fetch recipe {} with id {}", rm.name, rm.id),
|
||||
}
|
||||
};
|
||||
|
||||
response
|
||||
.json::<recipe::Recipe>()
|
||||
.await
|
||||
.map(|r| Rc::new(r))
|
||||
.map_err(|err| anyhow::anyhow!(err))
|
||||
});
|
||||
|
||||
let recipes = try_join_all(recipes).await?;
|
||||
|
@ -180,20 +203,19 @@ where
|
|||
1,
|
||||
);
|
||||
|
||||
// TODO: wrap this
|
||||
let _ = api_client.rest_semaphore.acquire().await.unwrap();
|
||||
let response = api_client
|
||||
.rest()
|
||||
.put(url)
|
||||
.header("Content-Type", "text/calendar; charset=utf-8")
|
||||
.body(cal_as_string)
|
||||
.send()
|
||||
.rest(|client| async {
|
||||
let response = client
|
||||
.put(url.clone())
|
||||
.header("Content-Type", "text/calendar; charset=utf-8")
|
||||
.body(cal_as_string.clone())
|
||||
.send()
|
||||
.await;
|
||||
Ok(response?)
|
||||
})
|
||||
.await;
|
||||
|
||||
log::info!("{}", info_message);
|
||||
|
||||
// TODO: magic numbers are bad...
|
||||
std::thread::sleep(std::time::Duration::from_millis(300));
|
||||
response
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue