]> code.delx.au - pymsnt/blobdiff - src/main.py
* PyMSNt now supports Twisted 2.5
[pymsnt] / src / main.py
index 1a7803e30e8e3436b7faa81e1d233a6fd7b22b2a..b31aa8e895e129f12c3ba2011e38eb3bed2f9a23 100644 (file)
-# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
+# Copyright 2004-2006 James Bunton <james@delx.cjb.net>
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
-import utils
-import os
-import os.path
-import shutil
-import time
-import sys
+import os, os.path, time, sys, codecs, getopt
 reload(sys)
 sys.setdefaultencoding("utf-8")
+sys.stdout = codecs.lookup('utf-8')[-1](sys.stdout)
+
+
+# Find the best reactor
+selectWarning = "Unable to install any good reactors (kqueue, epoll, poll).\nWe fell back to using select. You may have scalability problems.\nThis reactor will not support more than 1024 connections at a time."
+try:
+       from twisted.internet import epollreactor as bestreactor
+except:
+       #try:
+               #from twisted.internet import kqreactor as bestreactor
+       #except:
+       try:
+               from twisted.internet import pollreactor as bestreactor
+       except:
+               try:
+                       from twisted.internet import selectreactor as bestreactor
+                       print selectWarning
+               except:
+                       try:
+                               from twisted.internet import default as bestreactor
+                               print selectWarning
+                       except:
+                               print "Unable to find a reactor. Please make sure you have Twisted properly installed.\nExiting..."
+                               sys.exit(1)
+bestreactor.install()
+
+import twistfix
+twistfix.main()
+
 
 # Must load config before everything else
 import config
 import xmlconfig
-xmlconfig.reloadConfig()
+configFile = "config.xml"
+configOptions = {}
+opts, args = getopt.getopt(sys.argv[1:], "bc:o:dDgtlp:h", ["background", "config=", "option=", "debug", "Debug", "garbage", "traceback", "log=", "pid=", "help"])
+for o, v in opts:
+       if o in ("-c", "--config"):
+               configFile = v
+       elif o in ("-b", "--background"):
+               config.background = True
+       elif o in ("-d", "--debug"):
+               config.debugLevel = "2"
+       elif o in ("-D", "--Debug"):
+               config.debugLevel = "3"
+       elif o in ("-g", "--garbage"):
+               import gc
+               gc.set_debug(gc.DEBUG_LEAK|gc.DEBUG_STATS)
+       elif o in ("-t", "--traceback"):
+               config.debugLevel = "1"
+       elif o in ("-l", "--log"):
+               config.debugFile = v
+       elif o in ("-p", "--pid"):
+               config.pid = v
+       elif o in ("-o", "--option"):
+               var, setting = v.split("=", 2)
+               configOptions[var] = setting
+       elif o in ("-h", "--help"):
+               print "%s [options]" % sys.argv[0]
+               print "   -h                  print this help"
+               print "   -b                  daemonize/background transport"
+               print "   -c <file>           read configuration from this file"
+               print "   -d                  print debugging output"
+               print "   -D                  print extended debugging output"
+               print "   -g                  print garbage collection output"
+               print "   -t                  print debugging only on traceback"
+               print "   -l <file>           write debugging output to file"
+               print "   -p <file>           write process ID to file"
+               print "   -o <var>=<setting>  set config var to setting"
+               sys.exit(0)
+
+xmlconfig.reloadConfig(configFile, configOptions)
+
+if config.reactor:
+       # They picked their own reactor. Lets install it.
+       del sys.modules["twisted.internet.reactor"]
+       if config.reactor == "epoll":
+               from twisted.internet import epollreactor
+               epollreactor.install()
+       elif config.reactor == "poll":
+               from twisted.internet import pollreactor
+               pollreactor.install()
+       elif config.reactor == "kqueue":
+               from twisted.internet import kqreactor
+               kqreactor.install()
+       elif len(config.reactor) > 0:
+               print "Unknown reactor: ", config.reactor, ". Using select(), reactor."
+
 
 from twisted.internet import reactor, task
 from twisted.internet.defer import Deferred
-from tlib.xmlw import Element, jid, component
+from twisted.words.xish.domish import Element
+from twisted.words.protocols.jabber import component
+from twisted.words.protocols.jabber.jid import internJID
+
 from debug import LogEvent, INFO, WARN, ERROR
 
+import debug
+import svninfo
+import utils
 import xdb
 import avatar
 import session
@@ -32,23 +116,24 @@ import lang
 import legacy
 import housekeep
 
-#import gc
-#gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_INSTANCES | gc.DEBUG_OBJECTS)
 
 
 class PyTransport(component.Service):
        def __init__(self):
                LogEvent(INFO)
