| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | ## |
|---|
| 3 | ## |
|---|
| 4 | ## This file is part of CDS Indico. |
|---|
| 5 | ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2011 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 | |
|---|
| 21 | import icalendar as ical |
|---|
| 22 | |
|---|
| 23 | from indico.web.http_api import HTTPAPIHook, DataFetcher |
|---|
| 24 | from indico.web.http_api.ical import ICalSerializer |
|---|
| 25 | from indico.web.http_api.util import get_query_parameter |
|---|
| 26 | from indico.web.http_api.responses import HTTPAPIError |
|---|
| 27 | from indico.web.wsgi import webinterface_handler_config as apache |
|---|
| 28 | from indico.util.fossilize import fossilize, IFossil |
|---|
| 29 | from indico.util.fossilize.conversion import Conversion |
|---|
| 30 | |
|---|
| 31 | from MaKaC.webinterface.rh.collaboration import RCCollaborationAdmin |
|---|
| 32 | from MaKaC.common.indexes import IndexesHolder |
|---|
| 33 | from MaKaC.plugins.Collaboration.RecordingManager.common import createIndicoLink |
|---|
| 34 | from MaKaC.plugins.Collaboration.collaborationTools import CollaborationTools |
|---|
| 35 | from MaKaC.conference import ConferenceHolder |
|---|
| 36 | from MaKaC.plugins.Collaboration.base import SpeakerStatusEnum |
|---|
| 37 | from MaKaC.plugins.Collaboration.fossils import ICollaborationMetadataFossil |
|---|
| 38 | |
|---|
| 39 | |
|---|
| 40 | globalHTTPAPIHooks = ['CollaborationAPIHook', 'CollaborationExportHook', 'VideoEventHook'] |
|---|
| 41 | |
|---|
| 42 | |
|---|
| 43 | def serialize_collaboration_alarm(fossil, now): |
|---|
| 44 | alarm = ical.Alarm() |
|---|
| 45 | trigger = "-PT" + str(fossil['alarm']) + "M" # iCalendar spec for pre-event trigger |
|---|
| 46 | alarm.set('trigger', trigger) |
|---|
| 47 | alarm.set('action', 'DISPLAY') |
|---|
| 48 | alarm.set('summary', "[" + fossil['type'] + "] " + fossil['status'] + " - " + fossil['title'].decode('utf-8')) |
|---|
| 49 | alarm.set('description', str(fossil['url'])) |
|---|
| 50 | return alarm |
|---|
| 51 | |
|---|
| 52 | |
|---|
| 53 | def serialize_collaboration(fossil, now): |
|---|
| 54 | event = ical.Event() |
|---|
| 55 | url = str(fossil['url']) |
|---|
| 56 | event.set('uid', 'indico-collaboration-%s@cern.ch' % fossil['uniqueId']) |
|---|
| 57 | event.set('dtstamp', now) |
|---|
| 58 | event.set('dtstart', fossil['startDate']) |
|---|
| 59 | event.set('dtend', fossil['endDate']) |
|---|
| 60 | event.set('url', url) |
|---|
| 61 | event.set('categories', "VideoService - " + fossil['type']) |
|---|
| 62 | event.set('summary', "[" + fossil['type'] + "] " + fossil['status'] + " - " + fossil['title'].decode('utf-8')) |
|---|
| 63 | event.set('description', url) |
|---|
| 64 | |
|---|
| 65 | # If there is an alarm required, add a subcomponent to the Event |
|---|
| 66 | if fossil.has_key('alarm'): |
|---|
| 67 | event.add_component(serialize_collaboration_alarm(fossil, now)) |
|---|
| 68 | |
|---|
| 69 | return event |
|---|
| 70 | |
|---|
| 71 | |
|---|
| 72 | # the iCal serializer needs some extra info on how to display things |
|---|
| 73 | ICalSerializer.register_mapper('collaborationMetadata', serialize_collaboration) |
|---|
| 74 | |
|---|
| 75 | |
|---|
| 76 | class CollaborationAPIHook(HTTPAPIHook): |
|---|
| 77 | PREFIX = 'api' |
|---|
| 78 | TYPES = ('recordingManager',) |
|---|
| 79 | RE = r'createLink' |
|---|
| 80 | GUEST_ALLOWED = False |
|---|
| 81 | VALID_FORMATS = ('json',) |
|---|
| 82 | COMMIT = True |
|---|
| 83 | HTTP_POST = True |
|---|
| 84 | |
|---|
| 85 | def _hasAccess(self, aw): |
|---|
| 86 | return RCCollaborationAdmin.hasRights(user=aw.getUser()) |
|---|
| 87 | |
|---|
| 88 | def _getParams(self): |
|---|
| 89 | |
|---|
| 90 | super(CollaborationAPIHook, self)._getParams() |
|---|
| 91 | self._indicoID = get_query_parameter(self._queryParams, ['iid', 'indicoID']) |
|---|
| 92 | self._cdsID = get_query_parameter(self._queryParams, ['cid', 'cdsID']) |
|---|
| 93 | |
|---|
| 94 | def api_recordingManager(self, aw): |
|---|
| 95 | if not self._indicoID or not self._cdsID: |
|---|
| 96 | raise HTTPAPIError('A required argument is missing.', apache.HTTP_BAD_REQUEST) |
|---|
| 97 | |
|---|
| 98 | success = createIndicoLink(self._indicoID, self._cdsID) |
|---|
| 99 | return {'success': success} |
|---|
| 100 | |
|---|
| 101 | |
|---|
| 102 | class CollaborationExportHook(HTTPAPIHook): |
|---|
| 103 | TYPES = ('eAgreements', ) |
|---|
| 104 | RE = r'(?P<confId>\w+)' |
|---|
| 105 | GUEST_ALLOWED = False |
|---|
| 106 | VALID_FORMATS = ('json', 'jsonp', 'xml') |
|---|
| 107 | |
|---|
| 108 | def _hasAccess(self, aw): |
|---|
| 109 | return RCCollaborationAdmin.hasRights(user=aw.getUser()) |
|---|
| 110 | |
|---|
| 111 | def _getParams(self): |
|---|
| 112 | super(CollaborationExportHook, self)._getParams() |
|---|
| 113 | self._conf = ConferenceHolder().getById(self._pathParams['confId'], True) |
|---|
| 114 | if not self._conf: |
|---|
| 115 | raise HTTPAPIError('Conference does not exist.', apache.HTTP_BAD_REQUEST) |
|---|
| 116 | |
|---|
| 117 | def export_eAgreements(self, aw): |
|---|
| 118 | manager = self._conf.getCSBookingManager() |
|---|
| 119 | requestType = CollaborationTools.getRequestTypeUserCanManage(self._conf, aw.getUser()) |
|---|
| 120 | contributions = manager.getContributionSpeakerByType(requestType) |
|---|
| 121 | for cont, speakers in contributions.items(): |
|---|
| 122 | for spk in speakers: |
|---|
| 123 | sw = manager.getSpeakerWrapperByUniqueId('%s.%s' % (cont, spk.getId())) |
|---|
| 124 | if not sw: |
|---|
| 125 | continue |
|---|
| 126 | signed = None |
|---|
| 127 | if sw.getStatus() in (SpeakerStatusEnum.FROMFILE, SpeakerStatusEnum.SIGNED): |
|---|
| 128 | signed = True |
|---|
| 129 | elif sw.getStatus() == SpeakerStatusEnum.REFUSED: |
|---|
| 130 | signed = False |
|---|
| 131 | yield { |
|---|
| 132 | 'confId': sw.getConference().getId(), |
|---|
| 133 | 'contrib': cont, |
|---|
| 134 | 'type': sw.getRequestType(), |
|---|
| 135 | 'status': sw.getStatus(), |
|---|
| 136 | 'signed': signed, |
|---|
| 137 | 'speaker': { |
|---|
| 138 | 'id': spk.getId(), |
|---|
| 139 | 'name': spk.getFullName(), |
|---|
| 140 | 'email': spk.getEmail() |
|---|
| 141 | } |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | |
|---|
| 145 | class VideoEventHook(HTTPAPIHook): |
|---|
| 146 | """ |
|---|
| 147 | This has been defined as a separate hook to CollaborationExportHook et al |
|---|
| 148 | due to the different input expected for both. It would be beneficial to |
|---|
| 149 | find a way to amalgamate the two at a later date. |
|---|
| 150 | """ |
|---|
| 151 | |
|---|
| 152 | TYPES = ('video', ) |
|---|
| 153 | RE = r'(?P<idlist>\w+(?:-\w+)*)' |
|---|
| 154 | DEFAULT_DETAIL = 'all' |
|---|
| 155 | MAX_RECORDS = { # @TODO: Ascertain reasonable limits for each section. |
|---|
| 156 | 'all': 100000, |
|---|
| 157 | 'vidyo': 50000, |
|---|
| 158 | 'webcast': 50000, |
|---|
| 159 | 'mcu': 50000, |
|---|
| 160 | 'evo': 50000 |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | def _getParams(self): |
|---|
| 164 | super(VideoEventHook, self)._getParams() |
|---|
| 165 | |
|---|
| 166 | """ In this case, idlist refers to the different indicies which can |
|---|
| 167 | be called, e.g: vidyo, evo, mcu etc. |
|---|
| 168 | """ |
|---|
| 169 | self._idList = self._pathParams['idlist'].split('-') |
|---|
| 170 | |
|---|
| 171 | if not self._queryParams.has_key('alarms'): |
|---|
| 172 | self._alarms = None |
|---|
| 173 | else: |
|---|
| 174 | self._alarms = get_query_parameter(self._queryParams, ['alarms'], 0, True) |
|---|
| 175 | |
|---|
| 176 | def export_video(self, aw): |
|---|
| 177 | expInt = VideoEventFetcher(aw, self) |
|---|
| 178 | return expInt.video(self._idList, self._alarms) |
|---|
| 179 | |
|---|
| 180 | |
|---|
| 181 | class VideoEventFetcher(DataFetcher): |
|---|
| 182 | DETAIL_INTERFACES = { |
|---|
| 183 | 'all' : ICollaborationMetadataFossil |
|---|
| 184 | } |
|---|
| 185 | ID_TO_IDX = { |
|---|
| 186 | 'all' : 'all', |
|---|
| 187 | 'vidyo' : 'Vidyo', |
|---|
| 188 | 'webcast' : 'WebcastRequest', |
|---|
| 189 | 'recording' : 'RecordingRequest', |
|---|
| 190 | 'mcu' : 'CERNMCU', |
|---|
| 191 | 'evo' : 'EVO' |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | def __init__(self, aw, hook): |
|---|
| 195 | super(VideoEventFetcher, self).__init__(aw, hook) |
|---|
| 196 | self._alarm = None |
|---|
| 197 | |
|---|
| 198 | def _postprocess(self, obj, fossil, iface): |
|---|
| 199 | if self._alarm is not None: |
|---|
| 200 | fossil['alarm'] = self._alarm |
|---|
| 201 | |
|---|
| 202 | return fossil |
|---|
| 203 | |
|---|
| 204 | def video(self, idList, alarm = None): |
|---|
| 205 | idx = IndexesHolder().getById('collaboration'); |
|---|
| 206 | bookings = [] |
|---|
| 207 | dateFormat = '%d/%m/%Y' |
|---|
| 208 | self._alarm = alarm |
|---|
| 209 | |
|---|
| 210 | for id in idList: |
|---|
| 211 | tempBookings = idx.getBookings(self.ID_TO_IDX[id], "conferenceStartDate", |
|---|
| 212 | self._orderBy, self._fromDT, self._toDT, |
|---|
| 213 | 'UTC', False, None, None, False, dateFormat) |
|---|
| 214 | bookings.extend(tempBookings.getResults()) |
|---|
| 215 | |
|---|
| 216 | """ Iterate the bookings and yield the results for fossilization, the form |
|---|
| 217 | by this point should be objs[i] = tuple('arranger', [CSBooking Objects]). |
|---|
| 218 | """ |
|---|
| 219 | def _iter_bookings(objs): |
|---|
| 220 | for obj in objs: |
|---|
| 221 | for bk in obj[1]: |
|---|
| 222 | bk._conf = obj[0] # Ensure all CSBookings are aware of their Conference |
|---|
| 223 | |
|---|
| 224 | """ This is for plugins whose structure include 'talkSelected', |
|---|
| 225 | examples of which in CERN Indico being WebcastRequest and |
|---|
| 226 | RecordingRequest. |
|---|
| 227 | """ |
|---|
| 228 | if bk.hasTalkSelection(): |
|---|
| 229 | ts = bk.getTalkSelectionList() |
|---|
| 230 | contributions = [] |
|---|
| 231 | |
|---|
| 232 | if ts is None: # No individual talks, therefore an event for every contribution |
|---|
| 233 | contributions = bk._conf.getContributionList() |
|---|
| 234 | else: |
|---|
| 235 | for contribId in ts: |
|---|
| 236 | tempContrib = bk._conf.getContributionById(contribId) |
|---|
| 237 | contributions.append(tempContrib) |
|---|
| 238 | |
|---|
| 239 | if len(contributions) == 0: # If we are here, no contributions but a request exists. |
|---|
| 240 | bk.setStartDate(bk._conf.getStartDate()) |
|---|
| 241 | bk.setEndDate(bk._conf.getEndDate()) |
|---|
| 242 | yield bk |
|---|
| 243 | else: # contributions is the list of all to be exported now |
|---|
| 244 | for contrib in contributions: |
|---|
| 245 | if contrib.isScheduled(): |
|---|
| 246 | bk.setStartDate(contrib.getStartDate()) |
|---|
| 247 | bk.setEndDate(contrib.getEndDate()) |
|---|
| 248 | else: |
|---|
| 249 | bk.setStartDate(bk._conf.getStartDate()) |
|---|
| 250 | bk.setEndDate(bk._conf.getEndDate()) |
|---|
| 251 | yield bk |
|---|
| 252 | |
|---|
| 253 | continue |
|---|
| 254 | |
|---|
| 255 | yield bk |
|---|
| 256 | |
|---|
| 257 | """ Simple filter, as this method can return None for Pending and True |
|---|
| 258 | for accepted, both are valid booking statuses for exporting. |
|---|
| 259 | """ |
|---|
| 260 | def filter(obj): |
|---|
| 261 | return obj.getAcceptRejectStatus() is not False |
|---|
| 262 | |
|---|
| 263 | iface = self.DETAIL_INTERFACES.get('all') |
|---|
| 264 | |
|---|
| 265 | for booking in self._process(_iter_bookings(bookings), filter, iface): |
|---|
| 266 | yield booking |
|---|