"""Синхронизация Google Calendar → локальные Event. Запускается таймером.""" import fcntl import os from datetime import datetime, timezone, timedelta from google.oauth2 import service_account from googleapiclient.discovery import build from sqlmodel import Session, select from . import config from .db import engine from .models import Event SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] _LOCK_PATH = "/tmp/morozovka-google-sync.lock" def _try_lock(): """Не даём двум копиям sync работать одновременно (таймер + ручной запуск).""" f = open(_LOCK_PATH, "w") try: fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) except BlockingIOError: f.close() return None return f def sync_google_calendar(): lock = _try_lock() if lock is None: print("[Google Sync] Уже выполняется другой процесс, выходим.", flush=True) return try: _sync_locked() finally: try: fcntl.flock(lock.fileno(), fcntl.LOCK_UN) lock.close() except Exception: pass def _sync_locked(): print("[Google Sync] Начинаем синхронизацию...", flush=True) if not os.path.isfile(config.GOOGLE_CREDENTIALS_FILE): print(f"[Google Sync] Нет файла credentials: {config.GOOGLE_CREDENTIALS_FILE}", flush=True) return creds = service_account.Credentials.from_service_account_file( config.GOOGLE_CREDENTIALS_FILE, scopes=SCOPES) service = build('calendar', 'v3', credentials=creds) time_min = (datetime.now(timezone.utc) - timedelta(days=30)).isoformat() calendar_id = config.GOOGLE_CALENDAR_ID print(f"[Google Sync] Запрашиваем календарь: {calendar_id}", flush=True) events_result = service.events().list( calendarId=calendar_id, timeMin=time_min, maxResults=100, singleEvents=True, orderBy='startTime', ).execute() google_events = events_result.get('items', []) print(f"[Google Sync] Найдено событий в Google API: {len(google_events)}", flush=True) count = 0 with Session(engine) as session: for g_event in google_events: g_id = g_event['id'] start = g_event['start'].get('dateTime', g_event['start'].get('date')) end = g_event['end'].get('dateTime', g_event['end'].get('date')) if 'T' not in start: continue start_dt = datetime.fromisoformat(start.replace('Z', '+00:00')) end_dt = datetime.fromisoformat(end.replace('Z', '+00:00')) existing = session.exec( select(Event).where(Event.google_event_id == g_id) ).first() if existing: if getattr(existing, 'manually_edited', False): continue existing.title = g_event.get('summary', 'Без названия') existing.start_time = start_dt existing.end_time = end_dt existing.location = g_event.get('location') existing.description = g_event.get('description') session.add(existing) else: session.add(Event( google_event_id=g_id, title=g_event.get('summary', 'Без названия'), event_type='lecture', start_time=start_dt, end_time=end_dt, location=g_event.get('location'), description=g_event.get('description'), is_public=True, )) count += 1 session.commit() print(f"[Google Sync] Новых событий: {count}", flush=True)