use crate::{
api::ApiClient,
config::Config,
error::AstudiosError,
model::{AndroidStudio, AndroidStudioReleasesList},
};
use std::io::Write;
use std::{
fs,
path::PathBuf,
time::{Duration, SystemTime},
};
pub struct AndroidStudioLister {
cache_dir: PathBuf,
}
impl AndroidStudioLister {
pub fn new() -> Result<Self, AstudiosError> {
let cache_dir = Config::cache_dir();
fs::create_dir_all(&cache_dir)?;
Ok(Self { cache_dir })
}
pub fn with_cache_dir(cache_dir: PathBuf) -> Result<Self, AstudiosError> {
fs::create_dir_all(&cache_dir)?;
Ok(Self { cache_dir })
}
pub fn get_releases(&self) -> Result<AndroidStudioReleasesList, AstudiosError> {
let cache_path = self.cache_dir.join("releases.json");
if let Some(cached) = self.load_cached_releases(&cache_path)? {
eprintln!("âšī¸ Loading Android Studio versions from cache...");
return Ok(cached);
}
eprintln!("đ Fetching Android Studio releases from JetBrains...");
std::io::stderr().flush().ok();
let client = ApiClient::new()?;
let content = client.fetch_releases()?;
self.save_releases_to_cache(&cache_path, &content)?;
eprintln!("â
Successfully fetched {} releases", content.items.len());
Ok(content)
}
pub fn get_latest_release(&self) -> Result<AndroidStudio, AstudiosError> {
let releases = self.get_releases()?;
releases
.items
.into_iter()
.find(|item| item.is_release())
.ok_or_else(|| {
AstudiosError::VersionNotFound("No release versions available".to_string())
})
}
pub fn get_latest_prerelease(&self) -> Result<AndroidStudio, AstudiosError> {
let releases = self.get_releases()?;
releases
.items
.into_iter()
.find(|item| item.is_beta() || item.is_canary())
.ok_or_else(|| {
AstudiosError::VersionNotFound("No pre-release versions available".to_string())
})
}
pub fn find_version_by_query(&self, query: &str) -> Result<AndroidStudio, AstudiosError> {
let releases = self.get_releases()?;
let query = query.to_lowercase();
if let Some(item) = releases.items.iter().find(|item| item.version == query) {
return Ok(item.clone());
}
if let Some(item) = releases
.items
.iter()
.find(|item| item.version.to_lowercase().contains(&query))
{
return Ok(item.clone());
}
if let Some(item) = releases
.items
.iter()
.find(|item| item.name.to_lowercase().contains(&query))
{
return Ok(item.clone());
}
if let Some(item) = releases
.items
.iter()
.find(|item| item.build.to_lowercase().contains(&query))
{
return Ok(item.clone());
}
self.find_by_channel_query(&releases.items, &query)
}
fn load_cached_releases(
&self,
cache_path: &PathBuf,
) -> Result<Option<AndroidStudioReleasesList>, AstudiosError> {
if !cache_path.exists() {
return Ok(None);
}
let metadata = fs::metadata(cache_path)?;
let modified = metadata.modified()?;
let age = SystemTime::now().duration_since(modified)?;
if age < Duration::from_secs(Config::CACHE_DURATION_SECS) {
let data = fs::read_to_string(cache_path)?;
let content: AndroidStudioReleasesList = serde_json::from_str(&data)?;
return Ok(Some(content));
}
Ok(None)
}
fn save_releases_to_cache(
&self,
cache_path: &PathBuf,
content: &AndroidStudioReleasesList,
) -> Result<(), AstudiosError> {
let data = serde_json::to_string_pretty(content)?;
fs::write(cache_path, data)?;
Ok(())
}
fn find_by_channel_query(
&self,
items: &[AndroidStudio],
query: &str,
) -> Result<AndroidStudio, AstudiosError> {
let parts: Vec<&str> = query.split_whitespace().collect();
if parts.len() >= 2 {
let version_part = parts[0];
let channel_part = parts[1].to_lowercase();
if let Some(item) = items.iter().find(|item| {
item.version.contains(version_part) && item.channel.to_lowercase() == channel_part
}) {
return Ok(item.clone());
}
}
Err(AstudiosError::VersionNotFound(format!(
"Version '{query}' not found. Use 'astudios list' to see available versions."
)))
}
pub fn filter_by_channel(
&self,
releases: AndroidStudioReleasesList,
release_only: bool,
beta_only: bool,
canary_only: bool,
) -> Vec<AndroidStudio> {
let mut items = releases.items;
if release_only {
items.retain(|item| item.is_release());
}
if beta_only {
items.retain(|item| item.is_beta());
}
if canary_only {
items.retain(|item| item.is_canary());
}
items
}
pub fn filter_by_current_platform(&self, releases: Vec<AndroidStudio>) -> Vec<AndroidStudio> {
releases
.into_iter()
.filter(|item| item.get_platform_download().is_some())
.collect()
}
pub fn get_current_platform_name() -> &'static str {
"macOS"
}
}