import platform import subprocess import tempfile from typing import Dict, Any from openspace.utils.logging import Logger logger = Logger.get_logger(__name__) platform_name = platform.system() class FeatureChecker: def __init__(self, platform_adapter=None, accessibility_helper=None): self.platform_adapter = platform_adapter self.accessibility_helper = accessibility_helper self.platform = platform_name self._cache = {} def check_screenshot_available(self, use_cache: bool = True) -> bool: if use_cache and 'screenshot' in self._cache: return self._cache['screenshot'] try: import pyautogui from PIL import Image size = pyautogui.size() result = size.width > 0 and size.height > 0 self._cache['screenshot'] = result logger.info(f"Screenshot check: {'available' if result else 'unavailable'}") return result except ImportError as e: logger.warning(f"Screenshot unavailable - missing dependency: {e}") self._cache['screenshot'] = False return False except Exception as e: logger.error(f"Screenshot check failed: {e}") self._cache['screenshot'] = False return False def check_shell_available(self, use_cache: bool = True) -> bool: if use_cache and 'shell' in self._cache: return self._cache['shell'] try: if self.platform == "Windows": cmd = ['cmd', '/c', 'echo', 'test'] else: cmd = ['echo', 'test'] result = subprocess.run( cmd, capture_output=True, timeout=2, text=True ) available = result.returncode == 0 self._cache['shell'] = available logger.info(f"Shell check: {'available' if available else 'unavailable'}") return available except FileNotFoundError as e: logger.warning(f"Shell check failed - command not found: {e}") self._cache['shell'] = False return False except Exception as e: logger.error(f"Shell check failed: {e}") self._cache['shell'] = False return False def check_python_available(self, use_cache: bool = True) -> bool: if use_cache and 'python' in self._cache: return self._cache['python'] python_commands = [] if self.platform == "Windows": python_commands = ['py', 'python', 'python3'] else: python_commands = ['python3', 'python'] for python_cmd in python_commands: try: result = subprocess.run( [python_cmd, '--version'], capture_output=True, timeout=2, text=True ) if result.returncode == 0: version = result.stdout.strip() or result.stderr.strip() self._cache['python'] = True logger.info(f"Python check: available ({python_cmd} - {version})") return True except FileNotFoundError: continue except Exception as e: logger.debug(f"Error testing {python_cmd}: {e}") continue logger.warning("Python check failed - no valid Python interpreter found") self._cache['python'] = False return False def check_file_ops_available(self, use_cache: bool = True) -> bool: if use_cache and 'file_ops' in self._cache: return self._cache['file_ops'] try: with tempfile.NamedTemporaryFile(mode='w+b', delete=True) as tmp: test_data = b'test data' tmp.write(test_data) tmp.flush() tmp.seek(0) read_data = tmp.read() available = read_data == test_data self._cache['file_ops'] = available logger.info(f"File operations check: {'available' if available else 'unavailable'}") return available except PermissionError as e: logger.warning(f"File operations check failed - permission denied: {e}") self._cache['file_ops'] = False return False except Exception as e: logger.error(f"File operations check failed: {e}") self._cache['file_ops'] = False return False def check_window_mgmt_available(self, use_cache: bool = True) -> bool: if use_cache and 'window_mgmt' in self._cache: return self._cache['window_mgmt'] try: if not self.platform_adapter: logger.warning("Window management check failed - no platform adapter loaded") self._cache['window_mgmt'] = False return False required_methods = ['activate_window', 'close_window', 'list_windows'] available_methods = [ method for method in required_methods if hasattr(self.platform_adapter, method) ] available = len(available_methods) > 0 self._cache['window_mgmt'] = available if available: logger.info(f"Window management check: {'available' if available else 'unavailable'} - supported methods: {', '.join(available_methods)}") else: logger.warning(f"Window management check failed - platform adapter missing required methods") return available except Exception as e: logger.error(f"Window management check failed: {e}") self._cache['window_mgmt'] = False return False def check_recording_available(self, use_cache: bool = True) -> bool: if use_cache and 'recording' in self._cache: return self._cache['recording'] try: if not self.platform_adapter: logger.warning("Recording check failed - no platform adapter loaded") self._cache['recording'] = False return False available = ( hasattr(self.platform_adapter, 'start_recording') and hasattr(self.platform_adapter, 'stop_recording') ) self._cache['recording'] = available logger.info(f"Recording check: {'available' if available else 'unavailable'}") return available except Exception as e: logger.error(f"Recording check failed: {e}") self._cache['recording'] = False return False def check_accessibility_available(self, use_cache: bool = True) -> bool: if use_cache and 'accessibility' in self._cache: return self._cache['accessibility'] try: if not self.accessibility_helper: logger.warning("Accessibility check failed - no accessibility helper loaded") self._cache['accessibility'] = False return False available = self.accessibility_helper.is_available() self._cache['accessibility'] = available logger.info(f"Accessibility check: {'available' if available else 'unavailable'}") return available except Exception as e: logger.error(f"Accessibility check failed: {e}") self._cache['accessibility'] = False return False def check_platform_adapter_available(self, use_cache: bool = True) -> bool: if use_cache and 'platform_adapter' in self._cache: return self._cache['platform_adapter'] available = self.platform_adapter is not None self._cache['platform_adapter'] = available logger.info(f"Platform adapter check: {'available' if available else 'unavailable'}") return available def check_all_features(self, use_cache: bool = True) -> Dict[str, bool]: logger.info(f"Checking all features (platform: {self.platform})") results = { 'accessibility': self.check_accessibility_available(use_cache), 'screenshot': self.check_screenshot_available(use_cache), 'recording': self.check_recording_available(use_cache), 'shell': self.check_shell_available(use_cache), 'python': self.check_python_available(use_cache), 'file_ops': self.check_file_ops_available(use_cache), 'window_mgmt': self.check_window_mgmt_available(use_cache), 'platform_adapter': self.check_platform_adapter_available(use_cache), } available_count = sum(1 for v in results.values() if v) total_count = len(results) logger.info(f"Feature check completed: {available_count}/{total_count} features available") return results def clear_cache(self): self._cache.clear() logger.debug("Feature check cache cleared") def get_feature_report(self) -> Dict[str, Any]: results = self.check_all_features() return { 'platform': { 'system': self.platform, 'release': platform.release(), 'version': platform.version(), 'machine': platform.machine(), 'processor': platform.processor(), }, 'features': results, 'summary': { 'total': len(results), 'available': sum(1 for v in results.values() if v), 'unavailable': sum(1 for v in results.values() if not v), } }