def __init__(self): self.wbRooms = {} #diccionario con rooms creados (no nec. abiertos) self.wbClients = {} #usuarios conectados self.wbClientStatus = {} #status de la pizarra de cada cliente (fuera de room) self.wbQueues = WBQueuer(self.wbClients)#adm. colas de rooms abiertos self.wbRoomsIA = [] #rooms IA que deberian estar funcionando actualmente self.wbRoomsPA = [] #rooms PA que deberian estar funcionando actualmente self.wbRoomRequests = [] #rooms solicitados self.offlineQuestions = {} self.sessions = SessionAdmin(self) self.cronner = Cronner() self.cronner.addJob(ClassRoomOpenTask(self)) self.cronner.addJob(ClassRoomClosingTask(self)) self.cronner.addJob(ClassRoomCloseTask(self)) self.cronner.addJob(ClassRoomTimeTask(self)) self.cronner.addJob(ClassRoomRemoveTask(self))
class WBServer(pb.Viewable): """Servidor que mantiene el estado del WhiteBoard y el Chat, como asi tambien una lista de clientes conectados al mismo, entre los que difunde los cambios realizados sobre el WhiteBoard. """ def __init__(self): self.wbRooms = {} #diccionario con rooms creados (no nec. abiertos) self.wbClients = {} #usuarios conectados self.wbClientStatus = {} #status de la pizarra de cada cliente (fuera de room) self.wbQueues = WBQueuer(self.wbClients)#adm. colas de rooms abiertos self.wbRoomsIA = [] #rooms IA que deberian estar funcionando actualmente self.wbRoomsPA = [] #rooms PA que deberian estar funcionando actualmente self.wbRoomRequests = [] #rooms solicitados self.offlineQuestions = {} self.sessions = SessionAdmin(self) self.cronner = Cronner() self.cronner.addJob(ClassRoomOpenTask(self)) self.cronner.addJob(ClassRoomClosingTask(self)) self.cronner.addJob(ClassRoomCloseTask(self)) self.cronner.addJob(ClassRoomTimeTask(self)) self.cronner.addJob(ClassRoomRemoveTask(self)) def requestRoom(self, roomId): if roomId in self.wbRooms.keys(): return if self.openRoom(roomId, self.wbRoomsIA, IACLASS): self.manageNextPupil(roomId, self.sessions.tutorEnter) elif self.openRoom(roomId, self.wbRoomsPA, PACLASS): self.sessions.tutorEnter(roomId, WAITING) self.wbClients[roomId].loadPAClass() self.setClientWindow(roomId, IN_WAITING) else: question = "Abrir IA extra?" d = self.askClient(roomId, "IA extra", question) d.addCallback(self.openExtraRoom, roomId) def openRoom(self, roomId, roomList, kind): rooms = [str(i[0]) for i in roomList] opened = False if roomId in rooms: room = roomList[rooms.index(roomId)] self.addRoom(room, kind) opened = True return opened def openExtraRoom(self, answer, roomId): if answer: room = (roomId, None) self.addRoom(room, EXTRA_IACLASS) self.sessionStart(roomId, EXTRA_IACLASS) if roomId not in self.wbRoomRequests: self.wbRoomRequests.append(roomId) def addRoom(self, roomDesc, roomType): """Agrega el room de tutorId a los rooms actuales e inicializa su cola de espera (un unico room por tutorId); el status del room corresponde al status de tutorId. """ assert(roomDesc[0] not in self.wbRooms.keys()) tutorStatus = self.wbClientStatus[roomDesc[0]] self.wbRooms[roomDesc[0]] = WBRoom(roomDesc, roomType, tutorStatus) self.wbClients[roomDesc[0]].roomId = roomDesc[0] self.wbQueues.addQueue(roomDesc[0]) def addClient(self, avatar, avatarId): """Agrega al avatar a la lista de usuarios actuales, indexado con su avatarId. Tambien agrega status de su pizarra. """ self.wbClients[avatarId] = avatar avatar.setMyWBColor() self.wbClientStatus[avatarId] = WBStatus() def itemId(self, avId, tagId): return "%s:%d" % (avId, tagId) def removeRoom(self, roomId): """Quita el room roomId de los rooms actuales; ademas quita a los clientes que esperaban por dicho room (y la correspondiente cola) y desconecta del mismo a los usuarios que estaban dentro. """ self.setClientWindow(roomId, OUT) #graba el log del chat self.writeLog(roomId) #si estaba respondiendo pregunta offline if self.wbClients[roomId].questionId != None: self.sessions.closeSession(roomId) self.wbClients[roomId].questionId = None return #si estaba en espera de ser creado o ya fue eliminado if roomId in self.wbRoomRequests: self.wbRoomRequests.remove(roomId) if roomId not in self.wbRooms.keys(): return room = self.wbRooms[roomId] #avisa que se cerro (cuando todavia tendria que estar) #cierra la cola if room.roomPupilCurrent() != None: d = self.wbClients[roomId].saveClassStatus() d.addCallback(lambda a: self.sessions.tutorQuit(roomId)) else: self.sessions.tutorQuit(roomId) self.exitViewers(roomId) self.exitPupils(roomId) self.wbClients[roomId].roomId = None try: self.wbClients[roomId].cleanWhiteBoard() except (pb.DeadReferenceError): pass del(self.wbRooms[roomId]) def removeClient(self, perspective): """Quita al cliente de id clientId de la lista de clientes actuales. """ #si era tutor elimino su room if perspective.perspective_whoami() == TUTOR: self.removeRoom(perspective.avId) else: if perspective.viewing != None: self.wbRooms[perspective.viewing].roomViewerExit(perspective.avId) #si esperaba lo elimino de la cola if perspective.waitingInQueue != None: self.wbQueues.leaveQueue(perspective.waitingInQueue, perspective.avId) #si estaba en "observacion" if perspective.waitingInRoom != None: self.wbRooms[perspective.waitingInRoom].roomPupilStopWaiting() avTutor = self.wbClients[perspective.waitingInRoom] avTutor.cleanWhiteBoard() self.exitViewers(perspective.waitingInRoom) self.manageNextPupil(perspective.waitingInRoom, self.sessions.pupilEnd) if perspective.roomId != None: self.wbRooms[perspective.roomId].roomPupilExit(perspective.avId) discount = (now().minute - perspective.lastMinute) % 60 perspective.discountIA(discount) avTutor = self.wbClients[perspective.roomId] try: self.notifyClient(perspective.roomId, "El alumno abandono") avTutor.saveClassStatus() avTutor.cleanWhiteBoard() except (pb.DeadReferenceError): pass self.exitViewers(perspective.roomId) self.manageNextPupil(perspective.roomId, self.sessions.pupilEnd) del(self.wbClients[perspective.avId]) del(self.wbClientStatus[perspective.avId]) def sessionStart(self, roomId, kind): if kind == PACLASS: self.sessions.newSession(roomId, kind, WAITING) self.setClientWindow(roomId, IN_WAITING) else: pupilId = self._nextPupil(roomId) if pupilId != None: self.sessions.newSession(roomId, kind, DECIDING, pupilId) self.setClientWindow(roomId, IN_DECIDING) else: self.sessions.newSession(roomId, kind, WAITING) self.setClientWindow(roomId, IN_WAITING) def _nextPupil(self, roomId): if roomId not in self.wbRooms.keys(): return None if self.wbRooms[roomId].roomIsClosed(): return None pupilId = self.wbQueues.nextClient(roomId) if pupilId != None: self.wbClients[roomId].cleanWhiteBoard() self.wbRooms[roomId].roomPupilCheck(pupilId) avClient = self.wbClients[pupilId] avTutor = self.wbClients[roomId] self.clientUpdate(avTutor, avClient) avClient.waitingInQueue = None avClient.waitingInRoom = roomId return pupilId def manageNextPupil(self, roomId, session): pupilId = self._nextPupil(roomId) if pupilId != None: session(roomId, DECIDING, pupilId) self.setClientWindow(roomId, IN_DECIDING) else: session(roomId, WAITING) self.setClientWindow(roomId, IN_WAITING) def lookForEmptyRoom(self, subject): for roomId in self.wbRooms.keys(): if self.wbRooms[roomId].roomIsEmpty() \ and self.wbRooms[roomId].roomType() != PACLASS \ and subject in self.wbClients[roomId].getSelectedSubjects(): return roomId return None def view_moveItems(self, perspective, selection, dx, dy): items = [] tagId = self.itemId(perspective.avId, int(perspective.avId)) for i in selection: items.append(self.itemId(perspective.avId, i)) wbStatus = self.getWorkingStatus(perspective) wbStatus.statusMoveItems(items, dx, dy, perspective.avId) if perspective.roomId != None: for clientId in self.wbRooms[perspective.roomId].roomListClients(): if perspective.avId != clientId: try: wb = self.wbClients[clientId] d = wb.remote.callRemote("wbMoveItems", tagId, items, dx, dy) except (pb.DeadReferenceError): pass def view_eraseItem(self, perspective, selection): items = [] tagId = self.itemId(perspective.avId, int(perspective.avId)) for i in selection: items.append(self.itemId(perspective.avId, i)) wbStatus = self.getWorkingStatus(perspective) wbStatus.statusEraseItem(items, perspective.avId) if perspective.roomId != None: for clientId in self.wbRooms[perspective.roomId].roomListClients(): if perspective.avId != clientId: try: wb = self.wbClients[clientId] d = wb.remote.callRemote("wbEraseItem", tagId, items) except (pb.DeadReferenceError): pass def view_fillItem(self, perspective, tagId, color): item = self.itemId(perspective.avId, tagId) wbStatus = self.getWorkingStatus(perspective) wbStatus.statusFillItem(item, color, perspective.avId) if perspective.roomId != None: for clientId in self.wbRooms[perspective.roomId].roomListClients(): if perspective.avId != clientId: try: wb = self.wbClients[clientId] d = wb.remote.callRemote("wbFillItem", item, color) except (pb.DeadReferenceError): pass def view_addItem(self, perspective, tagId, kind, points, outline, fill, width, graph=None): itemId = self.itemId(perspective.avId, tagId) wbStatus = self.getWorkingStatus(perspective) wbStatus.statusAddItem(itemId, kind, points, outline, fill, width, perspective.avId) if perspective.roomId != None: for clientId in self.wbRooms[perspective.roomId].roomListClients(): if perspective.avId != clientId: try: wb = self.wbClients[clientId] d = wb.remote.callRemote("wbAddItem", itemId, kind, points, outline, fill, width, graph=graph) except (pb.DeadReferenceError): pass def view_addTextBox(self, perspective, tagId, points, color): """Agrega una caja de texto (creada por client) en las coordenadas dadas; se actualiza estado en el servidor y si esta dentro de un room el cambio se aplica al status de ese room y se difunde a los demas clientes; caso contrario actualiza su status solamente. """ self.pupilsInIAClass() boxId = self.itemId(perspective.avId, tagId) wbStatus = self.getWorkingStatus(perspective) wbStatus.statusAddTextBox(boxId, points, color, perspective.avId) if perspective.roomId != None: for clientId in self.wbRooms[perspective.roomId].roomListClients(): if perspective.avId != clientId: try: wb = self.wbClients[clientId] d = wb.remote.callRemote("wbAddTextBox", boxId, points, color) except (pb.DeadReferenceError): pass def view_insertChars(self, perspective, tagId, index, string): """Inserta el texto string desde index en la caja de texto identificada por boxId; se actualiza estado en el servidor y si esta dentro de un room el cambio se aplica al status de ese room y se difunde a los demas clientes; caso contrario actualiza su status solamente. """ boxId = self.itemId(perspective.avId, tagId) wbStatus = self.getWorkingStatus(perspective) currentText = wbStatus.statusGetField(boxId, "text") c = string.decode("utf-8") text = currentText.decode("utf-8") text = text[:index] + c + text[index:] currentText = text.encode("utf-8") wbStatus.statusSetField(boxId, "text", currentText) if perspective.roomId != None: for clientId in self.wbRooms[perspective.roomId].roomListClients(): if perspective.avId != clientId: try: wb = self.wbClients[clientId] d = wb.remote.callRemote("wbInsertChars", boxId, index, string) except (pb.DeadReferenceError): pass def view_deleteChars(self, perspective, tagId, startIndex, endIndex): """Borra el texto entre startIndex y endIndex de la caja de texto identificada por boxId; se actualiza estado en el servidor y si esta dentro de un room el cambio se aplica al status de ese room y se difunde a los demas clientes; caso contrario actualiza su status solamente. """ boxId = self.itemId(perspective.avId, tagId) wbStatus = self.getWorkingStatus(perspective) currentText = wbStatus.statusGetField(boxId, "text") text = currentText.decode("utf-8") text = text[:startIndex] + text[endIndex+1:] currentText = text.encode("utf-8") wbStatus.statusSetField(boxId, "text", currentText) if perspective.roomId != None: for clientId in self.wbRooms[perspective.roomId].roomListClients(): if perspective.avId != clientId: try: wb = self.wbClients[clientId] d = wb.remote.callRemote("wbDeleteChars", boxId, startIndex, endIndex) except (pb.DeadReferenceError): pass def view_sendMsg(self, perspective, string): """Envia una linea de texto mediante el chat; se actualiza estado en el servidor y si esta dentro de un room el cambio se guarda en el log de ese room y se difunde a los demas clientes; caso contrario actualiza su status solamente. """ c = string.decode("utf-8") if perspective.roomId != None: if "[" + perspective.name + "]: " not in string: string = "[" + perspective.name + "]: " + string #genero log. self.wbRooms[perspective.roomId].logChat += string + "\n" #envio a todos los clientes en el salon for clientId in self.wbRooms[perspective.roomId].roomListClients(): try: wb = self.wbClients[clientId] d = wb.remote.callRemote("wbSendMsg", string) except (pb.DeadReferenceError): pass def writeLog(self, roomId): """guarda el log del chat en un archivo""" try: sessionId = self.sessions.sessionKey(roomId) f = open(ARCHIVE_DIR + str(sessionId) + "-chat.txt", "w") f.write(time.strftime('%x %X') + "\n\n") f.write(self.wbRooms[roomId].logChat) f.close() except KeyError: pass def getWorkingStatus(self, perspective): if perspective.roomId != None: wbStatus = self.wbRooms[perspective.roomId].wbStatus else: wbStatus = self.wbClientStatus[perspective.avId] return wbStatus def clientUpdate(self, perspective, sourceClient): """Actualiza el WhiteBoard (vacio) de client y su status al estado actual de source en el servidor. """ status = self.wbClientStatus[sourceClient.avId] self.clientUpdateFromStatus(perspective, status) def clientUpdateFromStatus(self, perspective, status): for itemId in status.statusGetKeys(): wbObject = status.statusGetObject(itemId) self.wbClientStatus[perspective.avId].add(itemId, wbObject) if wbObject["kind"] == "text": perspective.remote.callRemote("wbAddTextBoxFull", itemId, wbObject["points"], wbObject["text"], wbObject["line"]) else: perspective.remote.callRemote("wbAddItem", itemId, wbObject["kind"], wbObject["points"], wbObject["outline"], wbObject["fill"], wbObject["width"]) def clientUpdateFromStatusModif(self, perspective, status): for itemId in status.statusGetKeys(): wbObject = status.statusGetObject(itemId) if wbObject["kind"] == "text": perspective.remote.callRemote("wbAddTextBoxFull", None, wbObject["points"], wbObject["text"], wbObject["line"]) else: perspective.remote.callRemote("wbAddItem", POST, wbObject["kind"], wbObject["points"], wbObject["outline"], wbObject["fill"], wbObject["width"]) def signalEndClass(self, roomId): #Cierra cola para evitar nuevos alumnos pupilWaiting = self.wbRooms[roomId].roomPupilWaiting() if pupilWaiting != None: self.wbClients[pupilWaiting].waitingInRoom = None # No hay alumnos => elimino room if self.wbRooms[roomId].roomPupilCurrent() == None: self.removeRoom(roomId) self.closeRoomQueue(roomId) def signalEndingClass(self, roomId): #Avisa a los que estaban esperando en cola for clientId in self.wbQueues.clientsWaiting(roomId): self.notifyClient(clientId, "La clase esta por terminar") def exitPupils(self, roomId): pupilWaiting = self.wbRooms[roomId].roomPupilWaiting() if pupilWaiting != None: self.wbClients[pupilWaiting].waitingInRoom = None self.wbClients[pupilWaiting].unlockWhiteBoard() self.setClientWindow(pupilWaiting, OUT) client = self.wbRooms[roomId].roomPupilCurrent() if client != None: discount = (now().minute - self.wbClients[client].lastMinute) % 60 self.wbClients[client].discountIA(discount) self.wbClients[client].lastMinute = None self.wbClients[client].roomId = None self.wbClientStatus[client].statusReset() self.wbClients[client].cleanWhiteBoard() self.notifyClient(client, "El aula se cerro") self.setClientWindow(client, OUT) self.wbRooms[roomId].roomPupilExit(client) def exitViewers(self, roomId): for client in self.wbRooms[roomId].roomListViewers(): self.wbClients[client].viewing = None self.wbClientStatus[client].statusReset() self.wbClients[client].cleanWhiteBoard() self.wbClients[client].unlockWhiteBoard() self.notifyClient(client, "Fin de clase") self.setClientWindow(client, OUT) self.wbRooms[roomId].roomViewersExit() def closeRoomQueue(self, roomId): #Avisa cierre de cola; elimina cola for client in self.wbQueues.clientsWaiting(roomId): self.notifyClient(client, "El aula se cerro") self.wbClients[client].waitingInQueue = None self.setClientWindow(client, IN_ASKING) self.wbQueues.removeQueue(roomId) def setClientWindow(self, clientId, desc): try: self.wbClients[clientId].remote.callRemote("serverClientWindow", desc) except (pb.DeadReferenceError): pass def notifyClient(self, clientId, msg): try: self.wbClients[clientId].remote.callRemote("serverMessage", msg) except (pb.DeadReferenceError): pass def askClient(self, clientId, title, question): try: d = self.wbClients[clientId].remote.callRemote("serverQuestion", title, question) except (pb.DeadReferenceError): d = defer.maybeDeferred(lambda: False) return d def askStringClient(self, clientId, title, question, initial=""): try: d = self.wbClients[clientId].remote.callRemote("serverStringQuestion", title, question, initial) except (pb.DeadReferenceError): d = defer.maybeDeferred(lambda: None) return d def getRoot(self): #rooms = self.openRoomsAndLenQueues() #sbOpen = self.shouldBeOpenRooms() #r = WebRoot(self.wbClients, rooms, sbOpen) r = WebRoot(self) return r def pupilsInIAClass(self): pupils = [] for clientId in self.wbClients.keys(): if self.wbClients[clientId].perspective_whoami() == PUPIL \ and self.wbClients[clientId].roomId != None \ and (self.wbRooms[self.wbClients[clientId].roomId].roomType() == IACLASS or self.wbRooms[self.wbClients[clientId].roomId].roomType() == EXTRA_IACLASS): pupils.append(clientId) return pupils def connectedUsers(self): return self.wbClients def openRoomsAndLenQueues(self): """ Devuelve diccionario con clave room abierto y valor longitud de cola para ese room; incluye cola general (clave: GENERAL) """ return self.wbQueues.getListQueues() def shouldBeOpenRooms(self): """ Devuelve lista de rooms que deberian estar abiertos """ return ([str(i[0]) for i in self.wbRoomsIA] + [str(i[0]) for i in self.wbRoomsPA]) def shouldBeOpenIARooms(self): return [str(i[0]) for i in self.wbRoomsIA] def shouldBeOpenPARooms(self): return [str(i[0]) for i in self.wbRoomsPA] def shouldBeOpenAndClosedRooms(self): """ Devuelve lista de rooms que debieran estar abiertos y no están """ ret = [] sbOpen = self.shouldBeOpenRooms() rOpen = self.openRoomsAndLenQueues() for room in sbOpen: if not rOpen.has_key(room): ret.append(room) return ret def openRoomsAndSubjects(self): ret = {} for roomId in self.wbRooms.keys(): ret[roomId] = self.wbClients[roomId].getSelectedSubjects() return ret def roomInfo(self, roomId): d = self.wbQueues.queueInfo(roomId) if roomId != GENERAL: d.addCallback(self._getRoomInfo) return d def _getRoomInfo(self, res): roomId = str(res[0][0]) tutor = res[0][1] + " " + res[0][2] if self.wbRooms[roomId].endTime != None: end = self.wbRooms[roomId].endTime.strftime("%H:%M") else: end = None kind = self.wbRooms[roomId].roomType() subjects = self.wbClients[roomId].getSubjects() selSubjects = [s for s in subjects.keys() if subjects[s]] return (tutor, kind, end, selSubjects) def remote_makeDemoUser(self, st): print 'tipo:', st return st