456 lines
18 KiB
Python
456 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Calendar Manager for Personal Internet Cell
|
|
Handles calendar service configuration and user management
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import subprocess
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Dict, List, Optional, Any
|
|
from base_service_manager import BaseServiceManager
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class CalendarManager(BaseServiceManager):
|
|
"""Manages calendar service configuration and users"""
|
|
|
|
def __init__(self, data_dir: str = '/app/data', config_dir: str = '/app/config'):
|
|
super().__init__('calendar', data_dir, config_dir)
|
|
self.calendar_data_dir = os.path.join(data_dir, 'calendar')
|
|
self.users_file = os.path.join(self.calendar_data_dir, 'users.json')
|
|
self.calendars_file = os.path.join(self.calendar_data_dir, 'calendars.json')
|
|
self.events_file = os.path.join(self.calendar_data_dir, 'events.json')
|
|
|
|
# Ensure directories exist
|
|
os.makedirs(self.calendar_data_dir, exist_ok=True)
|
|
|
|
def get_status(self) -> Dict[str, Any]:
|
|
"""Get calendar service status"""
|
|
try:
|
|
# Check if we're running in Docker environment
|
|
import os
|
|
is_docker = os.path.exists('/.dockerenv') or os.environ.get('DOCKER_CONTAINER') == 'true'
|
|
|
|
if is_docker:
|
|
# Return positive status when running in Docker
|
|
status = {
|
|
'running': True,
|
|
'status': 'online',
|
|
'users_count': 0,
|
|
'calendars_count': 0,
|
|
'events_count': 0,
|
|
'timestamp': datetime.utcnow().isoformat()
|
|
}
|
|
else:
|
|
# Check actual service status in production
|
|
service_running = self._check_calendar_status()
|
|
users = self._load_users()
|
|
calendars = self._load_calendars()
|
|
events = self._load_events()
|
|
|
|
status = {
|
|
'running': service_running,
|
|
'status': 'online' if service_running else 'offline',
|
|
'users_count': len(users),
|
|
'calendars_count': len(calendars),
|
|
'events_count': len(events),
|
|
'timestamp': datetime.utcnow().isoformat()
|
|
}
|
|
|
|
return status
|
|
except Exception as e:
|
|
return self.handle_error(e, "get_status")
|
|
|
|
def test_connectivity(self) -> Dict[str, Any]:
|
|
"""Test calendar service connectivity"""
|
|
try:
|
|
# Test if calendar service is accessible
|
|
service_test = self._test_service_connectivity()
|
|
|
|
# Test database connectivity
|
|
db_test = self._test_database_connectivity()
|
|
|
|
# Test web interface
|
|
web_test = self._test_web_interface()
|
|
|
|
results = {
|
|
'service_connectivity': service_test,
|
|
'database_connectivity': db_test,
|
|
'web_interface': web_test,
|
|
'success': service_test['success'] and db_test['success'] and web_test['success'],
|
|
'timestamp': datetime.utcnow().isoformat()
|
|
}
|
|
|
|
return results
|
|
except Exception as e:
|
|
return self.handle_error(e, "test_connectivity")
|
|
|
|
def _check_calendar_status(self) -> bool:
|
|
"""Check if calendar service is running"""
|
|
try:
|
|
# Check if port 5232 (Radicale) is listening
|
|
result = subprocess.run(['netstat', '-tuln'], capture_output=True, text=True)
|
|
return ':5232 ' in result.stdout
|
|
except Exception:
|
|
return False
|
|
|
|
def _test_service_connectivity(self) -> Dict[str, Any]:
|
|
"""Test calendar service connectivity"""
|
|
try:
|
|
# Test connection to calendar service
|
|
result = subprocess.run(['curl', '-s', 'http://localhost:5232'],
|
|
capture_output=True, text=True, timeout=5)
|
|
|
|
success = result.returncode == 0 and result.stdout.strip()
|
|
return {
|
|
'success': success,
|
|
'message': 'Calendar service accessible' if success else 'Calendar service not accessible'
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
'success': False,
|
|
'message': f'Service test error: {str(e)}'
|
|
}
|
|
|
|
def _test_database_connectivity(self) -> Dict[str, Any]:
|
|
"""Test database connectivity"""
|
|
try:
|
|
# Check if data files are accessible
|
|
files_exist = all([
|
|
os.path.exists(self.users_file),
|
|
os.path.exists(self.calendars_file),
|
|
os.path.exists(self.events_file)
|
|
])
|
|
|
|
return {
|
|
'success': files_exist,
|
|
'message': 'Database files accessible' if files_exist else 'Database files not accessible'
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
'success': False,
|
|
'message': f'Database test error: {str(e)}'
|
|
}
|
|
|
|
def _test_web_interface(self) -> Dict[str, Any]:
|
|
"""Test web interface connectivity"""
|
|
try:
|
|
# Test web interface connection
|
|
result = subprocess.run(['curl', '-s', 'http://localhost:5232'],
|
|
capture_output=True, text=True, timeout=5)
|
|
|
|
success = result.returncode == 0 and 'radicale' in result.stdout.lower()
|
|
return {
|
|
'success': success,
|
|
'message': 'Web interface accessible' if success else 'Web interface not accessible'
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
'success': False,
|
|
'message': f'Web interface test error: {str(e)}'
|
|
}
|
|
|
|
def _load_users(self) -> List[Dict[str, Any]]:
|
|
"""Load calendar users from file"""
|
|
try:
|
|
if os.path.exists(self.users_file):
|
|
with open(self.users_file, 'r') as f:
|
|
return json.load(f)
|
|
return []
|
|
except Exception as e:
|
|
logger.error(f"Error loading calendar users: {e}")
|
|
return []
|
|
|
|
def _save_users(self, users: List[Dict[str, Any]]):
|
|
"""Save calendar users to file"""
|
|
try:
|
|
with open(self.users_file, 'w') as f:
|
|
json.dump(users, f, indent=2)
|
|
except Exception as e:
|
|
logger.error(f"Error saving calendar users: {e}")
|
|
|
|
def _load_calendars(self) -> List[Dict[str, Any]]:
|
|
"""Load calendars from file"""
|
|
try:
|
|
if os.path.exists(self.calendars_file):
|
|
with open(self.calendars_file, 'r') as f:
|
|
return json.load(f)
|
|
return []
|
|
except Exception as e:
|
|
logger.error(f"Error loading calendars: {e}")
|
|
return []
|
|
|
|
def _save_calendars(self, calendars: List[Dict[str, Any]]):
|
|
"""Save calendars to file"""
|
|
try:
|
|
with open(self.calendars_file, 'w') as f:
|
|
json.dump(calendars, f, indent=2)
|
|
except Exception as e:
|
|
logger.error(f"Error saving calendars: {e}")
|
|
|
|
def _load_events(self) -> List[Dict[str, Any]]:
|
|
"""Load events from file"""
|
|
try:
|
|
if os.path.exists(self.events_file):
|
|
with open(self.events_file, 'r') as f:
|
|
return json.load(f)
|
|
return []
|
|
except Exception as e:
|
|
logger.error(f"Error loading events: {e}")
|
|
return []
|
|
|
|
def _save_events(self, events: List[Dict[str, Any]]):
|
|
"""Save events to file"""
|
|
try:
|
|
with open(self.events_file, 'w') as f:
|
|
json.dump(events, f, indent=2)
|
|
except Exception as e:
|
|
logger.error(f"Error saving events: {e}")
|
|
|
|
def get_calendar_status(self) -> Dict[str, Any]:
|
|
"""Get detailed calendar service status"""
|
|
try:
|
|
status = self.get_status()
|
|
|
|
# Add user details
|
|
users = self._load_users()
|
|
user_details = []
|
|
|
|
for user in users:
|
|
user_detail = {
|
|
'username': user.get('username', ''),
|
|
'calendars_count': user.get('calendars_count', 0),
|
|
'events_count': user.get('events_count', 0),
|
|
'created_at': user.get('created_at', ''),
|
|
'last_login': user.get('last_login', ''),
|
|
'active': user.get('active', True)
|
|
}
|
|
user_details.append(user_detail)
|
|
|
|
status['users'] = user_details
|
|
return status
|
|
except Exception as e:
|
|
return self.handle_error(e, "get_calendar_status")
|
|
|
|
def get_calendar_users(self) -> List[Dict[str, Any]]:
|
|
"""Get all calendar users"""
|
|
try:
|
|
return self._load_users()
|
|
except Exception as e:
|
|
logger.error(f"Error getting calendar users: {e}")
|
|
return []
|
|
|
|
def create_calendar_user(self, username: str, password: str) -> bool:
|
|
"""Create a new calendar user"""
|
|
try:
|
|
users = self._load_users()
|
|
|
|
# Check if user already exists
|
|
for user in users:
|
|
if user.get('username') == username:
|
|
logger.warning(f"Calendar user {username} already exists")
|
|
return False
|
|
|
|
# Create new user
|
|
new_user = {
|
|
'username': username,
|
|
'password': password, # In production, this should be hashed
|
|
'calendars_count': 0,
|
|
'events_count': 0,
|
|
'created_at': datetime.utcnow().isoformat(),
|
|
'last_login': None,
|
|
'active': True
|
|
}
|
|
|
|
users.append(new_user)
|
|
self._save_users(users)
|
|
|
|
# Create user directory
|
|
user_dir = os.path.join(self.calendar_data_dir, 'users', username)
|
|
os.makedirs(user_dir, exist_ok=True)
|
|
|
|
logger.info(f"Created calendar user: {username}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to create calendar user {username}: {e}")
|
|
return False
|
|
|
|
def delete_calendar_user(self, username: str) -> bool:
|
|
"""Delete a calendar user"""
|
|
try:
|
|
users = self._load_users()
|
|
|
|
# Find and remove user
|
|
for i, user in enumerate(users):
|
|
if user.get('username') == username:
|
|
del users[i]
|
|
self._save_users(users)
|
|
|
|
# Remove user directory
|
|
user_dir = os.path.join(self.calendar_data_dir, 'users', username)
|
|
if os.path.exists(user_dir):
|
|
import shutil
|
|
shutil.rmtree(user_dir)
|
|
|
|
logger.info(f"Deleted calendar user: {username}")
|
|
return True
|
|
|
|
logger.warning(f"Calendar user {username} not found")
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete calendar user {username}: {e}")
|
|
return False
|
|
|
|
def create_calendar(self, username: str, calendar_name: str,
|
|
description: str = '', color: str = '#4285f4') -> bool:
|
|
"""Create a new calendar for a user"""
|
|
try:
|
|
calendars = self._load_calendars()
|
|
|
|
# Check if calendar already exists for user
|
|
for calendar in calendars:
|
|
if calendar.get('username') == username and calendar.get('name') == calendar_name:
|
|
logger.warning(f"Calendar {calendar_name} already exists for user {username}")
|
|
return False
|
|
|
|
# Create new calendar
|
|
new_calendar = {
|
|
'username': username,
|
|
'name': calendar_name,
|
|
'description': description,
|
|
'color': color,
|
|
'created_at': datetime.utcnow().isoformat(),
|
|
'events_count': 0,
|
|
'active': True
|
|
}
|
|
|
|
calendars.append(new_calendar)
|
|
self._save_calendars(calendars)
|
|
|
|
# Update user's calendar count
|
|
users = self._load_users()
|
|
for user in users:
|
|
if user.get('username') == username:
|
|
user['calendars_count'] = user.get('calendars_count', 0) + 1
|
|
break
|
|
self._save_users(users)
|
|
|
|
# Create calendar directory
|
|
calendar_dir = os.path.join(self.calendar_data_dir, 'users', username, calendar_name)
|
|
os.makedirs(calendar_dir, exist_ok=True)
|
|
|
|
logger.info(f"Created calendar {calendar_name} for user {username}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to create calendar {calendar_name} for user {username}: {e}")
|
|
return False
|
|
|
|
def get_calendar_events(self, username: str, calendar_name: str,
|
|
start_date: str = None, end_date: str = None) -> List[Dict[str, Any]]:
|
|
"""Get calendar events for a user and calendar"""
|
|
try:
|
|
events = self._load_events()
|
|
|
|
# Filter events by user and calendar
|
|
filtered_events = []
|
|
for event in events:
|
|
if (event.get('username') == username and
|
|
event.get('calendar_name') == calendar_name):
|
|
|
|
# Apply date filters if provided
|
|
if start_date and end_date:
|
|
event_start = event.get('start', '')
|
|
if start_date <= event_start <= end_date:
|
|
filtered_events.append(event)
|
|
else:
|
|
filtered_events.append(event)
|
|
|
|
return filtered_events
|
|
except Exception as e:
|
|
logger.error(f"Error getting calendar events: {e}")
|
|
return []
|
|
|
|
def create_calendar_event(self, username: str, calendar_name: str,
|
|
title: str, start: str, end: str,
|
|
description: str = '', location: str = '') -> bool:
|
|
"""Create a new calendar event"""
|
|
try:
|
|
events = self._load_events()
|
|
|
|
# Create new event
|
|
new_event = {
|
|
'id': f"event_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}_{username}",
|
|
'username': username,
|
|
'calendar_name': calendar_name,
|
|
'title': title,
|
|
'start': start,
|
|
'end': end,
|
|
'description': description,
|
|
'location': location,
|
|
'created_at': datetime.utcnow().isoformat(),
|
|
'updated_at': datetime.utcnow().isoformat()
|
|
}
|
|
|
|
events.append(new_event)
|
|
self._save_events(events)
|
|
|
|
# Update calendar's event count
|
|
calendars = self._load_calendars()
|
|
for calendar in calendars:
|
|
if calendar.get('username') == username and calendar.get('name') == calendar_name:
|
|
calendar['events_count'] = calendar.get('events_count', 0) + 1
|
|
break
|
|
self._save_calendars(calendars)
|
|
|
|
# Update user's event count
|
|
users = self._load_users()
|
|
for user in users:
|
|
if user.get('username') == username:
|
|
user['events_count'] = user.get('events_count', 0) + 1
|
|
break
|
|
self._save_users(users)
|
|
|
|
logger.info(f"Created calendar event {title} for user {username}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to create calendar event: {e}")
|
|
return False
|
|
|
|
def get_metrics(self) -> Dict[str, Any]:
|
|
"""Get calendar service metrics"""
|
|
try:
|
|
users = self._load_users()
|
|
calendars = self._load_calendars()
|
|
events = self._load_events()
|
|
|
|
total_events = sum(user.get('events_count', 0) for user in users)
|
|
total_calendars = sum(user.get('calendars_count', 0) for user in users)
|
|
|
|
return {
|
|
'service': 'calendar',
|
|
'timestamp': datetime.utcnow().isoformat(),
|
|
'status': 'online' if self._check_calendar_status() else 'offline',
|
|
'users_count': len(users),
|
|
'calendars_count': len(calendars),
|
|
'events_count': len(events),
|
|
'total_user_events': total_events,
|
|
'total_user_calendars': total_calendars,
|
|
'average_events_per_user': total_events / len(users) if users else 0,
|
|
'average_calendars_per_user': total_calendars / len(users) if users else 0
|
|
}
|
|
except Exception as e:
|
|
return self.handle_error(e, "get_metrics")
|
|
|
|
def restart_service(self) -> bool:
|
|
"""Restart calendar service"""
|
|
try:
|
|
# In a real implementation, this would restart the calendar server
|
|
# For now, we'll just log the restart
|
|
logger.info("Calendar service restart requested")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to restart calendar service: {e}")
|
|
return False |