""" Utility functions for the Logo Downloader application """ import os import re import json import time from pathlib import Path from typing import List, Optional from urllib.parse import urlparse import logging from services.appconfig import IMAGE_SIGNATURES, MIN_FILE_SIZE, MAX_FILE_SIZE # Setup logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def create_safe_filename(name: str) -> str: """ Create a safe filename from entity name Args: name (str): Entity name Returns: str: Safe filename """ safe_name = re.sub(r'[^\w\s-]', '', name).strip() safe_name = re.sub(r'[-\s]+', '_', safe_name) return safe_name def get_file_extension(url: str) -> str: """ Extract file extension from URL Args: url (str): Image URL Returns: str: File extension """ parsed_url = urlparse(url) extension = os.path.splitext(parsed_url.path)[1] if not extension or extension.lower() not in ['.png', '.jpg', '.jpeg', '.svg', '.webp']: extension = '.png' return extension def is_valid_image_file(filepath: str) -> bool: """ Validate if file is a proper image Args: filepath (str): Path to image file Returns: bool: True if valid image """ try: # Check file exists and size if not os.path.exists(filepath): return False file_size = os.path.getsize(filepath) if file_size < MIN_FILE_SIZE or file_size > MAX_FILE_SIZE: logger.warning(f"Invalid file size: {file_size}") return False # Check image signature with open(filepath, 'rb') as f: header = f.read(12) for signature in IMAGE_SIGNATURES: if header.startswith(signature): return True return False except Exception as e: logger.error(f"Error validating image: {e}") return False def create_directory(path: Path) -> bool: """ Create directory if it doesn't exist Args: path (Path): Directory path Returns: bool: True if successful """ try: path.mkdir(parents=True, exist_ok=True) return True except Exception as e: logger.error(f"Error creating directory {path}: {e}") return False def clean_up_file(filepath: str) -> None: """ Remove file if it exists Args: filepath (str): Path to file to remove """ try: if os.path.exists(filepath): os.remove(filepath) except Exception as e: logger.error(f"Error removing file {filepath}: {e}") def parse_json_safely(json_string: str) -> Optional[dict]: """ Safely parse JSON string Args: json_string (str): JSON string to parse Returns: dict or None: Parsed JSON or None if failed """ try: return json.loads(json_string) except json.JSONDecodeError: return None def rate_limit_delay(delay: float = 1.0) -> None: """ Add delay between requests to be respectful to servers Args: delay (float): Delay in seconds """ time.sleep(delay) def format_file_size(size_bytes: int) -> str: """ Format file size in human readable format Args: size_bytes (int): Size in bytes Returns: str: Formatted size string """ if size_bytes < 1024: return f"{size_bytes} B" elif size_bytes < 1024 * 1024: return f"{size_bytes / 1024:.1f} KB" else: return f"{size_bytes / (1024 * 1024):.1f} MB" def truncate_text(text: str, max_length: int = 100) -> str: """ Truncate text to specified length Args: text (str): Text to truncate max_length (int): Maximum length Returns: str: Truncated text """ if len(text) <= max_length: return text return text[:max_length - 3] + "..."