160 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			160 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Python
		
	
	
	
# 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 PyQt6.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 PyQt6.QtWebEngineWidgets import QWebEngineProfile
 | 
						|
        except Exception as e:
 | 
						|
            raise RuntimeError(
 | 
						|
                "PyQt6.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:
 | 
						|
            from PyQt6.QtWebEngineCore import QWebEngineProfile as CoreProfile
 | 
						|
 | 
						|
            profile.setPersistentCookiesPolicy(
 | 
						|
                CoreProfile.PersistentCookiesPolicy.ForcePersistentCookies
 | 
						|
            )
 | 
						|
        except Exception:
 | 
						|
            pass
 | 
						|
 | 
						|
        logger.info("Created QWebEngineProfile for %s -> %s", name, profile_path)
 | 
						|
        return profile
 |