# services/profile_service.py import os import re import shutil import logging from typing import Optional, List from config import PROFILES_DIR # 👈 dùng từ config logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) def _sanitize_name(name: str) -> str: """ Chuyển tên (email/username) thành dạng an toàn cho filesystem. Giữ chữ, số, dấu gạch ngang và gạch dưới. Ví dụ: "user+test@example.com" -> "user-test_example-com" """ if not isinstance(name, str): raise ValueError("Profile name must be a string") name = name.strip().lower() # Thay các kí tự '@' và '+' thành '-' name = name.replace("@", "-at-").replace("+", "-plus-") # Thay các kí tự không an toàn thành '-' name = re.sub(r"[^a-z0-9._-]", "-", name) # Compact nhiều dấu '-' liên tiếp name = re.sub(r"-{2,}", "-", name) # Trim dấu '-' đầu cuối name = name.strip("-") return name or "profile" class ProfileService: """ Service để quản lý thư mục profiles. Mặc định root folder lấy từ config.PROFILES_DIR. """ base_dir = PROFILES_DIR def __init__(self, profiles_root: Optional[str] = None): # 👇 Dùng PROFILES_DIR nếu không truyền thủ công self.profiles_root = os.path.abspath(profiles_root or PROFILES_DIR) os.makedirs(self.profiles_root, exist_ok=True) logger.info("Profiles root: %s", self.profiles_root) def get_profile_dirname(self, name: str) -> str: """Tên folder đã sanitize (chỉ tên folder, không có path)""" return _sanitize_name(name) def save_profile(self, key: str): # ở đây có thể không cần làm gì nhiều vì Qt tự lưu cookie # nhưng bạn có thể log hoặc thêm custom logic print(f"[ProfileService] Saved profile for {key}") def get_profile_path(self, key: str) -> str: path = os.path.join(self.base_dir, key) os.makedirs(path, exist_ok=True) return path def exists(self, name: str) -> bool: """Check folder có tồn tại không""" return os.path.isdir(self.get_profile_path(name)) def create(self, name: str, copy_from: Optional[str] = None, exist_ok: bool = True) -> str: """ Tạo folder profile. - name: email/username - copy_from: nếu truyền path tới folder mẫu, sẽ copy nội dung từ đó - exist_ok: nếu True và folder đã tồn tại thì không raise Trả về path tới folder profile. """ path = self.get_profile_path(name) if os.path.isdir(path): if exist_ok: logger.debug("Profile already exists: %s", path) return path raise FileExistsError(f"Profile already exists: {path}") os.makedirs(path, exist_ok=True) logger.info("Created profile dir: %s", path) if copy_from: copy_from = os.path.abspath(copy_from) if os.path.isdir(copy_from): for item in os.listdir(copy_from): s = os.path.join(copy_from, item) d = os.path.join(path, item) if os.path.isdir(s): shutil.copytree(s, d, dirs_exist_ok=True) else: shutil.copy2(s, d) logger.info("Copied profile template from %s to %s", copy_from, path) else: logger.warning("copy_from path not found or not a dir: %s", copy_from) return path def delete(self, name: str, ignore_errors: bool = False) -> None: """Xóa folder profile (recursive).""" path = self.get_profile_path(name) if not os.path.isdir(path): raise FileNotFoundError(f"Profile not found: {path}") shutil.rmtree(path, ignore_errors=ignore_errors) logger.info("Deleted profile dir: %s", path) def list_profiles(self) -> List[str]: """Trả về danh sách tên folder (dirnames) trong profiles_root.""" try: return sorted( [ d for d in os.listdir(self.profiles_root) if os.path.isdir(os.path.join(self.profiles_root, d)) ] ) except FileNotFoundError: return [] # ----------------- Optional: QWebEngineProfile creator ----------------- def create_qwebengine_profile(self, name: str, parent=None, profile_id: Optional[str] = None): """ Tạo và trả về QWebEngineProfile đã cấu hình persistent storage (cookies, local storage, cache). Yêu cầu PyQt5.QtWebEngineWidgets được cài. - name: email/username để đặt thư mục lưu - parent: parent QObject cho QWebEngineProfile (thường là self) - profile_id: tên id cho profile (tùy chọn) """ try: from PyQt5.QtWebEngineWidgets import QWebEngineProfile except Exception as e: raise RuntimeError( "PyQt5.QtWebEngineWidgets không khả dụng. " "Không thể tạo QWebEngineProfile." ) from e profile_path = self.get_profile_path(name) os.makedirs(profile_path, exist_ok=True) profile_name = profile_id or self.get_profile_dirname(name) profile = QWebEngineProfile(profile_name, parent) profile.setPersistentStoragePath(profile_path) profile.setCachePath(profile_path) # Force lưu cookie persist try: profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies) except Exception: pass logger.info("Created QWebEngineProfile for %s -> %s", name, profile_path) return profile