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