import json
import logging
from datetime import datetime
from pathlib import Path
from fastapi.responses import JSONResponse
from fastapi import HTTPException
from ..models import PluginEntry, PluginManifest
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
[docs]
async def create_distributable_entry(plugin: PluginEntry) -> dict:
"""Transform plugin entry to distributable format"""
try:
# Get metadata from main manifest using display name
metadata = await get_installed_plugin_metadata(plugin.name)
# Get the remote_source from plugin data
if not plugin.remote_source and plugin.source_path:
# Extract from source_path if needed
plugin_id = Path(plugin.source_path).parent.name
remote_source = f"runvnc/{plugin_id}"
else:
remote_source = plugin.remote_source or ''
return {
'name': plugin.name,
'version': plugin.version,
'description': plugin.description,
'source': 'github',
'github_url': f"https://github.com/{remote_source}",
'commands': metadata['commands'],
'services': metadata['services'],
'dependencies': metadata['dependencies'],
'added_at': datetime.now().isoformat()
}
except Exception as e:
logger.error(f"Error creating distributable entry: {str(e)}")
raise
[docs]
async def add_plugin(INDEX_DIR: Path, index_name: str, plugin: PluginEntry):
"""Add a plugin to an index"""
try:
logger.debug(f"Adding plugin: {plugin.name} to index: {index_name}")
logger.debug(f"Plugin data: {plugin.dict()}")
index_dir = INDEX_DIR / index_name
index_file = index_dir / 'index.json'
if not index_file.exists():
return JSONResponse({'success': False, 'message': 'Index not found'})
# Early validation - check for required GitHub info
has_github_info = (
plugin.remote_source or
plugin.github_url or
getattr(plugin, 'metadata', {}).get('github_url')
)
if not has_github_info:
return JSONResponse({
'success': False,
'message': 'Plugin missing GitHub repository information. Cannot add to index.'
})
# Check if plugin is local
if plugin.source == 'local':
return JSONResponse({
'success': False,
'message': 'Local plugins cannot be added to an index'
})
with open(index_file, 'r') as f:
index_data = json.load(f)
if any(p['name'] == plugin.name for p in index_data['plugins']):
return JSONResponse({'success': False, 'message': 'Plugin already in index'})
# Transform to distributable format
try:
plugin_data = await create_distributable_entry(plugin)
except Exception as e:
return JSONResponse({
'success': False,
'message': f'Failed to create distributable plugin entry: {str(e)}'
})
index_data['plugins'].append(plugin_data)
with open(index_file, 'w') as f:
json.dump(index_data, f, indent=2)
return JSONResponse({'success': True, 'data': index_data})
except Exception as e:
logger.error(f"Error adding plugin: {str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
[docs]
async def remove_plugin(INDEX_DIR: Path, index_name: str, plugin_name: str):
"""Remove a plugin from an index"""
try:
index_dir = INDEX_DIR / index_name
index_file = index_dir / 'index.json'
if not index_file.exists():
return JSONResponse({'success': False, 'message': 'Index not found'})
with open(index_file, 'r') as f:
index_data = json.load(f)
# Remove from index data
index_data['plugins'] = [p for p in index_data['plugins'] if p['name'] != plugin_name]
with open(index_file, 'w') as f:
json.dump(index_data, f, indent=2)
return JSONResponse({'success': True, 'data': index_data})
except Exception as e:
logger.error(f"Error removing plugin: {str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))