#!/usr/bin/python # -*- coding: utf-8 -*- """ Libervia: a Salut à Toi frontend Copyright (C) 2011 Jérôme Poisson (goffi@goffi.org) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . """ from twisted.application import internet, service from twisted.internet import glib2reactor glib2reactor.install() from twisted.internet import reactor, defer from twisted.web import server from twisted.web import error as weberror from twisted.web.static import File from twisted.web.resource import Resource from twisted.words.protocols.jabber.jid import JID from txjsonrpc.web import jsonrpc from txjsonrpc import jsonrpclib from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService import re import glob import os.path from server_side.blog import MicroBlog TIMEOUT = 120 #Session's time out, after that the user will be disconnected LIBERVIA_DIR = "output/" CARDS_DIR = "cards/" class MethodHandler(jsonrpc.JSONRPC): def __init__(self, sat_host): jsonrpc.JSONRPC.__init__(self) self.sat_host=sat_host def render(self, request): self.session = request.getSession() try: profile = self.session.sat_profile except AttributeError: #user is not identified, we return a jsonrpc fault parsed = jsonrpclib.loads(request.content.read()) fault = jsonrpclib.Fault(0, "Not allowed") #FIXME: define some standard error codes for libervia return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) return jsonrpc.JSONRPC.render(self, request) def jsonrpc_getProfileJid(self): """Return the jid of the profile""" profile = self.session.sat_profile self.session.sat_jid = self.sat_host.bridge.getParamA("JabberID", "Connection", profile_key=profile) return self.session.sat_jid def jsonrpc_getContacts(self): """Return all passed args.""" profile = self.session.sat_profile return self.sat_host.bridge.getContacts(profile) def jsonrpc_setStatus(self, status): """Change the status""" profile = self.session.sat_profile print "new status received:", status self.sat_host.bridge.setPresence('', '', 0, {'':status}, profile) def jsonrpc_sendMessage(self, to_jid, msg, subject, type): """send message""" profile = self.session.sat_profile return self.sat_host.bridge.sendMessage(to_jid, msg, subject, type, profile) def jsonrpc_sendMblog(self, raw_text): """Parse raw_text of the microblog box, and send message consequently""" profile = self.session.sat_profile match = re.match(r'@(.+?): *(.*$)', raw_text) if match: recip = match.group(1) text = match.group(2) if recip == '@' and text: #This text if for the public microblog print "Sending message to everybody" return self.sat_host.bridge.sendPersonalEvent("MICROBLOG", {'content':text}, profile) else: return self.sat_host.bridge.sendGroupBlog([recip], text, profile) def jsonrpc_getPresenceStatus(self): """Get Presence information for connected contacts""" profile = self.session.sat_profile return self.sat_host.bridge.getPresenceStatus(profile) def jsonrpc_getHistory(self, from_jid, to_jid, size): """Return history for the from_jid/to_jid couple""" #FIXME: this method should definitely be asynchrone, need to fix it !!! profile = self.session.sat_profile try: _jid = JID(self.session.sat_jid) except: error("No jid saved for this profile") return {} if JID(from_jid).userhost() != _jid.userhost() and JID(to_jid) != _jid.userhost(): error("Trying to get history from a different jid, maybe a hack attempt ?") return {} return self.sat_host.bridge.getHistory(from_jid, to_jid, size) def jsonrpc_getRoomJoined(self): """Return list of room already joined by user""" profile = self.session.sat_profile return self.sat_host.bridge.getRoomJoined(profile) def jsonrpc_launchTarotGame(self, other_players): """Create a room, invite the other players and start a Tarot game""" profile = self.session.sat_profile self.sat_host.bridge.tarotGameLaunch(other_players, profile) def jsonrpc_getTarotCardsPaths(self): """Give the path of all the tarot cards""" return map(lambda x: x[len(LIBERVIA_DIR):],glob.glob(os.path.join(LIBERVIA_DIR,CARDS_DIR,'*_*.png'))); def jsonrpc_tarotGameReady(self, player, referee): """Tell to the server that we are ready to start the game""" profile = self.session.sat_profile self.sat_host.bridge.tarotGameReady(player, referee) def jsonrpc_tarotGameContratChoosed(self, player_nick, referee, contrat): """Tell to the server that we are ready to start the game""" profile = self.session.sat_profile self.sat_host.bridge.tarotGameContratChoosed(player_nick, referee, contrat, profile) def jsonrpc_tarotGamePlayCards(self, player_nick, referee, cards): """Tell to the server that we are ready to start the game""" profile = self.session.sat_profile self.sat_host.bridge.tarotGamePlayCards(player_nick, referee, cards, profile) class Register(jsonrpc.JSONRPC): """This class manage the registration procedure with SàT It provide an api for the browser, check password and setup the web server""" def __init__(self, sat_host): jsonrpc.JSONRPC.__init__(self) self.sat_host=sat_host self.profiles_waiting={} self.request=None def getWaitingRequest(self, profile): """Tell if a profile is trying to log in""" if self.profiles_waiting.has_key(profile): return self.profiles_waiting[profile] else: return None def _fillMblogNodes(self, result, session): """Fill the microblog nodes association for this session""" print "Filling session for %s with %s" % (session.sat_profile, result) session.sat_mblog_nodes = dict(result) def render(self, request): """ Render method with some hacks: - if login is requested, try to login with form data - except login, every method is jsonrpc - user doesn't need to be authentified for isRegistered, but must be for all other methods """ if request.postpath==['login']: return self.login(request) _session = request.getSession() parsed = jsonrpclib.loads(request.content.read()) if parsed.get("method")!="isRegistered": #if we don't call login or isRegistered, we need to be identified try: profile = _session.sat_profile except AttributeError: #user is not identified, we return a jsonrpc fault fault = jsonrpclib.Fault(0, "Not allowed") #FIXME: define some standard error codes for libervia return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) self.request = request return jsonrpc.JSONRPC.render(self, request) def login(self, request): """ this method is called with the POST information from the registering form it test if the password is ok, and log in if it's the case, else it return an error @param request: request of the register formulaire, must have "login" and "password" as arguments @return: A constant indicating the state: - BAD REQUEST: something is wrong in the request (bad arguments, profile_key for login) - AUTH ERROR: either the profile or the password is wrong - ALREADY WAITING: a request has already be made for this profile - server.NOT_DONE_YET: the profile is being processed, the return value will be given by self._logged or self._logginError """ try: _login = request.args['login'][0] if _login.startswith('@'): raise Exception('No profile_key allowed') _pass = request.args['password'][0] except KeyError: return "BAD REQUEST" _profile_check = self.sat_host.bridge.getProfileName(_login) _profile_pass = self.sat_host.bridge.getParamA("Password", "Connection", profile_key=_login) if not _profile_check or _profile_check != _login or _profile_pass != _pass: return "AUTH ERROR" if self.profiles_waiting.has_key(_login): return "ALREADY WAITING" if self.sat_host.bridge.isConnected(_login): return self._logged(_login, request, finish=False) self.profiles_waiting[_login] = request self.sat_host.bridge.connect(_login) return server.NOT_DONE_YET def __cleanWaiting(self, login): """Remove login from waiting queue""" try: del self.profiles_waiting[login] except KeyError: pass def _logged(self, profile, request, finish=True): """Set everything when a user just logged and return "LOGGED" to the requester""" self.__cleanWaiting(profile) _session = request.getSession() _session.sat_profile = profile self.sat_host.prof_connected.add(profile) d = defer.Deferred() self.sat_host.bridge.getMblogNodes(profile, d.callback, d.errback) d.addCallback(self._fillMblogNodes, _session) if finish: request.write('LOGGED') request.finish() else: return "LOGGED" def _logginError(self, login, request, error_type): """Something went wrong during loggin, return an error""" self.__cleanWaiting(login) return error_type def jsonrpc_isConnected(self): _session = self.request.getSession() profile = _session.sat_profile return self.sat_host.bridge.isConnected(profile) def jsonrpc_connect(self): _session = self.request.getSession() profile = _session.sat_profile if self.profiles_waiting.has_key(profile): raise jsonrpclib.Fault('1','Already waiting') #FIXME: define some standard error codes for libervia self.profiles_waiting[profile] = self.request self.sat_host.bridge.connect(profile) return server.NOT_DONE_YET def jsonrpc_isRegistered(self): """Tell if the user is already registered""" _session = self.request.getSession() try: profile = _session.sat_profile except AttributeError: return False return True class SignalHandler(jsonrpc.JSONRPC): def __init__(self, sat_host): Resource.__init__(self) self.register=None self.sat_host=sat_host self.signalDeferred = {} self.queue = {} #XXX: gof: don't forgot to purge queue on session end def plugRegister(self, register): self.register = register def jsonrpc_getSignals(self): """Keep the connection alive until a signal is received, then send it @return: (signal, *signal_args)""" _session = self.request.getSession() profile = _session.sat_profile if profile in self.queue: #if we have signals to send in queue if self.queue[profile]: return self.queue[profile].pop(0) else: #the queue is empty, we delete the profile from queue del self.queue[profile] self.signalDeferred[profile] = defer.Deferred() return self.signalDeferred[profile] def getGenericCb(self, function_name): """Return a generic function which send all params to signalDeferred.callback function must have profile as last argument""" def genericCb(*args): profile = args[-1] if not profile in self.sat_host.prof_connected: return if profile in self.signalDeferred: self.signalDeferred[profile].callback((function_name,args[:-1])) del self.signalDeferred[profile] else: if not self.queue.has_key(profile): self.queue[profile] = [] self.queue[profile].append((function_name, args[:-1])) return genericCb def connected(self, profile): assert(self.register) #register must be plugged request = self.register.getWaitingRequest(profile) if request: self.register._logged(profile, request) def connectionError(self, error_type, profile): assert(self.register) #register must be plugged request = self.register.getWaitingRequest(profile) if request: #The user is trying to log in if error_type == "AUTH_ERROR": _error_t = "AUTH ERROR" else: _error_t = "UNKNOWN" self.register._logginError(profile, request, _error_t) def render(self, request): """ Render method wich reject access if user is not identified """ _session = request.getSession() parsed = jsonrpclib.loads(request.content.read()) try: profile = _session.sat_profile except AttributeError: #user is not identified, we return a jsonrpc fault fault = jsonrpclib.Fault(0, "Not allowed") #FIXME: define some standard error codes for libervia return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) self.request = request return jsonrpc.JSONRPC.render(self, request) class Libervia(service.Service): def __init__(self): root = File(LIBERVIA_DIR) self.signal_handler = SignalHandler(self) _register = Register(self) self.signal_handler.plugRegister(_register) self.sessions = {} #key = session value = user self.prof_connected = set() #Profiles connected ## bridge ## try: self.bridge=DBusBridgeFrontend() except BridgeExceptionNoService: print(u"Can't connect to SàT backend, are you sure it's launched ?") import sys sys.exit(1) self.bridge.register("connected", self.signal_handler.connected) self.bridge.register("connectionError", self.signal_handler.connectionError) for signal_name in ['presenceUpdate', 'personalEvent', 'newMessage', 'roomJoined', 'roomUserJoined', 'roomUserLeft', 'tarotGameStarted', 'tarotGameNew', 'tarotGameChooseContrat', 'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore']: self.bridge.register(signal_name, self.signal_handler.getGenericCb(signal_name)) root.putChild('json_signal_api', self.signal_handler) root.putChild('json_api', MethodHandler(self)) root.putChild('register_api', _register) root.putChild('blog', MicroBlog(self)) root.putChild('css', File("server_css/")) self.site = server.Site(root) def startService(self): reactor.listenTCP(8080, self.site) def run(self): reactor.run() def stop(self): reactor.stop() application = service.Application('Libervia') service = Libervia() service.setServiceParent(application)