1 // Copyright (C) 2024 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use crates_index::{http, Crate, SparseIndex}; 16 use std::{cell::RefCell, collections::HashSet}; 17 18 use crate::Error; 19 20 pub struct CratesIoIndex { 21 fetcher: Box<dyn CratesIoFetcher>, 22 } 23 24 impl CratesIoIndex { new() -> Result<CratesIoIndex, Error>25 pub fn new() -> Result<CratesIoIndex, Error> { 26 Ok(CratesIoIndex { 27 fetcher: Box::new(OnlineFetcher { 28 index: crates_index::SparseIndex::new_cargo_default()?, 29 agent: ureq::Agent::new_with_defaults(), 30 fetched: RefCell::new(HashSet::new()), 31 }), 32 }) 33 } new_offline() -> Result<CratesIoIndex, Error>34 pub fn new_offline() -> Result<CratesIoIndex, Error> { 35 Ok(CratesIoIndex { 36 fetcher: Box::new(OfflineFetcher { 37 index: crates_index::SparseIndex::new_cargo_default()?, 38 }), 39 }) 40 } get_crate(&self, crate_name: impl AsRef<str>) -> Result<Crate, Error>41 pub fn get_crate(&self, crate_name: impl AsRef<str>) -> Result<Crate, Error> { 42 self.fetcher.fetch(crate_name.as_ref()) 43 } 44 } 45 46 pub trait CratesIoFetcher { fetch(&self, crate_name: &str) -> Result<Crate, Error>47 fn fetch(&self, crate_name: &str) -> Result<Crate, Error>; 48 } 49 50 struct OnlineFetcher { 51 index: SparseIndex, 52 agent: ureq::Agent, 53 // Keep track of crates we have fetched, to avoid fetching them multiple times. 54 fetched: RefCell<HashSet<String>>, 55 } 56 57 struct OfflineFetcher { 58 index: SparseIndex, 59 } 60 61 impl CratesIoFetcher for OnlineFetcher { fetch(&self, crate_name: &str) -> Result<Crate, Error>62 fn fetch(&self, crate_name: &str) -> Result<Crate, Error> { 63 let mut fetched = self.fetched.borrow_mut(); 64 if fetched.contains(crate_name) { 65 return Ok(self.index.crate_from_cache(crate_name.as_ref())?); 66 } 67 68 let request = self 69 .index 70 .make_cache_request(crate_name)? 71 .version(ureq::http::Version::HTTP_11) 72 .body(())?; 73 74 let response = self.agent.run(request)?; 75 let (parts, mut body) = response.into_parts(); 76 let response = http::Response::from_parts(parts, body.read_to_vec()?); 77 let response = self 78 .index 79 .parse_cache_response(crate_name, response, true)? 80 .ok_or(Error::CrateNotFound(crate_name.to_string()))?; 81 82 fetched.insert(crate_name.to_string()); 83 Ok(response) 84 } 85 } 86 87 impl CratesIoFetcher for OfflineFetcher { fetch(&self, crate_name: &str) -> Result<Crate, Error>88 fn fetch(&self, crate_name: &str) -> Result<Crate, Error> { 89 Ok(self.index.crate_from_cache(crate_name.as_ref())?) 90 } 91 } 92