morozovka-portal/app/google_sync.py

114 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Синхронизация 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)