| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | ## |
|---|
| 3 | ## |
|---|
| 4 | ## This file is part of CDS Indico. |
|---|
| 5 | ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN. |
|---|
| 6 | ## |
|---|
| 7 | ## CDS Indico is free software; you can redistribute it and/or |
|---|
| 8 | ## modify it under the terms of the GNU General Public License as |
|---|
| 9 | ## published by the Free Software Foundation; either version 2 of the |
|---|
| 10 | ## License, or (at your option) any later version. |
|---|
| 11 | ## |
|---|
| 12 | ## CDS Indico is distributed in the hope that it will be useful, but |
|---|
| 13 | ## WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 14 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 15 | ## General Public License for more details. |
|---|
| 16 | ## |
|---|
| 17 | ## You should have received a copy of the GNU General Public License |
|---|
| 18 | ## along with CDS Indico; if not, write to the Free Software Foundation, Inc., |
|---|
| 19 | ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 20 | from indico.core.extpoint.reservation import IReservationListener, IReservationStartStopListener |
|---|
| 21 | from indico.core.extpoint.base import Component |
|---|
| 22 | from zope.interface import implements |
|---|
| 23 | from datetime import datetime, timedelta |
|---|
| 24 | from persistent import Persistent |
|---|
| 25 | from indico.modules.scheduler.client import Client |
|---|
| 26 | from indico.modules.scheduler import tasks |
|---|
| 27 | from MaKaC.plugins.base import Observable, PluginsHolder |
|---|
| 28 | from MaKaC.webinterface.wcomponents import WTemplated |
|---|
| 29 | from MaKaC.common.utils import getEmailList, formatDateTime |
|---|
| 30 | from MaKaC.common.info import HelperMaKaCInfo |
|---|
| 31 | from MaKaC.common.mail import GenericMailer |
|---|
| 32 | from MaKaC.webinterface.mail import GenericNotification |
|---|
| 33 | from MaKaC.plugins.RoomBooking.common import getRoomBookingOption |
|---|
| 34 | from MaKaC.webinterface import urlHandlers |
|---|
| 35 | |
|---|
| 36 | class ReservationStartEndNotificationListener(Component): |
|---|
| 37 | implements(IReservationListener) |
|---|
| 38 | |
|---|
| 39 | def reservationCreated(self, resv): |
|---|
| 40 | if getRoomBookingOption('notificationEnabled'): |
|---|
| 41 | resv.getStartEndNotification().resvCreated() |
|---|
| 42 | |
|---|
| 43 | def reservationUpdated(self, resv): |
|---|
| 44 | if getRoomBookingOption('notificationEnabled'): |
|---|
| 45 | resv.getStartEndNotification().resvUpdated() |
|---|
| 46 | |
|---|
| 47 | def reservationDeleted(self, resv): |
|---|
| 48 | pass |
|---|
| 49 | |
|---|
| 50 | class ReservationStartEndEmailListener(Component): |
|---|
| 51 | implements(IReservationStartStopListener) |
|---|
| 52 | |
|---|
| 53 | def reservationStarted(self, obj, resv): |
|---|
| 54 | sendReservationStartStopNotification(resv, 'start') |
|---|
| 55 | |
|---|
| 56 | def reservationFinished(self, obj, resv): |
|---|
| 57 | sendReservationStartStopNotification(resv, 'end') |
|---|
| 58 | |
|---|
| 59 | def sendReservationStartStopNotification(resv, which): |
|---|
| 60 | if which == 'start' and resv.room.resvStartNotification: |
|---|
| 61 | msg = getRoomBookingOption('startNotificationEmail') |
|---|
| 62 | subject = getRoomBookingOption('startNotificationEmailSubject') |
|---|
| 63 | elif which == 'end' and resv.room.resvEndNotification: |
|---|
| 64 | msg = getRoomBookingOption('endNotificationEmail') |
|---|
| 65 | subject = getRoomBookingOption('endNotificationEmailSubject') |
|---|
| 66 | else: |
|---|
| 67 | return |
|---|
| 68 | |
|---|
| 69 | av = resv.bookedForUser |
|---|
| 70 | if av: |
|---|
| 71 | bookedForDetails = '%s %s\n%s' % (av.getFullName(), av.getPersonId() or '', av.getEmail()) |
|---|
| 72 | else: |
|---|
| 73 | bookedForDetails = '%s\n%s' % (resv.bookedForName, resv.contactEmail) |
|---|
| 74 | |
|---|
| 75 | msgArgs = { |
|---|
| 76 | 'bookedForUser': bookedForDetails, |
|---|
| 77 | 'roomName': resv.room.getFullName(), |
|---|
| 78 | 'roomAtts': resv.room.customAtts, |
|---|
| 79 | 'bookingStart': formatDateTime(resv.startDT), |
|---|
| 80 | 'bookingEnd': formatDateTime(resv.endDT), |
|---|
| 81 | 'detailsLink': urlHandlers.UHRoomBookingBookingDetails.getURL(resv) |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | recipients = [] |
|---|
| 85 | recipients += getRoomBookingOption('notificationEmails') |
|---|
| 86 | if getRoomBookingOption('notificationEmailsToBookedFor'): |
|---|
| 87 | recipients += getEmailList(resv.contactEmail) |
|---|
| 88 | maildata = { |
|---|
| 89 | 'fromAddr': HelperMaKaCInfo.getMaKaCInfoInstance().getNoReplyEmail(returnSupport=True), |
|---|
| 90 | 'toList': recipients, |
|---|
| 91 | 'subject': subject.format(**msgArgs), |
|---|
| 92 | 'body': msg.format(**msgArgs) |
|---|
| 93 | } |
|---|
| 94 | GenericMailer.send(GenericNotification(maildata)) |
|---|
| 95 | |
|---|
| 96 | class ReservationStartEndNotification(Persistent, Observable): |
|---|
| 97 | |
|---|
| 98 | def __init__(self, resv): |
|---|
| 99 | minutes = getRoomBookingOption('notificationBefore') |
|---|
| 100 | self._notificationBefore = timedelta(minutes=minutes) |
|---|
| 101 | self._startActionTriggered = False |
|---|
| 102 | self._endActionTriggered = False |
|---|
| 103 | self._resv = resv |
|---|
| 104 | self._startDT = self._resv.getLocalizedStartDT() - self._notificationBefore |
|---|
| 105 | self._endDT = self._resv.getLocalizedEndDT() |
|---|
| 106 | self._hasTasks = False |
|---|
| 107 | |
|---|
| 108 | def resvCreated(self): |
|---|
| 109 | if self._resv.endDT > datetime.now(): |
|---|
| 110 | self.createTasks() |
|---|
| 111 | |
|---|
| 112 | def resvUpdated(self): |
|---|
| 113 | #self._startActionTriggered = self._endActionTriggered = False # XXX REMOVE THIS LATER XXX |
|---|
| 114 | # Event was created in the past and is still in the past -> don't do anything |
|---|
| 115 | if not self._hasTasks and self._resv.endDT <= datetime.now(): |
|---|
| 116 | return |
|---|
| 117 | if self._startDT != self._resv.getLocalizedStartDT() - self._notificationBefore: |
|---|
| 118 | self._startDT = self._resv.getLocalizedStartDT() - self._notificationBefore |
|---|
| 119 | if self._endActionTriggered: |
|---|
| 120 | if self._resv.startDT > datetime.now(): |
|---|
| 121 | # If the booking had finished but it was changed to start in the future, we can safely re-execute both actions |
|---|
| 122 | self._startActionTriggered = False |
|---|
| 123 | self._endActionTriggered = False |
|---|
| 124 | # In any other case we don't need to change flags |
|---|
| 125 | if not self._startActionTriggered: |
|---|
| 126 | self.createTasks('start') |
|---|
| 127 | if self._endDT != self._resv.getLocalizedEndDT() or not self._hasTasks: |
|---|
| 128 | self._endDT = self._resv.getLocalizedEndDT() |
|---|
| 129 | if not self._endActionTriggered: |
|---|
| 130 | self.createTasks('end') |
|---|
| 131 | |
|---|
| 132 | def createTasks(self, which=None): |
|---|
| 133 | # We cannot get an ID for the task here and since RB can use a separate DB we also cannot store the task itself in the DB. |
|---|
| 134 | # So we have no way of modifying/removing our task when the booking is updated/cancelled/rejected. |
|---|
| 135 | # Instead we always add a new task and the task checks if it's valid when being executed |
|---|
| 136 | # If a notification has been sent before, we don't send a new one (e.g. if someone changes the end time of an expired event) |
|---|
| 137 | self._hasTasks = True |
|---|
| 138 | cl = Client() |
|---|
| 139 | if not which or which == 'start': |
|---|
| 140 | cl.enqueue(tasks.RoomReservationStartedTask(self._resv, self._startDT)) |
|---|
| 141 | if not which or which == 'end': |
|---|
| 142 | cl.enqueue(tasks.RoomReservationFinishedTask(self._resv, self._endDT)) |
|---|
| 143 | |
|---|
| 144 | def taskTriggered(self, which, task): |
|---|
| 145 | if not self._shouldTriggerActions(task.getStartOn(), which, task.getLogger()): |
|---|
| 146 | return |
|---|
| 147 | if which == 'start': |
|---|
| 148 | self._notify('reservationStarted', self._resv) |
|---|
| 149 | elif which == 'end' and self.isActionTriggered('start'): |
|---|
| 150 | self._notify('reservationFinished', self._resv) |
|---|
| 151 | self.actionTriggered(which) |
|---|
| 152 | |
|---|
| 153 | def _shouldTriggerActions(self, now, which, logger): |
|---|
| 154 | if which == 'start' and self._startDT != now: |
|---|
| 155 | logger.info('Reservation %s did not start yet (%s != %s), not triggering actions' % (self._resv.guid, self._startDT, now)) |
|---|
| 156 | return False |
|---|
| 157 | elif which == 'end' and self._endDT != now: |
|---|
| 158 | logger.info('Reservation %s did not finish yet (%s != %s), not triggering actions' % (self._resv.guid, self._endDT, now)) |
|---|
| 159 | return False |
|---|
| 160 | elif self.isActionTriggered(which): |
|---|
| 161 | logger.info('Reservation %s already had its %s actions sent, not triggering actions' % (self._resv.guid, which)) |
|---|
| 162 | return False |
|---|
| 163 | return True |
|---|
| 164 | |
|---|
| 165 | def actionTriggered(self, which): |
|---|
| 166 | if which == 'start': |
|---|
| 167 | self._startActionTriggered = True |
|---|
| 168 | elif which == 'end': |
|---|
| 169 | self._endActionTriggered = True |
|---|
| 170 | |
|---|
| 171 | def isActionTriggered(self, which): |
|---|
| 172 | if which == 'start': |
|---|
| 173 | return self._startActionTriggered |
|---|
| 174 | elif which == 'end': |
|---|
| 175 | return self._endActionTriggered |
|---|
| 176 | |
|---|
| 177 | |
|---|
| 178 | |
|---|
| 179 | |
|---|
| 180 | |
|---|