source: indico/indico/MaKaC/plugins/Collaboration/CERNMCU/collaboration.py @ b89cb42

burotelhello-world-walkthroughipv6new-webexv0.97-seriesv0.98-seriesv0.98.2v0.98.3v0.98b1v0.98b2v0.99v1.0v1.1
Last change on this file since b89cb42 was b89cb42, checked in by Pedro Ferreira <jose.pedro.ferreira@…>, 3 years ago

[FIX] CERNMCU SIP support and Adhoc participants

  • SIP participants were not being correctly created;
  • Improved a bit the participant creation/deletion cycle;
  • refactored some areas of the code, so that it is more maintainable - some functions were too long;
  • made it so that Ad Hoc participants are removed on stop and refresh;
  • Property mode set to 100644
File size: 43.7 KB
Line 
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
21from MaKaC.common.timezoneUtils import nowutc, setAdjustedDate, getAdjustedDate
22from MaKaC.plugins.Collaboration.base import CSBookingBase
23from MaKaC.common.utils import formatDateTime, validIP, unicodeLength, unicodeSlice
24from MaKaC.plugins.Collaboration.CERNMCU.common import CERNMCUException, \
25    ParticipantPerson, ParticipantRoom, getCERNMCUOptionValueByName, \
26    CERNMCUError, handleSocketError, getMinStartDate, getMaxEndDate
27from MaKaC.plugins.Collaboration.CERNMCU.mcu import MCU, MCUConfCommonParams, MCUTime, \
28    paramsForLog, MCUParams, MCUParticipantCommonParams, datetimeFromMCUTime
29from MaKaC.i18n import _
30
31from xmlrpclib import Fault
32from datetime import timedelta
33from MaKaC.common.logger import Logger
34from MaKaC.common.Counter import Counter
35from MaKaC.services.interface.rpc.json import unicodeToUtf8
36import socket
37import re
38from MaKaC.common.fossilize import fossilize
39
40from MaKaC.plugins.Collaboration.CERNMCU.fossils import ICSBookingIndexingFossil
41from MaKaC.common.fossilize import fossilizes
42from MaKaC.plugins.Collaboration.fossils import ICSBookingBaseConfModifFossil
43
44
45class CSBooking(CSBookingBase): #already Fossilizable
46    fossilizes(ICSBookingBaseConfModifFossil, ICSBookingIndexingFossil)
47
48    _hasTitle = True
49    _hasStart = True
50    _hasStop = True
51    _hasStartStopAll = True
52    _hasCheckStatus = True
53
54    _requiresServerCallForStart = True
55    _requiresClientCallForStart = False
56
57    _requiresServerCallForStop = True
58    _requiresClientCallForStop = False
59
60    _needsBookingParamsCheck = True
61    _needsToBeNotifiedOnView = True
62
63    _hasEventDisplay = True
64
65    _commonIndexes = ["All Videoconference"]
66
67    _simpleParameters = {
68        "name": (str, ''),
69        "description": (str, ''),
70        "id": (str, ''),
71        "displayPin": (bool, False)}
72
73    _complexParameters = ["pin", "hasPin", "autoGenerateId", "customId", "participants"]
74
75    _participantNameRE = re.compile("^i-c(\d+)b(\d+)p(\d+)$")
76
77    def __init__(self, bookingType, conf):
78        CSBookingBase.__init__(self, bookingType, conf)
79
80        self._oldName = None
81        self._pin = None
82        self._autoGeneratedId = None #boolean storing if the id was generated by Indico (True) or chosen by user (False)
83        self._customId = None #the custom id chosen by the user, if any
84
85        self._participants = {} #{id, Participant object}
86        self._participantIdCounter = Counter(1)
87        self._numberOfConnectedParticipants = 0
88        self._numberOfDisconnectedParticipants = 0
89
90        self._created = False
91        self._creationTriesCounter = 0
92
93    def getMCUStartTime(self):
94        return MCUTime(self.getAdjustedStartDate(getCERNMCUOptionValueByName("MCUTZ")))
95
96    def getDurationSeconds(self):
97        diff = self.getEndDate() - self.getStartDate()
98
99        #we have to do this for conferences where the start and end dates are on both sides of a summer time change
100        #because the MCU is not timezone aware (blame the guy who programmed it)
101        mcuTimezone = getCERNMCUOptionValueByName("MCUTZ")
102        timeChangeDifference = self.getAdjustedStartDate(mcuTimezone).utcoffset() - self.getAdjustedEndDate(mcuTimezone).utcoffset()
103        diff = diff - timeChangeDifference
104
105        return diff.days * 86400 + diff.seconds
106
107    def setDurationSeconds(self, durationSeconds):
108        #we have to do this for conferences where the start and end dates are on both sides of a summer time change
109        #because the MCU is not timezone aware (blame the guy who programmed it)
110        tempEndDate = self.getStartDate() + timedelta(seconds = durationSeconds)
111        mcuTimezone = getCERNMCUOptionValueByName("MCUTZ")
112        timeChangeDifference = self.getAdjustedStartDate(mcuTimezone).utcoffset() - getAdjustedDate(tempEndDate, tz = mcuTimezone).utcoffset()
113
114        self.setEndDate(tempEndDate + timeChangeDifference)
115
116    def setAutoGenerateId(self, autoGenerateId):
117        self._autoGeneratedId = (autoGenerateId == 'yes')
118
119    def getAutoGenerateId(self):
120        if self._autoGeneratedId:
121            return 'yes'
122        else:
123            return 'no'
124
125    def setCustomId(self, customId):
126        self._customId = customId
127
128    def getCustomId(self):
129        if self._autoGeneratedId:
130            return ''
131        else:
132            return self._customId
133
134    def getPin(self):
135        """ This method returns the pin that will be displayed in the indico page
136        """
137        return self._pin
138
139    def setPin(self, pin):
140        if not pin or pin.strip() == "":
141            self._pin = ""
142        else:
143            self._pin = pin
144
145    def getHasPin(self):
146        return self._pin is not None and len(self._pin) > 0
147
148    def setHasPin(self, value):
149        #ignore, will be called only on rollback
150        pass
151
152    #particioant methods
153    def getParticipantById(self, participantId):
154        return self._participants[participantId]
155
156    def getParticipantList(self, returnSorted = False):
157        if returnSorted:
158            keys = self._participants.keys()
159            keys.sort()
160            return [self._participants[k] for k in keys]
161        else:
162            return self._participants.values()
163
164    def getParticipants(self):
165        return fossilize(self.getParticipantList(returnSorted = True))
166
167    def setParticipants(self, participants):
168        """ Participants is an unpickled JSON object with the participant data.
169            It will be a list of dictionaries.
170        """
171
172        participantsCopy = dict(self._participants)
173        self._participants = {}
174
175        for p in participants:
176
177            participantId = p.get("participantId", None)
178
179            if participantId is None or not participantId in participantsCopy: # new participant
180                participantId = self._participantIdCounter.newCount()
181                if p["type"] == 'person':
182                    participantObject = ParticipantPerson(self, participantId, p)
183                elif p["type"] == "room":
184                    participantObject = ParticipantRoom(self, participantId, p)
185
186            else: #already existing participant
187                participantObject = participantsCopy[participantId]
188                participantObject.updateData(p)
189
190            self._participants[participantId] = participantObject
191
192        self._p_changed = 1
193
194    def updateNumberOfConnectedParticipants(self):
195        self._numberOfConnectedParticipants = 0
196        self._numberOfDisconnectedParticipants = 0
197        for participant in self.getParticipantList():
198            if participant.getCallState() == "connected":
199                self._numberOfConnectedParticipants = self._numberOfConnectedParticipants + 1
200            else:
201                self._numberOfDisconnectedParticipants = self._numberOfDisconnectedParticipants + 1
202
203    ## overriding methods
204    def _getTitle(self):
205        return self._bookingParams["name"]
206
207
208    def _checkBookingParams(self):
209
210        if len(self._bookingParams["name"].strip()) == 0:
211            raise CERNMCUException("name parameter (" + str(self._bookingParams["name"]) +") is empty for booking with id: " + str(self._id))
212
213        if unicodeLength(self._bookingParams["name"]) >= 32:
214            raise CERNMCUException("name parameter (" + str(self._bookingParams["name"]) +") is longer than 31 characters for booking with id: " + str(self._id))
215
216        self._bookingParams["name"] = self._bookingParams["name"].strip()
217
218        if len(self._bookingParams["description"].strip()) == 0:
219            raise CERNMCUException("description parameter (" + str(self._bookingParams["description"]) +") is empty for booking with id: " + str(self._id))
220
221        if not self._autoGeneratedId:
222            if len(self._customId.strip()) == 0:
223                raise CERNMCUException("customId parameter (" + str(self._customId) +") is empty for booking with id: " + str(self._id))
224            else:
225                try:
226                    int(self._customId)
227                except ValueError:
228                    raise CERNMCUException("customId parameter (" + str(self._customId) +") is not an integer for booking with id: " + str(self._id))
229                if len(self._customId.strip()) != 5:
230                    raise CERNMCUException("customId parameter (" + str(self._customId) +") is longer than 5 digits for booking with id: " + str(self._id))
231                self._customId = int(self._customId)
232
233        if self._pin:
234            try:
235                int(self._pin)
236            except ValueError:
237                raise CERNMCUException("pin parameter (" + str(self._pin) +") is not an integer for booking with id: " + str(self._id))
238
239            if len(self._pin) >= 32:
240                raise CERNMCUException("pin parameter (" + str(self._pin) +") is longer than 31 characters for booking with id: " + str(self._id))
241
242        #if self.getAdjustedStartDate('UTC')  < (nowutc()):
243        #    raise CERNMCUException("Cannot create booking in the past. Booking id: %s"% (str(self._id)))
244
245        if self.getAdjustedEndDate('UTC')  < (nowutc()):
246            raise CERNMCUException("End date cannot be in the past. Booking id: %s"% (str(self._id)))
247
248        minStartDate = getMinStartDate(self.getConference())
249        if self.getAdjustedStartDate() < minStartDate:
250            raise CERNMCUException("Cannot create a booking before the Indico event's start date. Please create it after %s"%(formatDateTime(minStartDate)))
251
252        maxEndDate = getMaxEndDate(self.getConference())
253        if self.getAdjustedStartDate() > maxEndDate:
254            raise CERNMCUException("Cannot create a booking after before the Indico event's end date. Please create it after %s"%(formatDateTime(maxEndDate)))
255
256        pSet = set()
257        ipSet = set()
258
259        for p in self._participants.itervalues():
260
261            if not validIP(p.getIp()):
262                raise CERNMCUException("Participant has not a correct ip. (ip string= " + p.getIp() + ")")
263
264            if p.getType() == 'person':
265                if not p.getFamilyName():
266                    raise CERNMCUException(_("Participant (person) does not have family name."))
267                if not p.getFirstName():
268                    raise CERNMCUException(_("Participant (person) does not have first name."))
269            elif p.getType() == 'room':
270                if not p.getName():
271                    raise CERNMCUException(_("Participant (room) does not have name."))
272
273            ip = p.getIp()
274            if ip in ipSet:
275                raise CERNMCUException(_("At least two of the participants have the same IP. Please change this"))
276            else:
277                ipSet.add(ip)
278
279        return False
280
281    def _create(self):
282        if self._autoGeneratedId:
283            if self._creationTriesCounter < 100:
284                numericId = self._plugin.getGlobalData().getNewConferenceId()
285            else:
286                return CERNMCUError('tooManyTries', "Could not obtain ID")
287        else:
288            numericId = self._customId
289
290        try:
291            mcu = MCU.getInstance()
292            params = MCUConfCommonParams(conferenceName = self._bookingParams["name"],
293                                         numericId = str(numericId),
294                                         startTime = self.getMCUStartTime(),
295                                         durationSeconds = self.getDurationSeconds(),
296                                         pin = self._pin,
297                                         description = unicodeSlice(self._bookingParams["description"], 0, 31),
298                                        )
299            Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling conference.create with params: %s""" % (self._conf.getId(), self.getId(), str(paramsForLog(params))))
300            answer = unicodeToUtf8(mcu.conference.create(params))
301            Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling conference.create. Got answer: %s""" % (self._conf.getId(), self.getId(), str(answer)))
302
303            for p in self._participants.itervalues():
304                result = self.addParticipant(p)
305                if not result is True:
306                    return result
307
308            self._statusMessage = _("Booking created")
309            self._statusClass = "statusMessageOK"
310            self._bookingParams["id"] = numericId
311            self._oldName = self._bookingParams["name"]
312            self._created = True
313            self.checkCanStart()
314
315        except Fault, e:
316            Logger.get('CERNMCU').warning("""Evt:%s, calling conference.create. Got error: %s""" % (self._conf.getId(), str(e)))
317            return self.handleFault('create', e)
318
319        except socket.error, e:
320            handleSocketError(e)
321
322    def _modify(self, oldBookingParams):
323        """ Relays to the MCU the changes done by the user to the Indico booking object.
324            For the participants, we retrieve a list of existing participants.
325            If a participant is both in the MCU and the Indico booking, it is not touched.
326            Thus, we only delete in the MCU those having been removed, and we only create only those who have been added.
327            In this way we avoid disconnection of already connected participants.
328        """
329
330        if self._created:
331
332            if self._autoGeneratedId:
333                numericId = self._bookingParams["id"]
334            else:
335                numericId = self._customId
336
337            try:
338                mcu = MCU.getInstance()
339                params = MCUConfCommonParams(
340                    conferenceName = self._oldName,
341                    newConferenceName = self._bookingParams["name"],
342                    numericId = str(numericId),
343                    startTime = self.getMCUStartTime(),
344                    durationSeconds = self.getDurationSeconds(),
345                    pin = self._pin,
346                    description = self._bookingParams["description"])
347
348                Logger.get('CERNMCU').debug(
349                    "Evt: %s, booking: %s, calling conference.modify with params: %s" %
350                    (self._conf.getId(), self.getId(), str(paramsForLog(params))))
351
352                answer = unicodeToUtf8(mcu.conference.modify(params))
353
354                Logger.get('CERNMCU').debug(
355                    "Evt: %s, booking: %s, calling conference.modify. Got answer: %s" %
356                    (self._conf.getId(), self.getId(), str(answer)))
357                self._bookingParams["id"] = numericId
358                self._oldName = self._bookingParams["name"]
359
360                #we take care of the participants
361                previousParticipantList = oldBookingParams["participants"]
362                previousParticipantDict = dict(
363                    (pData["participantName"], pData)
364                    for pData in previousParticipantList)
365
366                # participants indexed by name
367                # (maybe they should be indexed by name by default?)
368                participantDict = dict(
369                    (p.getParticipantName(), p)
370                    for p in self._participants.itervalues())
371
372                currParticipantSet = set(participantDict)
373                prevParticipantSet = set(previousParticipantDict)
374
375                # lists of participants to be added, removed or edited
376                addList = currParticipantSet - prevParticipantSet
377                editList = currParticipantSet.intersection(prevParticipantSet)
378                removeList = prevParticipantSet - editList
379
380                # we add the new participants
381                for name in addList:
382                    result = self.addParticipant(participantDict[name])
383                    if not result is True:
384                            return result
385
386                #for the participants that exist both in Indico and the MCU,
387                #we re-add them if the IP has changed,
388                #or we just modify the displayName if the IP has not changed
389                for name in editList:
390
391                    participant = participantDict[name]
392                    existingData = previousParticipantDict[name]
393
394                    if (existingData["ip"] != participant.getIp() and \
395                        existingData["participantType"] == "by_address") or \
396                        (existingData["participantProtocol"] != participant.getParticipantProtocol()):
397
398                        result = self.modifyParticipantAllData(participant,
399                                                               existingData)
400
401                        if not result is True:
402                            return result
403
404                    elif existingData["displayName"] != participant.getDisplayName():
405                        result = self.modifyParticipantDisplayName(participantName,
406                                                                   participant.getParticipantType(),
407                                                                   participant.getParticipantProtocol(),
408                                                                   participant.getDisplayName())
409                        if not result is True:
410                            return result
411
412                #we remove the participants existed previously but have been removed
413                for name in removeList:
414                    existingData = previousParticipantDict[name]
415                    if not existingData["participantType"] == "by_name" and not existingData["callState"] == "disconnected":
416                        result = self.removeParticipant(existingData["participantName"],
417                                                        existingData["participantType"],
418                                                        existingData["participantProtocol"])
419                        if not result is True:
420                            return result
421
422                self._created = True
423                self.checkCanStart()
424
425            except Fault, e:
426                Logger.get('CERNMCU').warning("""Evt:%s, booking:%s, calling conference.modify. Got error: %s""" % (self._conf.getId(), self.getId(), str(e)))
427                return self.handleFault('modify', e)
428
429            except socket.error, e:
430                handleSocketError(e)
431
432        else: #not yet created because of errors: try to recreate
433            self._create()
434
435
436    def _start(self):
437        self._checkStatus()
438        if self._canBeStarted:
439            for p in self.getParticipantList(returnSorted = True):
440                self._connectParticipant(p)
441
442            self._statusMessage = _("Conference started!")
443            self.checkCanStart(changeMessage = False)
444
445    def _connectParticipant(self, participant):
446        """
447        Tries to actually connect a participant using the MCU.
448        Returns a Fault object in case something goes wrong,
449        None otherwise
450        """
451
452        try:
453            mcu = MCU.getInstance()
454            if (participant.getCallState() == "disconnected" or participant.getCallState() == "dormant") and participant.getParticipantType() != "ad_hoc":
455                params = MCUParams(conferenceName = self._bookingParams["name"],
456                                   participantName = participant.getParticipantName(),
457                                   participantType = participant.getParticipantType(),
458                                   participantProtocol = participant.getParticipantProtocol())
459                Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participant.connect with params: %s""" % (self._conf.getId(), self.getId(), str(paramsForLog(params))))
460                answer = unicodeToUtf8(mcu.participant.connect(params))
461                Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participant.connect. Got answer: %s""" % (self._conf.getId(), self.getId(), str(answer)))
462
463                participant.setCallState("connected")
464
465        except Fault, e:
466            Logger.get('CERNMCU').warning("""Evt:%s, booking:%s, calling participant.connect. Got error: %s""" % (self._conf.getId(), self.getId(), str(e)))
467            fault = self.handleFault('start', e)
468            if fault:
469                return fault
470
471        except socket.error, e:
472            handleSocketError(e)
473
474    def startSingleParticipant(self, participant):
475        self._checkStatus()
476        if self._canBeStarted:
477            result = self._connectParticipant(participant)
478            self.checkCanStart(changeMessage = False)
479            return result
480
481    def _stop(self):
482        self._checkStatus()
483        if self._canBeStopped:
484            for p in self.getParticipantList(returnSorted = True):
485                self._disconnectParticipant(p)
486
487            self.checkCanStart()
488            self._statusMessage = _("Conference stopped")
489            self._statusClass = "statusMessageOther"
490
491    def _disconnectParticipant(self, participant):
492        try:
493            mcu = MCU.getInstance()
494
495            if participant.getCallState() == "connected":
496                params = MCUParams(conferenceName = self._bookingParams["name"],
497                                   participantName = participant.getParticipantName(),
498                                   participantType = participant.getParticipantType(),
499                                   participantProtocol = participant.getParticipantProtocol())
500
501                Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participant.disconnect with params: %s""" % (self._conf.getId(), self.getId(), str(paramsForLog(params))))
502                answer = unicodeToUtf8(mcu.participant.disconnect(params))
503                Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participant.disconnect. Got answer: %s""" % (self._conf.getId(), self.getId(), str(answer)))
504
505                participant.setCallState("disconnected")
506
507            # if participant is Ad Hoc, get rid of it
508            if not participant.isCreatedByIndico():
509                # not a local...
510                del self._participants[participant.getId()]
511
512        except Fault, e:
513            Logger.get('CERNMCU').warning("""Evt:%s, booking:%s, calling participant.disconnect. Got error: %s""" % (self._conf.getId(), self.getId(), str(e)))
514            fault = self.handleFault('stop', e)
515            if fault:
516                return fault
517
518        except socket.error, e:
519            handleSocketError(e)
520
521    def stopSingleParticipant(self, participant):
522        self._checkStatus()
523        if self._canBeStopped:
524            result = self._disconnectParticipant(participant)
525            self.checkCanStart(changeMessage = False)
526            return result
527
528    def _notifyOnView(self):
529        self.checkCanStart()
530
531    def _checkStatus(self):
532        if self._created:
533            self.queryConference()
534            self._cleanupAdHocParticipants()
535            self.checkCanStart()
536
537    def _delete(self):
538        if self._created:
539            name = self._bookingParams["name"]
540
541            try:
542                mcu = MCU.getInstance()
543                params = MCUParams(conferenceName = name)
544                Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling conference.destroy with params: %s""" % (self._conf.getId(), self.getId(), str(paramsForLog(params))))
545                answer = unicodeToUtf8(mcu.conference.destroy(params))
546                Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling conference.destroy. Got answer: %s""" % (self._conf.getId(), self.getId(), str(answer)))
547
548                self._created = False
549
550            except Fault, e:
551                Logger.get('CERNMCU').warning("""Evt:%s, booking:%s, calling conference.destroy. Got error: %s""" % (self._conf.getId(), self.getId(), str(e)))
552                if e.faultCode == 4: #conference didn't exist in the MCU, but we delete it from Indico anyway
553                    pass
554                else:
555                    return self.handleFault('delete', e)
556            except socket.error, e:
557                handleSocketError(e)
558
559    ## end of overrided methods
560
561    def addParticipant(self, participant):
562        """ Adds a participant to the MCU conference represented by this booking.
563            participant: a Participant object
564            returns: True if successful, a CERNMCUError if there is a problem in some cases, raises an Exception in others
565        """
566        try:
567            mcu = MCU.getInstance()
568
569            params = MCUParticipantCommonParams(conferenceName = self._bookingParams["name"],
570                                                participantName = participant.getParticipantName(),
571                                                displayNameOverrideValue = participant.getDisplayName(),
572                                                address = participant.getIp(),
573                                                participantProtocol = participant.getParticipantProtocol()
574                                                )
575            Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participant.add with params: %s""" % (self._conf.getId(), self.getId(), str(paramsForLog(params))))
576            answer = unicodeToUtf8(mcu.participant.add(params))
577            Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participant.add. Got answer: %s""" % (self._conf.getId(), self.getId(), str(answer)))
578
579
580            self.checkCanStart()
581            if self._numberOfConnectedParticipants > 0:
582                #we have to connect the new participant
583                self._connectParticipant(participant)
584                self.checkCanStart(changeMessage = False)
585
586
587            return True
588
589        except Fault, e:
590            Logger.get('CERNMCU').warning("""Evt:%s, calling participant.add. Got error: %s""" % (self._conf.getId(), str(e)))
591            fault = self.handleFault('add', e)
592            fault.setInfo(participant.getIp())
593            return fault
594
595    def modifyParticipantAllData(self, participant, existingData):
596        """
597        Modifies a participant, by removing and re-adding.
598        participant is a Participant object and existingData a dictionary
599        """
600
601        result = self.removeParticipant(
602            existingData["participantName"],
603            existingData["participantType"],
604            existingData["participantProtocol"])
605
606        if not result is True:
607            return result
608
609        result = self.addParticipant(participant)
610
611        return result
612
613    def modifyParticipantDisplayName(self, participantName, participantType, participantProtocol, newDisplayName):
614        """ Modifies the display name of a participant
615            returns: True if successful, a CERNMCUError if there is a problem in some cases, raises an Exception in others
616        """
617        try:
618            mcu = MCU.getInstance()
619
620            params = MCUParticipantCommonParams(
621                conferenceName = self._bookingParams["name"],
622                participantName = participantName,
623                participantType = participantType,
624                participantProtocol = participantProtocol,
625                displayNameOverrideValue = newDisplayName)
626
627            Logger.get('CERNMCU').info(
628                "Evt: %s, booking: %s, calling participant.modify with params: %s" %
629                (self._conf.getId(), self.getId(), str(paramsForLog(params))))
630
631            answer = unicodeToUtf8(mcu.participant.modify(params))
632
633            Logger.get('CERNMCU').info(
634                "Evt:%s, booking:%s, calling participant.modify. Got answer: %s" %
635                (self._conf.getId(), self.getId(), str(answer)))
636
637            return True
638        except Fault, e:
639            Logger.get('CERNMCU').warning("""Evt:%s, calling participant.modify. Got error: %s""" % (self._conf.getId(), str(e)))
640            fault = self.handleFault('modifyParticipant', e)
641            fault.setInfo(participantName)
642            return fault
643
644    def removeParticipant(self, participantName, participantType, participantProtocol):
645        """ Removes a participant to the MCU conference represented by this booking.
646            participant: a participant name like the one returned by Participant.getParticipantName
647            returns: True if successful, a CERNMCUError if there is a problem in some cases, raises an Exception in others
648        """
649        try:
650            mcu = MCU.getInstance()
651
652            params = MCUParams(conferenceName = self._bookingParams["name"],
653                               participantName = participantName,
654                               participantType = participantType,
655                               participantProtocol = participantProtocol)
656            Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participant.remove with params: %s""" % (self._conf.getId(), self.getId(), str(paramsForLog(params))))
657            answer = unicodeToUtf8(mcu.participant.remove(params))
658            Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participant.remove. Got answer: %s""" % (self._conf.getId(), self.getId(), str(answer)))
659            return True
660        except Fault, e:
661            Logger.get('CERNMCU').warning("""Evt:%s, calling participant.remove. Got error: %s""" % (self._conf.getId(), str(e)))
662            return self.handleFault('remove', e)
663
664    def checkCanStart(self, changeMessage = True):
665        if self._created:
666            now = nowutc()
667            self._canBeNotifiedOfEventDateChanges = CSBooking._canBeNotifiedOfEventDateChanges
668            self.updateNumberOfConnectedParticipants()
669            if self.getStartDate() < now and self.getEndDate() > now:
670                self._canBeStarted = self._numberOfDisconnectedParticipants > 0
671                self._canBeStopped = self._numberOfConnectedParticipants > 0
672                if changeMessage:
673                    self._statusMessage = _("Ready to start!")
674                    self._statusClass = "statusMessageOK"
675            else:
676                self._canBeStarted = False
677                self._canBeStopped = False
678                if now > self.getEndDate() and changeMessage:
679                    self._statusMessage = _("Already took place")
680                    self._statusClass = "statusMessageOther"
681                    self._needsToBeNotifiedOfDateChanges = False
682                    self._canBeNotifiedOfEventDateChanges = False
683
684    def _cleanupAdHocParticipants(self):
685        """
686        Remove ad-hoc participants that had been disconnected
687        """
688
689        for pId, participant in self._participants.items():
690            if participant.getCallState() == "disconnected" and \
691               not participant.isCreatedByIndico():
692                # disconnected, not local
693                # remove from the participant dictionary
694                del self._participants[pId]
695
696
697    def _updateAdHocParticipant(self, name, attrs):
698        """
699        Sets the data for a new ad-hoc participant
700        """
701
702        newId = self._participantIdCounter.newCount()
703        self._participants[newId] = ParticipantRoom(self,
704                                                    newId,
705                                                    {"name": attrs["displayName"],
706                                                     "ip": attrs["ip"]},
707                                                    participantName=name,
708                                                    createdByIndico=False)
709        self._participants[newId].setParticipantType(attrs["participantType"])
710        self._participants[newId].setParticipantProtocol(attrs["participantProtocol"])
711        self._participants[newId].setCallState(attrs["callState"])
712
713
714    def _processQueryConference(self, conference):
715        """
716        Processe an individual conference result from an `enumerate` query
717        This is a helper method for queryConference
718        """
719
720        # we update the local Indico description
721        remoteDescription = conference.get("description", '')
722        if unicodeLength(remoteDescription) < 31 or \
723               not self._bookingParams["description"].startswith(remoteDescription):
724            self._bookingParams["description"] = remoteDescription
725
726        # we update the local id
727        self._bookingParams["id"] = conference.get("numericId", '')
728        if not self._autoGeneratedId:
729            self._customId = self._bookingParams["id"]
730
731        self._oldName = self._bookingParams["name"]
732
733        # we update the local PIN
734        self._pin = conference.get("pin", '')
735
736        # we obtain the remote list of participants in the MCU
737        remoteParticipants = self.queryParticipants()
738
739        # participants/ids indexed by Name
740        # (maybe they should be indexed by name by default?)
741        participantDict = dict(
742            (p.getParticipantName(), (k,p))
743            for k, p in self._participants.iteritems())
744
745        remoteNames = set(remoteParticipants)
746        localNames = set(p.getParticipantName()
747                         for p in self._participants.itervalues())
748
749        # participants that only exist locally (not yet added remotely)
750        localOnlyNames = localNames - remoteNames
751        # participants that exist both locally and in the MCU
752        commonNames = localNames & remoteNames
753        # participants that were added remotely (MCU)
754        mcuOnlyNames = remoteNames - localNames
755
756        # if a participant has been removed remotely, we remove it locally
757        # don't use iteritems, we need a copy
758        for name in localOnlyNames:
759            pId, __ = participantDict[name]
760            del self._participants[pId]
761
762        # for participants that are both in the MCU and Indico,
763        # we update some of their attributes in Indico
764        for name in commonNames:
765            pId, participant = participantDict[name]
766            remoteData = remoteParticipants[name]
767            remoteDispName = remoteData["displayName"]
768
769            if participant.getDisplayName() != remoteDispName:
770                # the display name was updated remotely
771                if participant.getType() == 'room':
772                    participant.setName(remoteDispName)
773                    participant.setInstitution(remoteDispName)
774                else:
775                    self._participants[pId] = \
776                        ParticipantRoom(self,
777                                        pId,
778                                        {"name": remoteDispName},
779                                        createdByIndico=True)
780
781            participant.setParticipantType(remoteData["participantType"])
782            participant.setParticipantProtocol(remoteData["participantProtocol"])
783            participant.setCallState(remoteData["callState"])
784
785        # we try to add to Indico participants that were added
786        # to the MCU (by an operator or by people joining ad hoc)
787        for name in mcuOnlyNames:
788
789            attrs = remoteParticipants[name]
790
791            # only for participants that are not already in
792            # disconnected state
793            if attrs["callState"] != "disconnected":
794                self._updateAdHocParticipant(name, attrs)
795
796        startTime = conference.get("startTime", None)
797        if startTime:
798            adjustedDate = setAdjustedDate(datetimeFromMCUTime(startTime), tz = getCERNMCUOptionValueByName("MCUTZ"))
799            self.setStartDate(adjustedDate)
800
801        durationSeconds = conference.get("durationSeconds", None)
802        if durationSeconds:
803            self.setDurationSeconds(durationSeconds)
804        else:
805            self.setEndDate(self.getStartDate())
806
807
808    def queryConference(self):
809        """ Queries the MCU for information about a conference with the same conferenceName as self._bookingParams["name"]
810            If found, the attributes of the Indico booking object are updated, included the participants.
811            If not found, an error message will appear and the conference will be marked as not created.
812        """
813        try:
814            mcu = MCU.getInstance()
815
816            enumerateID = None
817            keepAsking = True
818            found = False
819
820            while keepAsking and not found:
821                if enumerateID:
822                    params = MCUParams(enumerateID = enumerateID)
823                else:
824                    params = MCUParams()
825
826                Logger.get('CERNMCU').info(
827                    "Evt:%s, booking:%s, calling conference.enumerate with params: %s"
828                    % (self._conf.getId(), self.getId(), str(paramsForLog(params))))
829
830                answer = unicodeToUtf8(mcu.conference.enumerate(params))
831                #un-comment to print all the garbage about other conferences received
832                # Logger.get('CERNMCU').debug(
833                #    "Evt:%s, booking:%s, calling conference.enumerate. Got answer: %s" %
834                #    (self._conf.getId(), self.getId(), str(answer)))
835
836                # we loop through all the returned output
837                for conference in answer.get("conferences", []):
838                    if conference.get("conferenceName", None) == \
839                           self._bookingParams["name"]:
840
841                        # we found our conference
842                        found = True
843
844                        Logger.get('CERNMCU').info(
845                            "Evt:%s, booking:%s, calling conference.enumerate. Found conference:\n%s" %
846                            (self._conf.getId(), self.getId(), str(conference)))
847
848                        self._processQueryConference(conference)
849
850                        # mark it as created, and stop the cycle
851                        self._created = True
852                        break
853
854
855                enumerateID = answer.get("enumerateID", None)
856                keepAsking = enumerateID is not None
857
858            if not found:
859                self._created = False
860
861            self._p_changed = 1
862
863        except Fault, e:
864            Logger.get('CERNMCU').warning("""Evt:%s, booking:%s, calling participants.enumerate. Got error: %s""" % (self._conf.getId(), self.getId(), str(e)))
865            raise e
866        except socket.error, e:
867            handleSocketError(e)
868
869    def queryParticipants(self):
870        """ Queries the MCU for the list of participants of this conference,
871            using participant.enumerate.
872            Each "participant" returned by the MCU is a dictionary with several keys.
873            Only the "participantName", "displayName", "address", "callState" and "conferenceName" values are important for us.
874            If the "conferenceName" value matches our booking name, then we have a participant from our conference.
875            We return a dictionary like this:
876            -key: the participantName of a participant
877            -value: a dictionary, with the participant data retrieved from the MCU
878        """
879        try:
880            mcu = MCU.getInstance()
881            participants = {} #key = participantName, value = {displayName, ip}
882
883            enumerateID = None
884            keepAsking = True
885
886            while keepAsking:
887                if enumerateID:
888                    params = MCUParams(enumerateID = enumerateID)
889                else:
890                    params = MCUParams()
891
892                Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participants.enumerate with params: %s""" % (self._conf.getId(), self.getId(), str(paramsForLog(params))))
893                answer = unicodeToUtf8(mcu.participant.enumerate(params))
894                #un-comment to print all the garbage received about other participants
895                #Logger.get('CERNMCU').debug("""Evt:%s, booking:%s, calling participants.enumerate. Got answer: %s""" % (self._conf.getId(), self.getId(), str(answer)))
896
897                for participant in answer.get("participants", []):
898                    if participant.get("conferenceName", None) == self._bookingParams["name"]:
899                        Logger.get('CERNMCU').info("""Evt:%s, booking:%s, calling participants.enumerate. Found participant:\n%s""" % (self._conf.getId(), self.getId(), str(participant)))
900
901                        name = participant.get("participantName", None)
902                        displayName = participant.get("displayName", None)
903                        participantType = participant.get("participantType", None)
904                        participantProtocol = participant.get("participantProtocol", None)
905                        callState = participant.get("callState", None)
906                        ip = participant.get("address", None)
907
908                        if name:
909                            participants[name] = {"displayName": displayName,
910                                                  "ip": ip,
911                                                  "participantType": participantType,
912                                                  "participantProtocol": participantProtocol,
913                                                  "callState": callState}
914
915                enumerateID = answer.get("enumerateID", None)
916                keepAsking = enumerateID is not None
917
918            return participants
919
920
921        except Fault, e:
922            Logger.get('CERNMCU').warning("""Evt:%s, booking:%s, calling participants.enumerate. Got error: %s""" % (self._conf.getId(), self.getId(), str(e)))
923            raise e
924        except socket.error, e:
925            handleSocketError(e)
926
927    def handleFault(self, operation, e):
928
929        if e.faultCode == 14: #authorization error
930            raise CERNMCUException(_("Authorization Error while Indico tried to connect to the MCU.\nPlease report to Indico support."), e)
931
932        if operation == 'create' or operation == 'modify':
933            if e.faultCode == 2: #duplicated name
934                fault = CERNMCUError(e.faultCode)
935                return fault
936            if e.faultCode == 6: #too many conferences in MCU, no more can be created
937                fault = CERNMCUError(e.faultCode)
938                return fault
939            elif e.faultCode == 18:  #duplicated ID
940                if self._autoGeneratedId:
941                    self._creationTriesCounter = self._creationTriesCounter + 1
942                    return self._create()
943                else:
944                    fault = CERNMCUError(e.faultCode)
945                    return fault
946            else: #another error
947                raise CERNMCUException(_("Problem with the MCU while creating or modifying a conference"), e)
948
949        elif operation == 'delete':
950            raise CERNMCUException(_("Problem with the MCU while removing a conference"), e)
951
952        elif operation == 'add':
953            if e.faultCode == 3: #duplicate participant name
954                fault = CERNMCUError(e.faultCode)
955                return fault
956            if e.faultCode == 7: #too many participants in MCU, no more can be created
957                fault = CERNMCUError(e.faultCode)
958                return fault
959            else:
960                raise CERNMCUException(_("Problem with the MCU while adding a participant"), e)
961
962        elif operation == 'modifyParticipant':
963            raise CERNMCUException(_("Problem with the MCU while modifying the name of a participant"), e)
964
965        elif operation == 'remove':
966            raise CERNMCUException(_("Problem with the MCU while removing a participant"), e)
967
968        elif operation == 'start':
969            if e.faultCode == 201: #we tried to connect a participant that could not be connected
970                fault = CERNMCUError(e.faultCode)
971                return fault
972            raise CERNMCUException(_("Problem with the MCU while starting a conference"), e)
973
974        elif operation == 'stop':
975            if e.faultCode == 201: #we tried to disconnect a participant that was not connected
976                fault = CERNMCUError(e.faultCode)
977                return fault
978            elif e.faultCode == 5: #we tried to disconnect a participant that didn't exist
979                fault = CERNMCUError(e.faultCode)
980                return fault
981            else:
982                raise CERNMCUException(_("Problem with the MCU while stopping a conference"), e)
Note: See TracBrowser for help on using the repository browser.