"""Disk service — unified interface for Google Drive, Yandex Disk, Apple iCloud. Manages cloud drive provider connections and file uploads. """ import json import logging from typing import Any, Optional from sqlalchemy.ext.asyncio import AsyncSession from app.models.user import User logger = logging.getLogger(__name__) DISK_PROVIDERS = ("google", "yandex", "apple") class DiskService: def __init__(self, db: AsyncSession): self.db = db async def get_connected_providers(self, user: User) -> list[dict[str, Any]]: """Get list of connected disk providers with basic info.""" providers = user.disk_providers or [] # Return sanitized list (no tokens) return [ { "provider": p["provider"], "connected_at": p.get("connected_at", ""), "email": p.get("email", ""), "quota": p.get("quota"), } for p in providers ] async def is_connected(self, user: User, provider: str) -> bool: providers = user.disk_providers or [] return any(p["provider"] == provider for p in providers) async def add_provider( self, user: User, provider: str, tokens: dict[str, Any], email: str = "" ) -> None: providers = user.disk_providers or [] # Remove existing entry for same provider providers = [p for p in providers if p["provider"] != provider] providers.append({ "provider": provider, "access_token": tokens.get("access_token", ""), "refresh_token": tokens.get("refresh_token", ""), "expires_at": tokens.get("expires_at"), "connected_at": __import__("datetime").datetime.utcnow().isoformat(), "email": email, }) user.disk_providers = providers await self.db.commit() async def remove_provider(self, user: User, provider: str) -> None: providers = user.disk_providers or [] user.disk_providers = [p for p in providers if p["provider"] != provider] await self.db.commit() async def get_access_token(self, user: User, provider: str) -> Optional[str]: providers = user.disk_providers or [] for p in providers: if p["provider"] == provider: return p.get("access_token") return None async def upload_to_provider( self, user: User, provider: str, file_name: str, file_content: bytes ) -> bool: """Upload file bytes to specified provider's VoIdea folder.""" token = await self.get_access_token(user, provider) if not token: logger.warning("No token for provider %s (user %s)", provider, user.id) return False if provider == "google": from app.integrations.oauth.google import ensure_app_folder, upload_file parent_id = await ensure_app_folder(token) return await upload_file(token, file_name, file_content, parent_id=parent_id) elif provider == "yandex": from app.integrations.oauth.yandex import ensure_app_folder, upload_file import tempfile, os await ensure_app_folder(token) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".html") try: tmp.write(file_content) tmp.close() return await upload_file(token, tmp.name, file_name) finally: os.unlink(tmp.name) elif provider == "apple": logger.info("Apple iCloud upload is a stub — file not actually uploaded") return True return False async def upload_to_any( self, user: User, file_name: str, file_content: bytes ) -> Optional[dict[str, Any]]: """Upload to first available provider. Returns provider info.""" providers = user.disk_providers or [] for entry in providers: provider = entry["provider"] success = await self.upload_to_provider(user, provider, file_name, file_content) if success: return {"provider": provider, "success": True} return None