|
@@ -7,6 +7,7 @@ import asyncio
|
|
|
import aiohttp
|
|
import aiohttp
|
|
|
import json
|
|
import json
|
|
|
import os
|
|
import os
|
|
|
|
|
+import time
|
|
|
from pathlib import Path
|
|
from pathlib import Path
|
|
|
from typing import Dict, Optional
|
|
from typing import Dict, Optional
|
|
|
import logging
|
|
import logging
|
|
@@ -19,6 +20,11 @@ class VersionManager:
|
|
|
self.repo_name = "dune-weaver"
|
|
self.repo_name = "dune-weaver"
|
|
|
self.github_api_url = f"https://api.github.com/repos/{self.repo_owner}/{self.repo_name}"
|
|
self.github_api_url = f"https://api.github.com/repos/{self.repo_owner}/{self.repo_name}"
|
|
|
self._current_version = None
|
|
self._current_version = None
|
|
|
|
|
+
|
|
|
|
|
+ # Caching for GitHub API to avoid rate limits and slow requests
|
|
|
|
|
+ self._latest_release_cache = None
|
|
|
|
|
+ self._cache_timestamp = None
|
|
|
|
|
+ self._cache_duration = 3600 # Cache for 1 hour (in seconds)
|
|
|
|
|
|
|
|
async def get_current_version(self) -> str:
|
|
async def get_current_version(self) -> str:
|
|
|
"""Read current version from VERSION file (async)"""
|
|
"""Read current version from VERSION file (async)"""
|
|
@@ -37,8 +43,18 @@ class VersionManager:
|
|
|
|
|
|
|
|
return self._current_version
|
|
return self._current_version
|
|
|
|
|
|
|
|
- async def get_latest_release(self) -> Dict[str, any]:
|
|
|
|
|
- """Get latest release info from GitHub API"""
|
|
|
|
|
|
|
+ async def get_latest_release(self, force_refresh: bool = False) -> Dict[str, any]:
|
|
|
|
|
+ """Get latest release info from GitHub API with caching"""
|
|
|
|
|
+ # Check if we have a valid cache
|
|
|
|
|
+ current_time = time.time()
|
|
|
|
|
+ if not force_refresh and self._latest_release_cache is not None and self._cache_timestamp is not None:
|
|
|
|
|
+ cache_age = current_time - self._cache_timestamp
|
|
|
|
|
+ if cache_age < self._cache_duration:
|
|
|
|
|
+ logger.debug(f"Returning cached version info (age: {cache_age:.0f}s)")
|
|
|
|
|
+ return self._latest_release_cache
|
|
|
|
|
+
|
|
|
|
|
+ # Cache miss or expired - fetch from GitHub
|
|
|
|
|
+ logger.info("Fetching latest release from GitHub API")
|
|
|
try:
|
|
try:
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with aiohttp.ClientSession() as session:
|
|
|
async with session.get(
|
|
async with session.get(
|
|
@@ -47,7 +63,7 @@ class VersionManager:
|
|
|
) as response:
|
|
) as response:
|
|
|
if response.status == 200:
|
|
if response.status == 200:
|
|
|
data = await response.json()
|
|
data = await response.json()
|
|
|
- return {
|
|
|
|
|
|
|
+ release_data = {
|
|
|
"version": data.get("tag_name", "").lstrip("v"),
|
|
"version": data.get("tag_name", "").lstrip("v"),
|
|
|
"name": data.get("name", ""),
|
|
"name": data.get("name", ""),
|
|
|
"published_at": data.get("published_at", ""),
|
|
"published_at": data.get("published_at", ""),
|
|
@@ -55,20 +71,30 @@ class VersionManager:
|
|
|
"body": data.get("body", ""),
|
|
"body": data.get("body", ""),
|
|
|
"prerelease": data.get("prerelease", False)
|
|
"prerelease": data.get("prerelease", False)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ # Update cache
|
|
|
|
|
+ self._latest_release_cache = release_data
|
|
|
|
|
+ self._cache_timestamp = current_time
|
|
|
|
|
+ logger.info(f"Cached new release info: {release_data.get('version')}")
|
|
|
|
|
+
|
|
|
|
|
+ return release_data
|
|
|
elif response.status == 404:
|
|
elif response.status == 404:
|
|
|
# No releases found
|
|
# No releases found
|
|
|
logger.info("No releases found on GitHub")
|
|
logger.info("No releases found on GitHub")
|
|
|
return None
|
|
return None
|
|
|
else:
|
|
else:
|
|
|
logger.warning(f"GitHub API returned status {response.status}")
|
|
logger.warning(f"GitHub API returned status {response.status}")
|
|
|
- return None
|
|
|
|
|
-
|
|
|
|
|
|
|
+ # Return cached data if available, even if stale
|
|
|
|
|
+ return self._latest_release_cache
|
|
|
|
|
+
|
|
|
except asyncio.TimeoutError:
|
|
except asyncio.TimeoutError:
|
|
|
logger.warning("Timeout while fetching latest release from GitHub")
|
|
logger.warning("Timeout while fetching latest release from GitHub")
|
|
|
- return None
|
|
|
|
|
|
|
+ # Return cached data if available
|
|
|
|
|
+ return self._latest_release_cache
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.error(f"Error fetching latest release: {e}")
|
|
logger.error(f"Error fetching latest release: {e}")
|
|
|
- return None
|
|
|
|
|
|
|
+ # Return cached data if available
|
|
|
|
|
+ return self._latest_release_cache
|
|
|
|
|
|
|
|
def compare_versions(self, version1: str, version2: str) -> int:
|
|
def compare_versions(self, version1: str, version2: str) -> int:
|
|
|
"""Compare two semantic versions. Returns -1, 0, or 1"""
|
|
"""Compare two semantic versions. Returns -1, 0, or 1"""
|
|
@@ -93,11 +119,15 @@ class VersionManager:
|
|
|
logger.warning(f"Invalid version format: {version1} vs {version2}")
|
|
logger.warning(f"Invalid version format: {version1} vs {version2}")
|
|
|
return 0
|
|
return 0
|
|
|
|
|
|
|
|
- async def get_version_info(self) -> Dict[str, any]:
|
|
|
|
|
- """Get complete version information"""
|
|
|
|
|
|
|
+ async def get_version_info(self, force_refresh: bool = False) -> Dict[str, any]:
|
|
|
|
|
+ """Get complete version information
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ force_refresh: If True, bypass cache and fetch from GitHub API
|
|
|
|
|
+ """
|
|
|
current = await self.get_current_version()
|
|
current = await self.get_current_version()
|
|
|
- latest_release = await self.get_latest_release()
|
|
|
|
|
-
|
|
|
|
|
|
|
+ latest_release = await self.get_latest_release(force_refresh=force_refresh)
|
|
|
|
|
+
|
|
|
if latest_release:
|
|
if latest_release:
|
|
|
latest = latest_release["version"]
|
|
latest = latest_release["version"]
|
|
|
comparison = self.compare_versions(current, latest)
|
|
comparison = self.compare_versions(current, latest)
|
|
@@ -105,7 +135,7 @@ class VersionManager:
|
|
|
else:
|
|
else:
|
|
|
latest = current # Fallback if no releases found
|
|
latest = current # Fallback if no releases found
|
|
|
update_available = False
|
|
update_available = False
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return {
|
|
return {
|
|
|
"current": current,
|
|
"current": current,
|
|
|
"latest": latest,
|
|
"latest": latest,
|
|
@@ -113,5 +143,11 @@ class VersionManager:
|
|
|
"latest_release": latest_release
|
|
"latest_release": latest_release
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ def clear_cache(self):
|
|
|
|
|
+ """Clear the cached version data"""
|
|
|
|
|
+ self._latest_release_cache = None
|
|
|
|
|
+ self._cache_timestamp = None
|
|
|
|
|
+ logger.info("Version cache cleared")
|
|
|
|
|
+
|
|
|
# Global instance
|
|
# Global instance
|
|
|
version_manager = VersionManager()
|
|
version_manager = VersionManager()
|