+               try:
+                       LogEvent(INFO, msg="SVN r" + str(svninfo.getSVNVersion()))
+               except:
+                       pass
+               LogEvent(INFO, msg="Reactor: " + str(reactor))
 
-               # Do any auto-update stuff
-               housekeep.init()
-               
                # Discovery, as well as some builtin features
                self.discovery = disco.ServerDiscovery(self)
-               self.discovery.addIdentity("gateway", legacy.id, legacy.name, config.jid)
-               self.discovery.addIdentity("conference", "text", legacy.name + " Chatrooms", config.jid)
+               self.discovery.addIdentity("gateway", legacy.id, config.discoName, config.jid)
+               self.discovery.addIdentity("conference", "text", config.discoName + " Chatrooms", config.jid)
                self.discovery.addFeature(disco.XCONFERENCE, None, config.jid) # So that clients know you can create groupchat rooms on the server
                self.discovery.addFeature("jabber:iq:conference", None, config.jid) # We don't actually support this, but Psi has a bug where it looks for this instead of the above
+               self.discovery.addIdentity("client", "pc", "MSN Messenger", "USER")
                
                self.xdb = xdb.XDB(config.jid, legacy.mangle)
                self.avatarCache = avatar.AvatarCache()
@@ -79,8 +164,8 @@ class PyTransport(component.Service):
                # Message IDs
                self.messageID = 0
                
-               self.loopCall = task.LoopingCall(self.loopCall)
-               self.loopCall.start(60.0)
+               self.loopTask = task.LoopingCall(self.loopFunc)
+               self.loopTask.start(60.0)
 
        def removeMe(self):
                LogEvent(INFO)
@@ -103,7 +188,7 @@ class PyTransport(component.Service):
        def reserveID(self, ID):
                self.reservedIDs.append(ID)
        
-       def loopCall(self):
+       def loopFunc(self):
                numsessions = len(self.sessions)
 
                #if config.debugOn and numsessions > 0:
@@ -118,12 +203,12 @@ class PyTransport(component.Service):
                        oldDict = self.sessions.copy()
                        self.sessions = {}
                        for key in oldDict:
-                               session = oldDict[key]
-                               if not session.alive:
+                               s = oldDict[key]
+                               if not s.alive:
                                        LogEvent(WARN, "", "Ghost session found.")
                                        # Don't add it to the new dictionary. Effectively removing it
                                else:
-                                       self.sessions[key] = session
+                                       self.sessions[key] = s
        
        def componentConnected(self, xmlstream):
                LogEvent(INFO)
@@ -162,13 +247,17 @@ class PyTransport(component.Service):
        def onMessage(self, el):
                fro = el.getAttribute("from")
                try:
-                       froj = jid.intern(fro)
+                       froj = internJID(fro)
                except Exception, e:
                        LogEvent(WARN, "", "Failed stringprep.")
                        return
                mtype = el.getAttribute("type")
-               if self.sessions.has_key(froj.userhost()):
-                       self.sessions[froj.userhost()].onMessage(el)
+               s = self.sessions.get(froj.userhost(), None)
+               if mtype == "error" and s:
+                       LogEvent(INFO, s.jabberID, "Removing session because of message type=error")
+                       s.removeMe()
+               elif s:
+                       s.onMessage(el)
                elif mtype != "error":
                        to = el.getAttribute("to")
                        ulang = utils.getLang(el)
@@ -178,19 +267,25 @@ class PyTransport(component.Service):
                                        body = child.__str__()
                        LogEvent(INFO, "", "Sending error response to a message outside of session.")
                        jabw.sendErrorMessage(self, fro, to, "auth", "not-authorized", lang.get(ulang).notLoggedIn, body)
+                       jabw.sendPresence(self, fro, to, ptype="unavailable")
        
        def onPresence(self, el):
                fro = el.getAttribute("from")
                to = el.getAttribute("to")
                try:
-                       froj = jid.intern(fro)
-                       toj = jid.intern(to)
+                       froj = internJID(fro)
+                       toj = internJID(to)
                except Exception, e:
                        LogEvent(WARN, "", "Failed stringprep.")
                        return
 
-               if self.sessions.has_key(froj.userhost()):
-                       self.sessions[froj.userhost()].onPresence(el)
+               ptype = el.getAttribute("type")
+               s = self.sessions.get(froj.userhost())
+               if ptype == "error" and s:
+                       LogEvent(INFO, s.jabberID, "Removing session because of message type=error")
+                       s.removeMe()
+               elif s:
+                       s.onPresence(el)
                else:
                        ulang = utils.getLang(el)
                        ptype = el.getAttribute("type")
