Add GitHub version checker to Stream-Mapparr plugin
Implements automatic version checking that displays update status on the plugin settings page. Features: - Fetches latest release version from GitHub using stdlib (urllib.request) - Displays version status at top of settings page (Update Available / Up to Date) - Caches version check results for 24 hours to minimize API calls - Re-checks automatically when plugin version changes - Fails gracefully if network unavailable or GitHub API errors occur Technical Details: - Uses only Python standard library (no requests dependency) - Version check triggered via fields property (runs when settings opened) - Cache stored in /data/stream_mapparr_version_check.json - Timeout set to 5 seconds for API requests - All errors logged at debug level to avoid cluttering logs
This commit is contained in:
@@ -9,7 +9,9 @@ import csv
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
from datetime import datetime
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
# Django model imports
|
# Django model imports
|
||||||
@@ -37,8 +39,20 @@ class Plugin:
|
|||||||
@property
|
@property
|
||||||
def fields(self):
|
def fields(self):
|
||||||
"""Dynamically generate settings fields including channel database selection."""
|
"""Dynamically generate settings fields including channel database selection."""
|
||||||
|
# Check for version updates (with caching)
|
||||||
|
version_info = {'message': f"Current version: {self.version}", 'status': 'unknown'}
|
||||||
|
try:
|
||||||
|
version_info = self._check_version_update()
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.debug(f"[Stream-Mapparr] Error checking version update: {e}")
|
||||||
|
|
||||||
# Static fields that are always present
|
# Static fields that are always present
|
||||||
static_fields = [
|
static_fields = [
|
||||||
|
{
|
||||||
|
"id": "version_status",
|
||||||
|
"type": "info",
|
||||||
|
"label": version_info['message'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "overwrite_streams",
|
"id": "overwrite_streams",
|
||||||
"label": "🔄 Overwrite Existing Streams",
|
"label": "🔄 Overwrite Existing Streams",
|
||||||
@@ -240,6 +254,7 @@ class Plugin:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.processed_data_file = "/data/stream_mapparr_processed.json"
|
self.processed_data_file = "/data/stream_mapparr_processed.json"
|
||||||
|
self.version_check_cache_file = "/data/stream_mapparr_version_check.json"
|
||||||
self.loaded_channels = []
|
self.loaded_channels = []
|
||||||
self.loaded_streams = []
|
self.loaded_streams = []
|
||||||
self.channel_stream_matches = []
|
self.channel_stream_matches = []
|
||||||
@@ -247,6 +262,158 @@ class Plugin:
|
|||||||
|
|
||||||
LOGGER.info(f"[Stream-Mapparr] {self.name} Plugin v{self.version} initialized")
|
LOGGER.info(f"[Stream-Mapparr] {self.name} Plugin v{self.version} initialized")
|
||||||
|
|
||||||
|
def _get_latest_version(self, owner, repo):
|
||||||
|
"""
|
||||||
|
Fetches the latest release tag name from GitHub using only Python's standard library.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
owner (str): GitHub repository owner
|
||||||
|
repo (str): GitHub repository name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Latest version tag or error message
|
||||||
|
"""
|
||||||
|
url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
|
||||||
|
|
||||||
|
# Add a user-agent to avoid potential 403 Forbidden errors
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Dispatcharr-Plugin-Version-Checker'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a request object with headers
|
||||||
|
req = urllib.request.Request(url, headers=headers)
|
||||||
|
|
||||||
|
# Make the request and open the URL with a timeout
|
||||||
|
with urllib.request.urlopen(req, timeout=5) as response:
|
||||||
|
# Read the response and decode it as UTF-8
|
||||||
|
data = response.read().decode('utf-8')
|
||||||
|
|
||||||
|
# Parse the JSON string
|
||||||
|
json_data = json.loads(data)
|
||||||
|
|
||||||
|
# Get the tag name
|
||||||
|
latest_version = json_data.get("tag_name")
|
||||||
|
|
||||||
|
if latest_version:
|
||||||
|
return latest_version
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
except urllib.error.HTTPError as http_err:
|
||||||
|
if http_err.code == 404:
|
||||||
|
LOGGER.debug(f"[Stream-Mapparr] GitHub repo not found or has no releases: {http_err}")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
LOGGER.debug(f"[Stream-Mapparr] HTTP error checking version: {http_err.code}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
# Catch other errors like timeouts
|
||||||
|
LOGGER.debug(f"[Stream-Mapparr] Error checking version: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _check_version_update(self):
|
||||||
|
"""
|
||||||
|
Check if a new version is available on GitHub.
|
||||||
|
Uses caching to limit checks to once per day or when the plugin version changes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Contains 'message' and 'status' keys for display
|
||||||
|
"""
|
||||||
|
current_version = self.version
|
||||||
|
github_owner = "PiratesIRC"
|
||||||
|
github_repo = "Stream-Mapparr"
|
||||||
|
|
||||||
|
# Default response
|
||||||
|
result = {
|
||||||
|
'message': f"Current version: {current_version}",
|
||||||
|
'status': 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Load cache if it exists
|
||||||
|
cache_data = {}
|
||||||
|
should_check = True
|
||||||
|
|
||||||
|
if os.path.exists(self.version_check_cache_file):
|
||||||
|
try:
|
||||||
|
with open(self.version_check_cache_file, 'r', encoding='utf-8') as f:
|
||||||
|
cache_data = json.load(f)
|
||||||
|
|
||||||
|
# Check if we need to recheck:
|
||||||
|
# 1. Plugin version changed (upgrade/downgrade)
|
||||||
|
# 2. More than 24 hours since last check
|
||||||
|
cached_plugin_version = cache_data.get('plugin_version')
|
||||||
|
last_check_str = cache_data.get('last_check')
|
||||||
|
|
||||||
|
if cached_plugin_version == current_version and last_check_str:
|
||||||
|
# Parse the last check time
|
||||||
|
last_check = datetime.fromisoformat(last_check_str)
|
||||||
|
time_diff = datetime.now() - last_check
|
||||||
|
|
||||||
|
# If less than 24 hours, use cached data
|
||||||
|
if time_diff < timedelta(hours=24):
|
||||||
|
should_check = False
|
||||||
|
latest_version = cache_data.get('latest_version')
|
||||||
|
|
||||||
|
# Compare versions
|
||||||
|
if latest_version and latest_version != current_version:
|
||||||
|
result = {
|
||||||
|
'message': f"🎉 Update available! Current: {current_version} → Latest: {latest_version}",
|
||||||
|
'status': 'update_available'
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
result = {
|
||||||
|
'message': f"✅ You are up to date (v{current_version})",
|
||||||
|
'status': 'up_to_date'
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.debug(f"[Stream-Mapparr] Error reading version cache: {e}")
|
||||||
|
should_check = True
|
||||||
|
|
||||||
|
# Perform the check if needed
|
||||||
|
if should_check:
|
||||||
|
latest_version = self._get_latest_version(github_owner, github_repo)
|
||||||
|
|
||||||
|
# Update cache
|
||||||
|
cache_data = {
|
||||||
|
'plugin_version': current_version,
|
||||||
|
'latest_version': latest_version,
|
||||||
|
'last_check': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.version_check_cache_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(cache_data, f, indent=2)
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.debug(f"[Stream-Mapparr] Error writing version cache: {e}")
|
||||||
|
|
||||||
|
# Compare versions
|
||||||
|
if latest_version and latest_version != current_version:
|
||||||
|
result = {
|
||||||
|
'message': f"🎉 Update available! Current: {current_version} → Latest: {latest_version}",
|
||||||
|
'status': 'update_available'
|
||||||
|
}
|
||||||
|
elif latest_version:
|
||||||
|
result = {
|
||||||
|
'message': f"✅ You are up to date (v{current_version})",
|
||||||
|
'status': 'up_to_date'
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
result = {
|
||||||
|
'message': f"Current version: {current_version} (unable to check for updates)",
|
||||||
|
'status': 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.debug(f"[Stream-Mapparr] Error in version check: {e}")
|
||||||
|
result = {
|
||||||
|
'message': f"Current version: {current_version} (update check failed)",
|
||||||
|
'status': 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def _get_channel_databases(self):
|
def _get_channel_databases(self):
|
||||||
"""
|
"""
|
||||||
Scan for channel database files and return metadata for each.
|
Scan for channel database files and return metadata for each.
|
||||||
|
|||||||
Reference in New Issue
Block a user