2014-04-26 11:17:10 +02:00
|
|
|
import logging
|
|
|
|
import httplib2
|
|
|
|
import datetime
|
|
|
|
import time
|
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
from eventplanner.models import Event, EventParticipation
|
|
|
|
from eventplanner_gcal.models import GCalMapping, GCalPushChannel, UserGCalCoupling
|
2023-01-03 20:13:35 +01:00
|
|
|
# noinspection PyUnresolvedReferences,PyUnresolvedReferences
|
2014-04-26 11:17:10 +02:00
|
|
|
from apiclient.http import BatchHttpRequest
|
2019-01-05 13:04:20 +01:00
|
|
|
from builtins import str as text # python2 and python3
|
2014-04-26 11:17:10 +02:00
|
|
|
from django.conf import settings
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
# ---------------------------------- Authentication using oauth2 -----------------------------------------------------
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def create_gcal_service_object():
|
2014-04-26 11:17:10 +02:00
|
|
|
"""Creates a Google API service object. This object is required whenever a Google API call is made"""
|
|
|
|
from oauth2client.file import Storage
|
2023-01-03 20:13:35 +01:00
|
|
|
# noinspection PyUnresolvedReferences
|
2014-04-26 11:17:10 +02:00
|
|
|
from apiclient.discovery import build
|
|
|
|
|
|
|
|
gcal_settings = settings.GCAL_COUPLING
|
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
storage = Storage(gcal_settings['credentials_file'])
|
2014-04-26 11:17:10 +02:00
|
|
|
credentials = storage.get()
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
logger.debug("Credentials", credentials)
|
|
|
|
if credentials is None or credentials.invalid is True:
|
|
|
|
# flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
|
2014-04-26 11:17:10 +02:00
|
|
|
logger.error("Unable to initialize Google Calendar coupling. Check your settings!")
|
|
|
|
return None
|
|
|
|
|
|
|
|
http = httplib2.Http()
|
2019-01-05 11:27:15 +01:00
|
|
|
http = credentials.authorize(http)
|
|
|
|
res = build(serviceName='calendar', version='v3',
|
|
|
|
http=http, developerKey=gcal_settings['developerKey'])
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
if res is None:
|
2019-01-05 13:04:20 +01:00
|
|
|
logger.error("Authentication at Google API failed. Check your settings!")
|
2014-04-26 11:17:10 +02:00
|
|
|
return res
|
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def get_service_object():
|
|
|
|
if get_service_object.__serviceObject is None:
|
|
|
|
get_service_object.__serviceObject = create_gcal_service_object()
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
return get_service_object.__serviceObject
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
get_service_object.__serviceObject = None
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
# --------------------- Building GCal event representation ----------------------------------------------------------
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def build_gcal_attendees_obj(event):
|
|
|
|
"""Builds an attendees object that is inserted into the GCal event.
|
|
|
|
Attendees are all users that have a google mail address."""
|
2014-04-26 11:17:10 +02:00
|
|
|
result = []
|
2014-06-20 12:24:01 +02:00
|
|
|
|
|
|
|
for userMapping in UserGCalCoupling.objects.all():
|
|
|
|
u = userMapping.user
|
2014-06-22 11:52:26 +02:00
|
|
|
|
|
|
|
# No get or create here, since a create would trigger another synchronization
|
2019-01-05 11:27:15 +01:00
|
|
|
# participation = EventParticipation.get_or_create( u, event )
|
2014-06-22 11:52:26 +02:00
|
|
|
try:
|
2019-01-05 11:27:15 +01:00
|
|
|
participation = EventParticipation.objects.get(event=event, user=u)
|
2019-01-05 13:04:20 +01:00
|
|
|
local_status = participation.status
|
|
|
|
local_comment = participation.comment
|
2014-06-22 11:52:26 +02:00
|
|
|
except EventParticipation.DoesNotExist:
|
2019-01-05 13:04:20 +01:00
|
|
|
local_status = "-"
|
|
|
|
local_comment = ""
|
2014-06-22 10:33:39 +02:00
|
|
|
|
|
|
|
status = "needsAction"
|
2019-01-05 13:04:20 +01:00
|
|
|
if local_status == "?":
|
|
|
|
status = "tentative"
|
|
|
|
elif local_status == 'Yes':
|
|
|
|
status = "accepted"
|
|
|
|
elif local_status == 'No':
|
|
|
|
status = "declined"
|
2014-06-20 12:24:01 +02:00
|
|
|
|
|
|
|
o = {
|
|
|
|
'id': userMapping.email,
|
2014-06-22 10:33:39 +02:00
|
|
|
'email': userMapping.email,
|
2014-06-20 12:24:01 +02:00
|
|
|
'displayName': u.username,
|
2019-01-05 13:04:20 +01:00
|
|
|
'comment': local_comment,
|
2014-06-20 12:24:01 +02:00
|
|
|
'responseStatus': status,
|
|
|
|
}
|
2019-01-05 11:27:15 +01:00
|
|
|
result.append(o)
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def build_gcal_event(event, timezone="Europe/Berlin"):
|
2014-04-26 11:17:10 +02:00
|
|
|
""" Builds a GCal event using a local event. """
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def create_date_time_obj(date, time_obj):
|
|
|
|
if time_obj is None:
|
|
|
|
return {'date': text(date), 'timeZone': timezone}
|
2014-04-26 11:17:10 +02:00
|
|
|
else:
|
2019-01-05 13:04:20 +01:00
|
|
|
return {'dateTime': text(date) + 'T' + text(time_obj), 'timeZone': timezone}
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
start_date = event.date
|
|
|
|
end_date = event.end_date
|
|
|
|
if end_date is None:
|
|
|
|
end_date = start_date
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
start_time = event.meeting_time
|
|
|
|
if start_time is None:
|
|
|
|
start_time = event.time
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
if start_time is None:
|
|
|
|
end_time = None
|
2014-04-26 11:17:10 +02:00
|
|
|
else:
|
2019-01-05 13:04:20 +01:00
|
|
|
end_time = datetime.time(22, 30)
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
g_location = text(event.location)
|
2014-06-22 14:10:53 +02:00
|
|
|
if event.map_location:
|
|
|
|
# Map location has the following format: latitude,longitude,zoomlevel
|
|
|
|
# the first two are needed
|
|
|
|
s = event.map_location.split(",")
|
2019-01-05 13:04:20 +01:00
|
|
|
g_location = text("%s,%s" % (s[0], s[1]))
|
2014-06-22 14:10:53 +02:00
|
|
|
|
2014-04-26 11:17:10 +02:00
|
|
|
return {
|
2019-01-05 13:04:20 +01:00
|
|
|
'summary': text(settings.GCAL_COUPLING['eventPrefix'] + event.title),
|
|
|
|
'description': text(event.desc),
|
|
|
|
'location': g_location,
|
|
|
|
'start': create_date_time_obj(start_date, start_time),
|
|
|
|
'end': create_date_time_obj(end_date, end_time),
|
2014-04-26 11:17:10 +02:00
|
|
|
'extendedProperties': {
|
|
|
|
'private': {
|
|
|
|
'blechreizEvent': 'true',
|
|
|
|
'blechreizID': event.id,
|
|
|
|
}
|
|
|
|
},
|
2019-01-05 13:04:20 +01:00
|
|
|
'attendees': build_gcal_attendees_obj(event),
|
2014-04-26 11:17:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
# ------------------------------ Callback Functions -------------------------------------------------------------------
|
2014-06-22 11:52:26 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def on_gcal_event_created(_, response, exception=None):
|
2014-06-22 11:52:26 +02:00
|
|
|
"""Callback function for created events to enter new gcal id in the mapping table"""
|
|
|
|
if exception is not None:
|
2019-01-05 13:04:20 +01:00
|
|
|
logger.error("on_gcal_event_created: Exception " + str(exception))
|
2014-06-22 11:52:26 +02:00
|
|
|
raise exception
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
google_id = response['id']
|
|
|
|
django_id = response['extendedProperties']['private']['blechreizID']
|
|
|
|
mapping = GCalMapping(gcal_id=google_id, event=Event.objects.get(pk=django_id))
|
2014-06-22 11:52:26 +02:00
|
|
|
mapping.save()
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
# ------------------------------ GCal Api Calls --------------------------------------------------------------------
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def get_all_gcal_events(service, from_now=False):
|
2014-04-26 11:17:10 +02:00
|
|
|
"""Retrieves all gcal events with custom property blechreizEvent=True i.e. all
|
|
|
|
events that have been created by this script."""
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
if from_now:
|
2014-04-26 11:17:10 +02:00
|
|
|
now = datetime.datetime.now()
|
2019-01-05 13:04:20 +01:00
|
|
|
min_time = now.strftime("%Y-%m-%dT%H:%M:%S-00:00")
|
2014-04-26 11:17:10 +02:00
|
|
|
else:
|
2019-01-05 13:04:20 +01:00
|
|
|
min_time = '2000-01-01T00:00:00-00:00'
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
events = service.events().list(
|
|
|
|
calendarId='primary',
|
|
|
|
singleEvents=True,
|
|
|
|
maxResults=1000,
|
|
|
|
orderBy='startTime',
|
2019-01-05 13:04:20 +01:00
|
|
|
timeMin=min_time,
|
2014-04-26 11:17:10 +02:00
|
|
|
timeMax='2100-01-01T00:00:00-00:00',
|
|
|
|
privateExtendedProperty='blechreizEvent=true',
|
2019-01-05 11:27:15 +01:00
|
|
|
).execute()
|
2014-04-26 11:17:10 +02:00
|
|
|
return events['items']
|
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def create_gcal_event(service, event, timezone="Europe/Berlin"):
|
2014-04-26 11:17:10 +02:00
|
|
|
"""Creates a new gcal event using a local event"""
|
2019-01-05 13:04:20 +01:00
|
|
|
google_event = build_gcal_event(event, timezone)
|
|
|
|
return service.events().insert(calendarId='primary', body=google_event)
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def update_gcal_event(service, event, timezone="Europe/Berlin"):
|
2014-04-26 11:17:10 +02:00
|
|
|
"""Updates an existing gcal event, using a local event"""
|
2019-01-05 13:04:20 +01:00
|
|
|
google_event = build_gcal_event(event, timezone)
|
2014-06-22 11:52:26 +02:00
|
|
|
try:
|
2019-01-05 11:27:15 +01:00
|
|
|
mapping = GCalMapping.objects.get(event=event)
|
2014-06-22 11:52:26 +02:00
|
|
|
except GCalMapping.DoesNotExist:
|
2019-01-05 13:04:20 +01:00
|
|
|
return create_gcal_event(service, event, timezone)
|
2014-06-22 11:52:26 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
return service.events().patch(calendarId='primary', eventId=mapping.gcal_id, body=google_event)
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def delete_gcal_event(service, event):
|
2014-04-26 11:17:10 +02:00
|
|
|
"""Deletes gcal that belongs to the given local event"""
|
2019-01-05 11:27:15 +01:00
|
|
|
mapping = GCalMapping.objects.get(event=event)
|
2019-01-05 13:04:20 +01:00
|
|
|
gcal_id = mapping.gcal_id
|
2014-06-22 11:52:26 +02:00
|
|
|
mapping.delete()
|
2019-01-05 13:04:20 +01:00
|
|
|
return service.events().delete(calendarId='primary', eventId=gcal_id)
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
# ------------------------------------- Synchronization -------------------------------------------------------------
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def delete_all_gcal_events(service=None):
|
2014-04-26 11:17:10 +02:00
|
|
|
"""Deletes all gcal events that have been created by this script"""
|
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
if service is None:
|
2019-01-05 13:04:20 +01:00
|
|
|
service = get_service_object()
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
gcal_ids = [ev['id'] for ev in get_all_gcal_events(service)]
|
|
|
|
num_ids = len(gcal_ids)
|
|
|
|
if num_ids == 0:
|
|
|
|
return num_ids
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
batch = BatchHttpRequest()
|
2019-01-05 13:04:20 +01:00
|
|
|
for ev_id in gcal_ids:
|
|
|
|
batch.add(service.events().delete(calendarId='primary', eventId=ev_id))
|
2014-04-26 11:17:10 +02:00
|
|
|
batch.execute()
|
|
|
|
|
2014-06-22 11:52:26 +02:00
|
|
|
GCalMapping.objects.all().delete()
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
return num_ids
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def sync_from_local_to_google(service=None):
|
2014-04-26 11:17:10 +02:00
|
|
|
""" Creates a google event for each local event (if it does not exist yet) and deletes all google events
|
|
|
|
that are not found in local database. Updates participation info of gcal events using local data
|
|
|
|
"""
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
if service is None:
|
|
|
|
service = get_service_object()
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
all_events = get_all_gcal_events(service)
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
events_at_google_django_id = set()
|
|
|
|
events_at_google_google_id = set()
|
|
|
|
for gcal_ev in all_events:
|
|
|
|
events_at_google_django_id.add(int(gcal_ev['extendedProperties']['private']['blechreizID']))
|
|
|
|
events_at_google_google_id.add(gcal_ev['id'])
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
local_events_django_id = set(Event.objects.all().values_list('pk', flat=True))
|
|
|
|
local_events_google_id = set(GCalMapping.objects.all().values_list('gcal_id', flat=True))
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
events_to_create_django_id = local_events_django_id - events_at_google_django_id
|
|
|
|
events_to_delete_google_id = events_at_google_google_id - local_events_google_id
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
batch = BatchHttpRequest()
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
batch_is_empty = True
|
|
|
|
for event_django_id in events_to_create_django_id:
|
|
|
|
batch.add(create_gcal_event(service, Event.objects.get(pk=event_django_id)), callback=on_gcal_event_created)
|
|
|
|
batch_is_empty = False
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
for eventGoogleID in events_to_delete_google_id:
|
2019-01-05 11:27:15 +01:00
|
|
|
batch.add(service.events().delete(calendarId='primary', eventId=eventGoogleID))
|
2019-01-05 13:04:20 +01:00
|
|
|
batch_is_empty = False
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
for gcal_ev in all_events:
|
|
|
|
event_django_id = int(gcal_ev['extendedProperties']['private']['blechreizID'])
|
2014-04-26 11:17:10 +02:00
|
|
|
try:
|
2019-01-05 13:04:20 +01:00
|
|
|
django_ev = Event.objects.get(pk=event_django_id)
|
|
|
|
if 'attendees' not in gcal_ev:
|
|
|
|
gcal_ev['attendees'] = []
|
2014-06-20 12:24:01 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
if gcal_ev['attendees'] != build_gcal_attendees_obj(django_ev):
|
|
|
|
batch.add(update_gcal_event(service, django_ev))
|
|
|
|
batch_is_empty = False
|
2014-04-26 11:17:10 +02:00
|
|
|
except Event.DoesNotExist:
|
|
|
|
pass
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
if not batch_is_empty:
|
2014-04-26 11:17:10 +02:00
|
|
|
batch.execute()
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
return len(events_to_create_django_id), len(events_to_delete_google_id)
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def sync_from_google_to_local(service=None):
|
2014-04-26 11:17:10 +02:00
|
|
|
"""Retrieves only participation infos for all events and updates local database if anything has changed. """
|
2019-01-05 11:27:15 +01:00
|
|
|
|
|
|
|
if service is None:
|
2019-01-05 13:04:20 +01:00
|
|
|
service = get_service_object()
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
new_status_received = False
|
|
|
|
all_events = get_all_gcal_events(service, from_now=True)
|
|
|
|
for e in all_events:
|
|
|
|
local_id = e['extendedProperties']['private']['blechreizID']
|
|
|
|
local_event = Event.objects.get(pk=local_id)
|
2014-04-26 11:17:10 +02:00
|
|
|
for a in e['attendees']:
|
2019-01-05 11:27:15 +01:00
|
|
|
user = UserGCalCoupling.objects.get(email=a['email']).user
|
2019-01-05 13:04:20 +01:00
|
|
|
part = EventParticipation.get_or_create(user, local_event)
|
2014-04-26 11:17:10 +02:00
|
|
|
if 'comment' in a:
|
|
|
|
part.comment = a['comment']
|
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
if a['responseStatus'] == 'needsAction':
|
2014-06-22 10:33:39 +02:00
|
|
|
part.status = "-"
|
2019-01-05 11:27:15 +01:00
|
|
|
elif a['responseStatus'] == 'tentative':
|
2014-04-26 11:17:10 +02:00
|
|
|
part.status = '?'
|
|
|
|
elif a['responseStatus'] == 'accepted':
|
|
|
|
part.status = 'Yes'
|
|
|
|
elif a['responseStatus'] == 'declined':
|
|
|
|
part.status = 'No'
|
|
|
|
else:
|
2019-01-05 11:27:15 +01:00
|
|
|
logger.error("Unknown response status when mapping gcal event: " + a['responseStatus'])
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
prev = EventParticipation.objects.get(event=part.event, user=part.user)
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
# Important: Save only if the participation info has changed
|
|
|
|
# otherwise everything is synced back to google via the post save signal
|
|
|
|
# and an endless loop is entered
|
|
|
|
if prev.status != part.status or prev.comment != part.comment:
|
|
|
|
part.save()
|
2019-01-05 13:04:20 +01:00
|
|
|
new_status_received = True
|
2014-06-17 23:42:18 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
return new_status_received
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------- Synchronization ----------------------------------------------------
|
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def check_gcal_subscription(service=None, time_to_live=14 * 24 * 3600, renew_before_expiry=None):
|
2014-04-26 11:17:10 +02:00
|
|
|
"""Google offers a push service if any event information has changed. This works using a so called
|
|
|
|
channel, which has a certain time to live. This method checks that a valid channel exists:
|
|
|
|
- if none exists a new one is created
|
|
|
|
- if existing channel does expire soon, the channel is renewed
|
|
|
|
- if channel has already expired a sync is triggered and a new channel is created
|
|
|
|
"""
|
2019-01-05 11:27:15 +01:00
|
|
|
if service is None:
|
2019-01-05 13:04:20 +01:00
|
|
|
service = get_service_object()
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
if renew_before_expiry is None:
|
|
|
|
renew_before_expiry = 0.8 * time_to_live
|
2014-04-26 11:17:10 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
callback_url = settings.GCAL_COUPLING['push_url']
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
# Test if a channel already exists for this callbackURL
|
|
|
|
try:
|
2019-01-05 13:04:20 +01:00
|
|
|
db_channel = GCalPushChannel.objects.get(address=callback_url)
|
|
|
|
g_channel = db_channel.to_google_channel()
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
# if expiration time between 0 and two days: stop and create new channel
|
2019-01-05 13:04:20 +01:00
|
|
|
cur_time = int(time.time() * 1000)
|
|
|
|
if g_channel.expiration > cur_time:
|
2014-04-26 11:17:10 +02:00
|
|
|
# not yet expired
|
2019-01-05 13:04:20 +01:00
|
|
|
if cur_time + renew_before_expiry * 1000 > g_channel.expiration:
|
2019-01-05 11:27:15 +01:00
|
|
|
# will expire in less than "renewBeforeExpiry"
|
2019-01-05 13:04:20 +01:00
|
|
|
logger.info("Renewing Google Calendar Subscription: " + callback_url)
|
|
|
|
GCalPushChannel.stop(service, g_channel)
|
|
|
|
GCalPushChannel.create_new(callback_url, service, time_to_live)
|
2014-04-26 11:17:10 +02:00
|
|
|
else:
|
2019-01-05 13:04:20 +01:00
|
|
|
logger.info("Channel active until %d " % (g_channel.expiration,))
|
2014-04-26 11:17:10 +02:00
|
|
|
else:
|
2019-01-05 11:27:15 +01:00
|
|
|
logger.info("Google calendar subscription had expired - getting new subscription")
|
2014-04-26 11:17:10 +02:00
|
|
|
# to get back in sync again we have to decide which data to take
|
|
|
|
# so we use the local data as reference
|
2019-01-05 13:04:20 +01:00
|
|
|
sync_from_local_to_google(service)
|
|
|
|
GCalPushChannel.create_new(callback_url, service, time_to_live)
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
except GCalPushChannel.DoesNotExist:
|
|
|
|
# create new channel and save it in database
|
2019-01-05 13:04:20 +01:00
|
|
|
logger.info("No CGalCallback Channel exists yet for: " + callback_url)
|
2014-04-26 11:17:10 +02:00
|
|
|
# to get back in sync again we have to decide which data to take
|
|
|
|
# so we use the local data as reference
|
2019-01-05 13:04:20 +01:00
|
|
|
sync_from_local_to_google(service)
|
|
|
|
GCalPushChannel.create_new(callback_url, service, time_to_live)
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def stop_all_gcal_subscriptions(service=None):
|
2014-04-26 11:17:10 +02:00
|
|
|
"""Stops the channel subscription """
|
|
|
|
|
2019-01-05 11:27:15 +01:00
|
|
|
if service is None:
|
2019-01-05 13:04:20 +01:00
|
|
|
service = get_service_object()
|
2014-04-26 11:17:10 +02:00
|
|
|
|
|
|
|
for dbChannel in GCalPushChannel.objects.all():
|
2019-01-05 13:04:20 +01:00
|
|
|
logger.info("Stopping %s expiry at %d " % (dbChannel.id, dbChannel.expiration))
|
|
|
|
GCalPushChannel.stop(service, dbChannel.to_google_channel())
|
2014-06-20 08:57:04 +02:00
|
|
|
|
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
def check_if_google_callback_is_valid(token, channel_id, resource_id, service=None):
|
2019-01-05 11:27:15 +01:00
|
|
|
if service is None:
|
2019-01-05 13:04:20 +01:00
|
|
|
service = get_service_object()
|
2014-06-20 08:57:04 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
all_channels = GCalPushChannel.objects.all()
|
|
|
|
if len(all_channels) == 0:
|
2019-01-05 11:27:15 +01:00
|
|
|
return False # no known subscriptions -> callback has to be from an old channel
|
2014-06-20 08:57:04 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
if len(all_channels) > 1:
|
2019-01-05 11:27:15 +01:00
|
|
|
logger.warning("Multiple GCal subscriptions! This is strange and probably an error. "
|
|
|
|
"All channels are closed and one new is created. ")
|
2019-01-05 13:04:20 +01:00
|
|
|
stop_all_gcal_subscriptions(service)
|
|
|
|
check_gcal_subscription()
|
|
|
|
all_channels = GCalPushChannel.objects.all()
|
2014-06-20 08:57:04 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
assert (len(all_channels) == 1)
|
2014-06-20 08:57:04 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
the_channel = all_channels[0]
|
2014-06-20 08:57:04 +02:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
if channel_id != the_channel.id or resource_id != the_channel.resource_id or token != the_channel.token:
|
2019-01-05 11:27:15 +01:00
|
|
|
logger.warning("Got GCal Response from an unexpected Channel"
|
|
|
|
"Got (%s,%s,%s) "
|
|
|
|
"expected (%s,%s,%s) "
|
|
|
|
"Old Channel is stopped."
|
2019-01-05 13:04:20 +01:00
|
|
|
% (channel_id, resource_id, token, the_channel.id, the_channel.resource_id, the_channel.token))
|
2019-01-05 11:27:15 +01:00
|
|
|
|
2019-01-05 13:04:20 +01:00
|
|
|
channel_to_stop = GCalPushChannel(id=channel_id, resource_id=resource_id, token=token)
|
|
|
|
GCalPushChannel.stop(service, channel_to_stop.to_google_channel())
|
2014-06-20 08:57:04 +02:00
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|