@@ -211,47 +306,70 @@ class PyTransport(component.Service):
                                
                                elif el.getAttribute("type") != "error":
                                        LogEvent(INFO, "", "Sending unavailable presence to non-logged in user.")
-                                       pres = Element((None, "presence"))
-                                       pres.attributes["from"] = to
-                                       pres.attributes["to"] = fro
-                                       pres.attributes["type"] = "unavailable"
-                                       self.send(pres)
+                                       jabw.sendPresence(self, fro, to, ptype="unavailable")
                                        return
                        
                        elif ptype and (ptype.startswith("subscribe") or ptype.startswith("unsubscribe")):
                                # They haven't logged in, and are trying to change subscription to a user
+                               # No, lets not log them in. Lets send an error :)
+                               jabw.sendPresence(self, fro, to, ptype="error")
+                               
                                # Lets log them in and then do it
-                               LogEvent(INFO, "", "Attempting to create a session to do subscription stuff.")
-                               s = session.makeSession(self, froj.userhost(), ulang)
-                               if s:
-                                       self.sessions[froj.userhost()] = s
-                                       LogEvent(INFO, "", "New session created.")
-                                       # Tell the session there's a new resource
-                                       s.handleResourcePresence(froj.userhost(), froj.resource, toj.userhost(), toj.resource, 0, None, None, None)
-                                       # Send this subscription
-                                       s.onPresence(el)
+                               #LogEvent(INFO, "", "Attempting to create a session to do subscription stuff.")
+                               #s = session.makeSession(self, froj.userhost(), ulang)
+                               #if s:
+                               #       self.sessions[froj.userhost()] = s
+                               #       LogEvent(INFO, "", "New session created.")
+                               #       # Tell the session there's a new resource
+                               #       s.handleResourcePresence(froj.userhost(), froj.resource, toj.userhost(), toj.resource, 0, None, None, None)
+                               #       # Send this subscription
+                               #       s.onPresence(el)
 
 
 class App:
        def __init__(self):
+               # Check for any other instances
+               if config.pid and os.name != "posix":
+                       config.pid = ""
+               if config.pid:
+                       twistd.checkPID(config.pid)
+
+               # Do any auto-update stuff
+               housekeep.init()
+               
+               # Daemonise the process and write the PID file
+               if config.background and os.name == "posix":
+                       twistd.daemonize()
+               if config.pid:
+                       self.writePID()
+
+               # Initialise debugging, and do the other half of SIGHUPstuff
+               debug.reloadConfig()
+               legacy.reloadConfig()
+
+               # Start the service
                jid = config.jid
-               if config.compjid: jid = config.compjid
+               if config.useXCP and config.compjid:
+                       jid = config.compjid
                self.c = component.buildServiceManager(jid, config.secret, "tcp:%s:%s" % (config.mainServer, config.port))
                self.transportSvc = PyTransport()
                self.transportSvc.setServiceParent(self.c)
                self.c.startService()
                reactor.addSystemEventTrigger('before', 'shutdown', self.shuttingDown)
        
-       def alreadyRunning(self):
-               print "There is already a transport instance running with this configuration."
-               print "Exiting..."
-               sys.exit(1)
-       
+       def writePID(self):
+               # Create a PID file
+               pid = str(os.getpid())
+               pf = open(config.pid, "w")
+               pf.write("%s\n" % pid)
+               pf.close()
+
        def shuttingDown(self):
                self.transportSvc.removeMe()
                # Keep the transport running for another 3 seconds
                def cb(ignored=None):
-                       pass
+                       if config.pid:
+                               twistd.removePID(config.pid)
                d = Deferred()
                d.addCallback(cb)
                reactor.callLater(3.0, d.callback, None)
@@ -260,15 +378,26 @@ class App:
 
 
 def SIGHUPstuff(*args):
-       xmlconfig.reloadConfig()
+       global configFile, configOptions
+       xmlconfig.reloadConfig(configFile, configOptions)
+       if config.pid and os.name != "posix":
+               config.pid = ""
+       debug.reloadConfig()
+       legacy.reloadConfig()
 
 if os.name == "posix":
        import signal
        # Set SIGHUP to reload the config file & close & open debug file
        signal.signal(signal.SIGHUP, SIGHUPstuff)
+       # Load some scripts for PID and daemonising
+       from twisted.scripts import _twistd_unix as twistd
 
 
-if __name__ == "__main__":
+def main():
+       # Create the application
        app = App()
        reactor.run()
 
+if __name__ == "__main__":
+       main()
+