tuanchris 3 місяців тому
батько
коміт
dc0b78f7a6
3 змінених файлів з 76 додано та 23 видалено
  1. 9 5
      main.py
  2. 48 12
      modules/core/version_manager.py
  3. 19 6
      static/js/base.js

+ 9 - 5
main.py

@@ -1977,17 +1977,21 @@ def signal_handler(signum, frame):
         os._exit(0)  # Force exit regardless of other threads
         os._exit(0)  # Force exit regardless of other threads
 
 
 @app.get("/api/version")
 @app.get("/api/version")
-async def get_version_info():
-    """Get current and latest version information"""
+async def get_version_info(force_refresh: bool = False):
+    """Get current and latest version information
+
+    Args:
+        force_refresh: If true, bypass cache and fetch fresh data from GitHub
+    """
     try:
     try:
-        version_info = await version_manager.get_version_info()
+        version_info = await version_manager.get_version_info(force_refresh=force_refresh)
         return JSONResponse(content=version_info)
         return JSONResponse(content=version_info)
     except Exception as e:
     except Exception as e:
         logger.error(f"Error getting version info: {e}")
         logger.error(f"Error getting version info: {e}")
         return JSONResponse(
         return JSONResponse(
             content={
             content={
-                "current": version_manager.get_current_version(),
-                "latest": version_manager.get_current_version(),
+                "current": await version_manager.get_current_version(),
+                "latest": await version_manager.get_current_version(),
                 "update_available": False,
                 "update_available": False,
                 "error": "Unable to check for updates"
                 "error": "Unable to check for updates"
             },
             },

+ 48 - 12
modules/core/version_manager.py

@@ -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()

+ 19 - 6
static/js/base.js

@@ -200,7 +200,12 @@ function connectWebSocket() {
                     const newFile = normalizeFilePath(data.data.current_file);
                     const newFile = normalizeFilePath(data.data.current_file);
                     if (newFile !== currentPreviewFile) {
                     if (newFile !== currentPreviewFile) {
                         currentPreviewFile = newFile;
                         currentPreviewFile = newFile;
-                        loadPlayerPreviewData(data.data.current_file);
+
+                        // Only preload if modal exists on this page (avoids waste on settings/LED pages)
+                        const modal = document.getElementById('playerPreviewModal');
+                        if (modal) {
+                            loadPlayerPreviewData(data.data.current_file);
+                        }
                     }
                     }
                 } else {
                 } else {
                     currentPreviewFile = null;
                     currentPreviewFile = null;
@@ -290,20 +295,28 @@ async function openPlayerPreviewModal() {
         const canvas = document.getElementById('playerPreviewCanvas');
         const canvas = document.getElementById('playerPreviewCanvas');
         const ctx = canvas.getContext('2d');
         const ctx = canvas.getContext('2d');
         const toggleBtn = document.getElementById('toggle-preview-modal-btn');
         const toggleBtn = document.getElementById('toggle-preview-modal-btn');
-        
+
         // Set static title
         // Set static title
         title.textContent = 'Live Pattern Preview';
         title.textContent = 'Live Pattern Preview';
-        
+
+        // Load preview data on-demand if not already loaded (fallback for edge cases)
+        if (!playerPreviewData && currentPreviewFile) {
+            // Show loading indicator
+            title.textContent = 'Loading pattern...';
+            await loadPlayerPreviewData(`./patterns/${currentPreviewFile}`);
+            title.textContent = 'Live Pattern Preview';
+        }
+
         // Show modal and update toggle button
         // Show modal and update toggle button
         modal.classList.remove('hidden');
         modal.classList.remove('hidden');
 
 
-        
+
         // Setup canvas
         // Setup canvas
         setupPlayerPreviewCanvas(ctx);
         setupPlayerPreviewCanvas(ctx);
-        
+
         // Draw initial state
         // Draw initial state
         drawPlayerPreview(ctx, targetProgress);
         drawPlayerPreview(ctx, targetProgress);
-        
+
     } catch (error) {
     } catch (error) {
         console.error(`Error opening player preview modal: ${error.message}`);
         console.error(`Error opening player preview modal: ${error.message}`);
         showStatusMessage('Failed to load pattern for animation', 'error');
         showStatusMessage('Failed to load pattern for animation', 'error');