Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added local caching mechanism for list of polkadot-sdk versions to fasten fetch time #28

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
84 changes: 84 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ env_logger = "0.11.3"
reqwest = { version = "0.12.3", features = ["json"] }
toml = "0.8.12"
tokio = { version = "1.37.0", features = ["full"] }
dirs = "3.0"

[dev-dependencies]
tokio-test = "0.4"
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ If you want to check if the dependencies in your local Cargo.toml file are match

If you want to update the ORML crates in your local Cargo.toml, you can use the `-O` or `--orml` flag along with the `--version` flag to update the ORML crates along with the polkadot-sdk crates. This works only if the supplied version is present in the ORML releases.

If you want to fetch available polkadot-sdk versions from a local cache, or want to just cache the versions list if using for the first time, you can use the `-C` or `--cache` flag alongside the `--list` flag.

If you want to update the local cache with a freshly fetched list of versions from GitHub, you can use the `-u` or `--update-cache` flag.

```sh
# Go to the directory containing the Cargo.toml file you want to update
cd <cargo-toml-dir>
Expand All @@ -43,6 +47,10 @@ psvm -l
psvm -v "1.4.0" -c
# Update the ORML dependencies along with the Polkadot SDK dependencies.
psvm -v "1.6.0" -O
# Update the local cache with freshly fetched list of versions from GitHub
psvm -u
# Fetch the list of versions from the local cache
psvm --list --cache
```

> Listing all available Polkadot SDK versions requires querying the GitHub API, so your IP may be rate-limited. If a rate limit is reached, the tool will fallback to the GitHub CLI to list the versions. Ensure you have the GitHub CLI installed and authenticated to avoid any issue.
Expand Down
99 changes: 99 additions & 0 deletions src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use crate::versions::get_polkadot_sdk_versions;
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};

/// The structure to hold the cached list of versions
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct Cache {
/// Data to be cached
pub data: Vec<String>,
}

impl Cache {
// Load cache from a file
pub fn load(path: &PathBuf) -> io::Result<Self> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let cache: Cache = serde_json::from_str(&contents)?;
Ok(cache)
}

// Save cache to a file
pub fn save(&self, path: &PathBuf) -> io::Result<()> {
// Create the cache path if it doesn't exist. (Should technically only happen once when running for the first time.)
if !Path::new(path).exists() {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
}

let contents = serde_json::to_string(&self)?;
let mut file = File::create(path)?;
file.write_all(contents.as_bytes())?;
Ok(())
}
}

pub fn get_cache_directory() -> Option<PathBuf> {
if let Some(cache_dir) = dirs::cache_dir() {
let app_cache_dir = cache_dir.join("psvm/cache.json");
Some(app_cache_dir)
} else {
None
}
}

/// Retrieves the list of Polkadot SDK versions, either from a local cache or by fetching them anew.
///
/// This function first attempts to load the list of Polkadot SDK versions from a local cache file.
/// If the cache file exists and can be loaded, the cached data is returned. If the cache does not exist,
/// is unreadable, or any other error occurs during loading, the function logs an error message,
/// fetches the list of versions by calling `get_polkadot_sdk_versions`, caches the newly fetched list,
/// and then returns it.
///
/// # Returns
/// A `Result` wrapping a vector of strings, where each string is a version of the Polkadot SDK.
/// If the operation is successful, `Ok(Vec<String>)` is returned, containing the list of versions.
/// If an error occurs during fetching new versions or saving them to the cache, an error is returned
/// wrapped in `Err(Box<dyn std::error::Error>)`.
///
/// # Errors
/// This function can return an error in several cases, including but not limited to:
/// - Failure to read the cache file due to permissions or file not found.
/// - Failure to write to the cache file, possibly due to permissions issues.
/// - Errors returned by `get_polkadot_sdk_versions` during the fetching process.
///
/// # Examples
/// ```
/// #[tokio::main]
/// async fn main() {
/// match get_polkadot_sdk_versions_from_cache().await {
/// Ok(versions) => println!("Polkadot SDK Versions: {:?}", versions),
/// Err(e) => eprintln!("Failed to get Polkadot SDK versions: {}", e),
/// }
/// }
/// ```
pub async fn get_polkadot_sdk_versions_from_cache(
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
// Can unwrap because we know the cache directory exists
let cache_path = get_cache_directory().unwrap();
// Attempt to load the cache
let cache = Cache::load(&cache_path);

let data = if let Ok(cache) = cache {
cache.data
} else {
log::error!("Cache file doesn't exist or failed to load, fetching new data");
let new_data = get_polkadot_sdk_versions().await?;
let new_cache = Cache {
data: new_data.clone(),
};
new_cache.save(&cache_path)?;
new_data
};

Ok(data)
}
51 changes: 43 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

mod cache;
mod tests;
mod versions;

Expand All @@ -29,6 +30,8 @@ use versions::{
get_version_mapping_with_fallback, include_orml_crates_in_version_mapping, Repository,
};

use cache::{get_cache_directory, get_polkadot_sdk_versions_from_cache};

pub const DEFAULT_GIT_SERVER: &str = "https://raw.githubusercontent.com";

/// Polkadot SDK Version Manager.
Expand All @@ -42,7 +45,12 @@ struct Command {
path: PathBuf,

/// Specifies the Polkadot SDK version. Use '--list' flag to display available versions.
#[clap(short, long, required_unless_present = "list")]
#[clap(
short,
long,
required_unless_present = "list",
required_unless_present = "update_cache"
)]
version: Option<String>,

/// Overwrite local dependencies (using path) with same name as the ones in the Polkadot SDK.
Expand All @@ -60,23 +68,43 @@ struct Command {
/// To either list available ORML versions or update the Cargo.toml file with corresponding ORML versions.
#[clap(short('O'), long)]
orml: bool,

/// To read the list of available versions from cache.
#[clap(short('C'), long)]
cache: bool,

/// To update the cache having the list of available versions.
#[clap(short('u'), long)]
update_cache: bool,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
let cmd = Command::parse();

if cmd.list {
let crates_versions = if cmd.orml {
get_release_branches_versions(Repository::Orml).await?
if cmd.update_cache {
log::info!("Updating cache by freshly fetching versions from GitHub");
let versions = get_polkadot_sdk_versions().await?;
let cache_dir = if let Some(cache_directory) = get_cache_directory() {
cache_directory
} else {
get_polkadot_sdk_versions().await?
return Err("Could not determine cache directory".into());
};
let cache = cache::Cache { data: versions };
cache.save(&cache_dir)?;
return Ok(());
}

println!("Available versions:");
for version in crates_versions.iter() {
println!("- {}", version);
if cmd.list {
if cmd.orml {
print_version_list(get_release_branches_versions(Repository::Orml).await?);
} else if cmd.cache {
log::info!("Reading versions from cache");
print_version_list(get_polkadot_sdk_versions_from_cache().await?);
} else {
log::info!("Fetching versions from GitHub");
print_version_list(get_polkadot_sdk_versions().await?);
}
return Ok(());
}
Expand All @@ -99,6 +127,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

fn print_version_list(crates_versions: Vec<String>) {
println!("Available versions:");
for version in crates_versions.iter() {
println!("- {}", version);
}
}

fn validate_workspace_path(mut path: PathBuf) -> Result<PathBuf, Box<dyn std::error::Error>> {
if path.is_dir() {
path = path.join("Cargo.toml");
Expand Down
Loading