Skip to content

Commit

Permalink
chore: add etag based cache system for repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
anatawa12 committed Jan 25, 2023
1 parent 7df45e5 commit ee59813
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 17 deletions.
6 changes: 4 additions & 2 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,11 @@ impl RepoPackages {
let client = crate::create_client();

if let Some(url) = Url::parse(&self.name_or_url).ok() {
let repo = download_remote_repository(&client, url)
let repo = download_remote_repository(&client, url, None)
.await
.expect("downloading repository");
.expect("downloading repository")
.expect("logic failure: no etag")
.0;

let cache = repo
.get("packages")
Expand Down
56 changes: 42 additions & 14 deletions src/vpm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,9 @@ impl Environment {
{
return Err(AddRepositoryErr::AlreadyAdded);
}
let remote_repo = download_remote_repository(&self.http, url.clone()).await?;
let (remote_repo, etag) = download_remote_repository(&self.http, url.clone(), None)
.await?
.expect("logic failure: no etag");
let local_path = self
.get_repos_dir()
.joined(format!("{}.json", uuid::Uuid::new_v4()));
Expand All @@ -469,6 +471,14 @@ impl Environment {
.unwrap_or(JsonMap::new());
local_cache.repo = Some(remote_repo);

// set etag
if let Some(etag) = etag {
local_cache
.vrc_get
.get_or_insert_with(Default::default)
.etag = etag;
}

write_repo(&local_path, &local_cache).await?;

self.add_user_repo(&UserRepoSetting::new(
Expand Down Expand Up @@ -603,13 +613,21 @@ async fn update_from_remote(client: &Client, path: &Path, repo: &mut LocalCached
return;
};

match download_remote_repository(&client, remote_url).await {
Ok(remote_repo) => {
let etag = repo.vrc_get.as_ref().map(|x| x.etag.as_str());
match download_remote_repository(&client, remote_url, etag).await {
Ok(None) => log::debug!("cache matched downloading {}", remote_url),
Ok(Some((remote_repo, etag))) => {
repo.cache = remote_repo
.get("packages")
.and_then(Value::as_object)
.cloned()
.unwrap_or(JsonMap::new());
// set etag
if let Some(etag) = etag {
repo.vrc_get.get_or_insert_with(Default::default).etag = etag;
} else {
repo.vrc_get.as_mut().map(|x| x.etag.clear());
}
repo.repo = Some(remote_repo);
}
Err(e) => {
Expand All @@ -632,23 +650,33 @@ async fn write_repo(path: &Path, repo: &LocalCachedRepository) -> io::Result<()>
Ok(())
}

// returns None if etag matches
pub(crate) async fn download_remote_repository(
client: &Client,
url: impl IntoUrl,
) -> io::Result<JsonMap> {
etag: Option<&str>,
) -> io::Result<Option<(JsonMap, Option<String>)>> {
fn map_err(err: reqwest::Error) -> io::Error {
io::Error::new(io::ErrorKind::NotFound, err)
}
client
.get(url)
.send()
.await
.err_mapped()?
.error_for_status()
.err_mapped()?
.json()
.await
.err_mapped()
let mut request = client.get(url);
if let Some(etag) = &etag {
request = request.header("If-None-Match", etag.to_owned())
}
let response = request.send().await.err_mapped()?;
let response = response.error_for_status().err_mapped()?;

if etag.is_some() && response.status() == 304 {
return Ok(None);
}

let etag = response
.headers()
.get("Etag")
.and_then(|x| x.to_str().ok())
.map(str::to_owned);

Ok(Some((response.json().await.err_mapped()?, etag)))
}

mod vpm_manifest {
Expand Down
10 changes: 9 additions & 1 deletion src/vpm/repo_holder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ impl RepoHolder {
let client = self.http.clone();
self.get_repo(path, || async {
// if local repository not found: try downloading remote one
let remote_repo = download_remote_repository(&client, remote_url).await?;
let (remote_repo, etag) = download_remote_repository(&client, remote_url, None)
.await?
.expect("logic failure: no etag");

let mut local_cache = LocalCachedRepository::new(
path.to_owned(),
Expand All @@ -41,6 +43,12 @@ impl RepoHolder {
.cloned()
.unwrap_or(JsonMap::new());
local_cache.repo = Some(remote_repo);
if let Some(etag) = etag {
local_cache
.vrc_get
.get_or_insert_with(Default::default)
.etag = etag;
}

match write_repo(path, &local_cache).await {
Ok(_) => {}
Expand Down
10 changes: 10 additions & 0 deletions src/vpm/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ pub mod repository {
#[serde(rename = "Description")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<Description>,
#[serde(rename = "vrc-get")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vrc_get: Option<VrcGetMeta>,
}

impl LocalCachedRepository {
Expand All @@ -113,6 +116,7 @@ pub mod repository {
name,
r#type: Some("JsonRepo".to_owned()),
}),
vrc_get: None,
}
}
}
Expand All @@ -135,6 +139,12 @@ pub mod repository {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct VrcGetMeta {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub etag: String,
}
}

pub mod remote_repo {
Expand Down

0 comments on commit ee59813

Please sign in to comment.