""" Subscription management module for scientific-surfing. Handles subscription operations with persistent storage. """ import requests from scientific_surfing.storage import StorageManager class SubscriptionManager: """Manages clash RSS subscriptions with persistent storage.""" storage: StorageManager = None def __init__(self, storage: StorageManager): self.storage = storage self.subscriptions_data = self.storage.load_subscriptions() self.config = self.storage.load_config() # Create subscriptions directory for storing downloaded files self.subscriptions_dir = self.storage.config_dir / "subscriptions" self.subscriptions_dir.mkdir(exist_ok=True) def add_subscription(self, url: str, name: str) -> None: """Add a new subscription.""" subscription = self.subscriptions_data.add_subscription(name, url) if self.storage.save_subscriptions(self.subscriptions_data): self.refresh_subscription(name) print(f"✅ Added subscription: {name} -> {url}") else: print("❌ Failed to save subscription") def refresh_subscription(self, name: str) -> None: """Refresh a subscription by downloading from URL.""" if name not in self.subscriptions_data.subscriptions: print(f"❌ Subscription '{name}' not found") return subscription = self.subscriptions_data.subscriptions[name] url = subscription.url print(f"🔄 Refreshing subscription: {name}") try: # Download the subscription content headers = { 'User-Agent': self.config.default_user_agent } timeout = self.config.timeout_seconds response = requests.get(url, headers=headers, timeout=timeout) response.raise_for_status() # File path without timestamp file_path = self.subscriptions_dir / f"{name}.yml" # Handle existing file by renaming with creation date if file_path.exists(): # Get creation time of existing file stat = file_path.stat() try: # Try st_birthtime first (macOS/Unix) creation_time = datetime.fromtimestamp(stat.st_birthtime) except AttributeError: # Fallback to st_ctime (Windows) creation_time = datetime.fromtimestamp(stat.st_ctime) backup_name = f"{name}_{creation_time.strftime('%Y%m%d_%H%M%S')}.yml" backup_path = self.subscriptions_dir / backup_name # Rename existing file file_path.rename(backup_path) print(f" 🔄 Backed up existing file to: {backup_name}") # Save the new downloaded content with open(file_path, 'w', encoding='utf-8') as f: f.write(response.text) # Update subscription metadata subscription.last_refresh = datetime.now() subscription.file_path = str(file_path) subscription.file_size = len(response.text) subscription.status_code = response.status_code subscription.content_hash = hash(response.text) subscription.last_error = None if self.storage.save_subscriptions(self.subscriptions_data): print(f"✅ Subscription '{name}' refreshed successfully") print(f" 📁 Saved to: {file_path}") print(f" 📊 Size: {len(response.text)} bytes") else: print("❌ Failed to save subscription metadata") except requests.exceptions.RequestException as e: print(f"❌ Failed to download subscription: {e}") subscription.last_error = str(e) self.storage.save_subscriptions(self.subscriptions_data) except IOError as e: print(f"❌ Failed to save file: {e}") subscription.last_error = str(e) self.storage.save_subscriptions(self.subscriptions_data) def delete_subscription(self, name: str) -> None: """Delete a subscription.""" if self.subscriptions_data.remove_subscription(name): if self.storage.save_subscriptions(self.subscriptions_data): print(f"🗑️ Deleted subscription: {name}") else: print("❌ Failed to delete subscription") else: print(f"❌ Subscription '{name}' not found") def rename_subscription(self, old_name: str, new_name: str) -> None: """Rename a subscription.""" if self.subscriptions_data.rename_subscription(old_name, new_name): if self.storage.save_subscriptions(self.subscriptions_data): print(f"✅ Renamed subscription: {old_name} -> {new_name}") else: print("❌ Failed to rename subscription") else: print(f"❌ Failed to rename subscription: '{old_name}' not found or '{new_name}' already exists") def activate_subscription(self, name: str) -> None: """Activate a subscription.""" if self.subscriptions_data.set_active(name): if self.storage.save_subscriptions(self.subscriptions_data): print(f"✅ Activated subscription: {name}") else: print("❌ Failed to activate subscription") else: print(f"❌ Subscription '{name}' not found") def list_subscriptions(self) -> None: """List all subscriptions.""" if not self.subscriptions_data.subscriptions: print("No subscriptions found") return print("📋 Subscriptions:") for name, subscription in self.subscriptions_data.subscriptions.items(): active_marker = "✅" if subscription.status == 'active' else " " last_refresh_str = "" if subscription.last_refresh: last_refresh_str = f" (last: {subscription.last_refresh.strftime('%Y-%m-%d %H:%M:%S')})" print(f" {active_marker} {name}: {subscription.url} ({subscription.status}){last_refresh_str}") def show_storage_info(self) -> None: """Show storage information.""" info = self.storage.get_storage_info() print("📁 Storage Information:") print(f" Platform: {info['platform']}") print(f" Config Directory: {info['config_dir']}") print(f" Config File: {info['config_file']}") print(f" Subscriptions File: {info['subscriptions_file']}") print(f" Directory Exists: {info['exists']}")