]> code.delx.au - pymsnt/commitdiff
Major changes. Moved everything away from twistd and .tac files. Its nicer this way...
authorjamesbunton <jamesbunton@55fbd22a-6204-0410-b2f0-b6c764c7e90a>
Tue, 24 Jan 2006 05:46:20 +0000 (05:46 +0000)
committerjamesbunton <jamesbunton@55fbd22a-6204-0410-b2f0-b6c764c7e90a>
Tue, 24 Jan 2006 05:46:20 +0000 (05:46 +0000)
git-svn-id: http://delx.cjb.net/svn/pymsnt/trunk@100 55fbd22a-6204-0410-b2f0-b6c764c7e90a

committer: jamesbunton <jamesbunton@55fbd22a-6204-0410-b2f0-b6c764c7e90a>

17 files changed:
PyMSNt [deleted file]
PyMSNt.py [new file with mode: 0644]
PyMSNt.tac [deleted file]
README
config-example.xml
src/baseproto/__init__.py
src/baseproto/glue.py
src/config.py
src/debug.py
src/jabw.py
src/legacy/__init__.py
src/legacy/glue.py
src/main.py
src/tlib/msn/__init__.py
src/utils.py
src/xdb.py
src/xmlconfig.py

diff --git a/PyMSNt b/PyMSNt
deleted file mode 100755 (executable)
index 396f91a..0000000
--- a/PyMSNt
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-
-# Comment out the above line and use the below for twistd
-PPATH="/usr/local/PyMSNt"
-twistd -oy "$PPATH/PyMSNt.tac" -r poll --pidfile "$PPATH/PyMSNt.pid" -l "$PPATH/debug.log"
diff --git a/PyMSNt.py b/PyMSNt.py
new file mode 100644 (file)
index 0000000..4f120ac
--- /dev/null
+++ b/PyMSNt.py
@@ -0,0 +1,13 @@
+#!/usr/bin/python
+
+# Make 'cwd'/src in the PYTHONPATH
+import sys, os, os.path
+PATH = os.path.abspath(os.path.dirname(sys.argv[0]))
+os.chdir(PATH)
+PATH = os.path.join(PATH, "src")
+sys.path[0] = PATH
+
+# Start the service
+import main
+main.main()
+
diff --git a/PyMSNt.tac b/PyMSNt.tac
deleted file mode 100644 (file)
index 465e819..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-# Path to the PyMSNt installed directory
-PATH = "/usr/local/PyMSNt/"
-
-# Path to the configuration file
-CONFIG = "/usr/local/PyMSNt/config.xml"
-
-# User privileges to switch to if run as root
-UID=1
-GID=1
-
-####
-# You shouldn't need to modify below this line
-####
-
-
-# Make 'cwd'/src in the PYTHONPATH
-import sys
-import os
-import os.path
-sys.path[0] = os.path.abspath(PATH + "/src/")
-os.chdir(PATH)
-
-# Set up the service
-import config
-config.configFile = CONFIG
-import main
-from twisted.application import service
-application = service.Application("PyMSNt", uid=UID, gid=GID)
-service = main.App()
-service.c.setServiceParent(application)
-
diff --git a/README b/README
index 2f767b583f2c3e59c4d6568d5a3f1b2890cc458d..d0c20aa252f4e22e91209e62eaf866d513a385fb 100644 (file)
--- a/README
+++ b/README
@@ -3,15 +3,7 @@ http://msn-transport.jabberstudio.org
 
 For quickstart, copy config-example.xml to config.xml, change the settings
 there, and run:
-# ./PyMSNt &
-
-If you want more control over daemonisation and logging, have a look at
-the twistd manpage. Edit PyMSNt.tac and run with twistd.
-Examples:
-To start as a daemon run:
-# twistd -oy PyMSNt.tac
-To start as a daemon, with logging, a PID file and poll as the reactor:
-# twistd -oy PyMSNt.tac -r poll --pidfile /var/run/PyMSNt.pid -l /var/log/PyMSNt.log
+# ./PyMSNt.py &
 
 
 For translations have a look at lang.py. If you need any help starting
index b1a5ca039d56ef3ae659fee54a322723ddcb122d..3c76e706597dd298942fef9a4158e0246e90d555 100644 (file)
 <!-- The location of the spool directory.. if relative, relative to the PyMSNt dir.
 Do not include the jid of the transport -->
 <!-- <spooldir>/path/to/data</spooldir> -->
+<!-- The location of the PID file, relative to the PyMSNt directory -->
+<pid>PyMSNt.pid</pid>
+<!-- If set, the transport will background itself when run -->
+<daemonise/>
 
 
 <!-- The IP address of the main Jabber server to connect to -->
 <mainServer>127.0.0.1</mainServer>
-<!-- The JID of the main Jabber server -->
-<mainServerJID>host.com</mainServerJID>
 <!-- The website of the Jabber service -->
 <website>http://host.com</website>
 <!-- The TCP port to connect to the Jabber server on (this is the default for Jabberd2) -->
 <port>5347</port>
 <!-- The authentication token to use when connecting to the Jabber server -->
 <secret>secret</secret>
+<!-- The Twisted reactor to choose. Pick poll or epoll on Linux, kqueue on BSD. Or leave as default (select) -->
+<!-- <reactor>poll</reactor> -->
 
 
 <!-- The default language to use -->
@@ -59,4 +63,17 @@ Do not include the jid of the transport -->
 <jid>admin@host.com</jid>
 </admins>-->
 
+
+<!-- Log settings -->
+
+<!-- The logging level 
+0 -> No logging
+1 -> Log tracebacks
+2 -> Log tracebacks, warnings and errors
+3 -> Log everything -->
+<!-- <debugLevel>0</debugLevel> -->
+
+<!-- The file to log to. Leave this disabled for stdout -->
+<!-- <debugFile>debug.log</debugLog> -->
+
 </pymsnt>
index 9b9dd6af3741cab3f0222a60b8c9a13d2db1ce48..b7713dd1780f07a6b4e1899d2479014764a88c81 100644 (file)
@@ -1,3 +1,3 @@
-from glue import LegacyConnection, LegacyGroupchat, translateAccount, startStats, updateStats
+from glue import LegacyConnection, LegacyGroupchat, translateAccount, startStats, updateStats, reloadConfig
 from glue import name, url, version, mangle, id, namespace
 from glue import formRegEntry, getAttributes, isGroupJID
index 3e992696db5023af4442d7422d9aa88910d69830..de18e32f1b78886ace5c229f0c17e950af3f84dc 100644 (file)
@@ -2,15 +2,13 @@
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
 import utils
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-else:
-       from tlib.domish import Element
+from tlib import xmlw # Provides Element, parseText, parseFile
 import avatar
 import groupchat
 import config
 import debug
 import lang
+import config
 
 
 # The name of the transport
@@ -32,6 +30,9 @@ id = "foo"
 namespace = "jabber:iq:register"
 
 
+def reloadConfig():
+       """ Is called whenever settings in the config module may have changed. """
+       pass
 
 def isGroupJID(jid):
        """ Returns True if the JID passed is a valid groupchat JID (eg, for MSN, if it does not contain '%') """
index d49a61a6140bb6cffedad6ff0ca6c2656931d6cc..d9387760399b3f112ad6a7e60d6cb0f8b4195872 100644 (file)
@@ -1,15 +1,12 @@
 # This file contains the default settings for various options.
 # Please edit config.xml instead of this file
 
-configFile = "config.xml"
-
 jid = "msn"
 ip = "127.0.0.1"
 compjid = ""
 spooldir = ""
 
 mainServer = "127.0.0.1"
-mainServerJID = ""
 website = ""
 port = "5347"
 secret = "secret"
@@ -29,3 +26,9 @@ ftOOBRoot = "http://" + ip + "/"
 
 admins = []
 
+reactor = ""
+daemonise = False
+pid = ""
+
+debugLevel = "0" # 0->None, 1->Traceback, 2->WARN,ERROR, 3->INFO,WARN,ERROR
+debugFile = ""
index 10c87d7695dc848099f673d1b9b1d7040aa1ab2c..284c85ca0abd1617f71d993c098046ebfb78f577 100644 (file)
@@ -4,6 +4,64 @@
 from twisted.python import log
 import sys
 
+import config
+
+
+def observer(eventDict):
+       edm = eventDict['message']
+       if isinstance(edm, LogEvent):
+               if edm.category == INFO and config.debugLevel < 3:
+                       return
+               if (edm.category == WARN or edm.category == ERROR) and config.debugLevel < 2:
+                       return
+               text = str(edm)
+       elif edm:
+               if config.debugLevel < 3: return
+               text = ' '.join(map(str, edm))
+       else:
+               if eventDict['isError'] and eventDict.has_key('failure'):
+                       if config.debugLevel < 1: return
+                       text = eventDict['failure'].getTraceback()
+               elif eventDict.has_key('format'):
+                       if config.debugLevel < 3: return
+                       text = eventDict['format'] % eventDict
+               else:
+                       return
+       
+       # Now log it!
+       timeStr = time.strftime(self.timeFormat, time.localtime(eventDict['time']))
+       text = text.replace("\n", "\n\t")
+       global debugFile
+       debugFile.write(timeStr + msgStr)
+       
+
+debugFile = None
+def reloadConfig():
+       global debugFile
+       if debugFile:
+               debugFile.close()
+       
+       try:
+               config.debugLevel = int(config.debugLevel)
+       except ValueError:
+               config.debugLevel = 0
+
+       if config.debugLevel > 0:
+               if len(config.debugFile) > 0:
+                       try:
+                               debugFile = open(config.debugFile, "w")
+                               log.msg("Rewrote log file.")
+                       except IOError:
+                               log.discardLogs() # Give up
+                               debugFile = sys.__stdout__
+                               return
+               else:
+                       debugFile = sys.__stdout__
+
+               log.startLoggingWithObserver(observer)
+       else:
+               log.discardLogs()
+
 class INFO : pass
 class WARN : pass
 class ERROR: pass
@@ -40,3 +98,5 @@ class LogEvent:
        def log(self):
                log.msg(self)
 
+
+
index ec2904fe608143e891716aaefa8eb63cb6361546..f063cefa2e12d4d55a8d308272d71d2e4cdca0e1 100644 (file)
@@ -253,6 +253,9 @@ class JabberConnection:
                if ptype and (ptype.startswith("subscribe") or ptype.startswith("unsubscribe")):
                        LogEvent(INFO, self.jabberID, "Parsed subscription presence packet")
                        self.subscriptionReceived(toj.userhost(), ptype)
+               elif ptype == "probe":
+                       LogEvent(INFO, self.jabberID, "Parsed presence probe")
+                       self.contactList.getContact(toj.userhost()).sendPresence(fro)
                else:
                        status = None
                        show = None
index 6e5a50afc672206ea5a35f10c316f170a89308f1..fd2413100d5055e914182efcd83667f298246794 100644 (file)
@@ -1,6 +1,6 @@
 # Copyright 2004 James Bunton <james@delx.cjb.net>
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
-from glue import LegacyConnection, LegacyGroupchat, translateAccount, startStats, updateStats
+from glue import LegacyConnection, LegacyGroupchat, translateAccount, startStats, updateStats, reloadConfig
 from glue import name, url, version, mangle, id, namespace
 from glue import formRegEntry, getAttributes, isGroupJID
index 84d45872f19287ea25f41c02b59f1ec16b9b09ff..67b788f949dbe74cd6523a092e0e75cc6d3a7f8c 100644 (file)
@@ -35,6 +35,9 @@ f.close()
 defaultAvatar = avatar.AvatarCache().setAvatar(defaultAvatarData)
 
 
+def reloadConfig():
+       msn.GETALLAVATARS = config.getAllAvatars
+
 def isGroupJID(jid):
        """ Returns True if the JID passed is a valid groupchat JID (for MSN, does not contain '%') """
        return (jid.find('%') == -1)
index 1a7803e30e8e3436b7faa81e1d233a6fd7b22b2a..ddfbb200a622fe91befcc1f5319a23b07581de15 100644 (file)
@@ -1,25 +1,72 @@
 # Copyright 2004-2005 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)
 
 # 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:dDgtl:h", ["background", "config=", "option=", "debug", "Debug", "garbage", "traceback", "log=", "help"])
+for o, v in opts:
+       if o in ("-c", "--config"):
+               configFile = v
+       elif o in ("-b", "--background"):
+               config.daemonise = 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 ("-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 "   -o <var>=<setting>  set config var to setting"
+               sys.exit(0)
+
+xmlconfig.reloadConfig(configFile, configOptions)
+
+# Choose a 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 default, select(), reactor."
+
 
 from twisted.internet import reactor, task
 from twisted.internet.defer import Deferred
 from tlib.xmlw import Element, jid, component
 from debug import LogEvent, INFO, WARN, ERROR
 
+import debug
+import utils
 import xdb
 import avatar
 import session
@@ -32,17 +79,12 @@ 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)
 
-               # 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)
@@ -234,24 +276,50 @@ class PyTransport(component.Service):
 
 class App:
        def __init__(self):
+               self.checkPID()
+
                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 checkPID(self):
+               # Check that we're not already running
+               if config.pid:
+                       if os.path.isfile(config.pid):
+                               if os.name == "posix":
+                                       pf = open(config.pid)
+                                       pid = int(str(pf.readline().strip()))
+                                       pf.close()
+                                       try:
+                                               os.kill(pid, signal.SIGHUP)
+                                               alreadyRunning()
+                                       except OSError:
+                                               pass
+                               else:
+                                       alreadyRunning()
+       
+                       # Create a PID file
+                       pid = str(os.getpid())
+                       pf = open(config.pid, "w")
+                       pf.write("%s\n" % pid)
+                       pf.close()
+
        def alreadyRunning(self):
-               print "There is already a transport instance running with this configuration."
-               print "Exiting..."
+               sys.__stdout__.write("There is already a transport instance running with this configuration.\nExiting...\n\n")
                sys.exit(1)
        
+       
        def shuttingDown(self):
                self.transportSvc.removeMe()
                # Keep the transport running for another 3 seconds
                def cb(ignored=None):
-                       pass
+                       if config.pid:
+                               os.remove(config.pid)
                d = Deferred()
                d.addCallback(cb)
                reactor.callLater(3.0, d.callback, None)
@@ -260,7 +328,10 @@ class App:
 
 
 def SIGHUPstuff(*args):
-       xmlconfig.reloadConfig()
+       global configFile, configOptions
+       xmlconfig.reloadConfig(configFile, configOptions)
+       debug.reloadConfig()
+       legacy.reloadConfig()
 
 if os.name == "posix":
        import signal
@@ -268,7 +339,29 @@ if os.name == "posix":
        signal.signal(signal.SIGHUP, SIGHUPstuff)
 
 
-if __name__ == "__main__":
+def main():
+       # Background the process
+       if config.daemonise:
+               try:
+                       pid = os.fork()
+                       if pid > 0:
+                               sys.exit(0)
+               except OSError, e:
+                       sys.stderr.write("Daemonise failed: (%d) %s\n" % (e.errno, e.strerror))
+                       sys.exit(1)
+
+       # Initialise debugging, and do the other half of SIGHUPstuff
+       debug.reloadConfig()
+       legacy.reloadConfig()
+
+       # Do any auto-update stuff
+       housekeep.init()
+
+       # Create the application
        app = App()
+
        reactor.run()
 
+if __name__ == "__main__":
+       main()
+
index edc622ab29b900cf56446584be1107438cd63f18..f4870c5c6503eeee802f6fc709a04ee711099485 100644 (file)
@@ -1,4 +1,4 @@
-from msnw import MSNConnection, MultiSwitchboardSession
+from msnw import MSNConnection, MultiSwitchboardSession, GETALLAVATARS
 from msn import FORWARD_LIST, ALLOW_LIST, BLOCK_LIST, REVERSE_LIST, PENDING_LIST
 from msn import STATUS_ONLINE, STATUS_OFFLINE, STATUS_HIDDEN, STATUS_IDLE, STATUS_AWAY, STATUS_BUSY, STATUS_BRB, STATUS_PHONE, STATUS_LUNCH
 from msn import MSNContact, MSNContactList
index 28181fcf68b2d3ae66b4376d956aed64cb61a8fd..8107ddc6aef70e5c3c79d978e7b35adfe9497d5e 100644 (file)
@@ -68,16 +68,6 @@ errorCodeMap = {
 }
 
 
-def parseText(text, beExtremelyLenient=False):
-       t = TextParser(beExtremelyLenient)
-       t.parseString(text)
-       return t.root
-
-def parseFile(filename, beExtremelyLenient=False):
-       t = TextParser(beExtremelyLenient)
-       t.parseFile(filename)
-       return t.root
-
 
 checkTwistedCached = None
 def checkTwisted():
@@ -126,36 +116,4 @@ class VersionNumber:
                        i += 1
        
 
-if checkTwisted():
-       from twisted.xish.domish import SuxElementStream
-else:
-       from tlib.domish import SuxElementStream
-class TextParser:
-       """ Taken from http://xoomer.virgilio.it/dialtone/rsschannel.py """
-
-       def __init__(self, beExtremelyLenient=False):
-               self.root = None
-               self.beExtremelyLenient = beExtremelyLenient
-
-       def parseFile(self, filename):
-               return self.parseString(file(filename).read())
-
-       def parseString(self, data):
-               es = SuxElementStream()
-               es.beExtremelyLenient = self.beExtremelyLenient
-               es.DocumentStartEvent = self.docStart
-               es.DocumentEndEvent = self.docEnd
-               es.ElementEvent = self.element
-               es.parse(data)
-               return self.root
-
-       def docStart(self, e):
-               self.root = e
-
-       def docEnd(self):
-               pass
-
-       def element(self, e):
-               self.root.addChild(e)
-
 
index 664848f548163420dac55bc33effec1b8f2756ce..7910b28d072634123431ec0d6a286d9d642a2950 100644 (file)
@@ -1,8 +1,7 @@
 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
-import utils
-from tlib.xmlw import Element
+from tlib import xmlw
 from debug import LogEvent, INFO, WARN
 import os
 import os.path
@@ -41,7 +40,7 @@ class XDB:
                        file = mangle(file)
                
                hash = file[0:2]
-               document = utils.parseFile(self.name + "/" + hash + "/" + file + ".xml")
+               document = xmlw.parseFile(self.name + "/" + hash + "/" + file + ".xml")
                
                return document
        
@@ -95,7 +94,7 @@ class XDB:
                        except IOError:
                                pass
                        if(not document):
-                               document = Element((None, "xdb"))
+                               document = xmlw.Element((None, "xdb"))
                        
                        # Remove the existing node (if any)
                        for child in document.elements():
index 699042e4c9a75891123002034441fbf164f0dd81..e8758b529edfe81e6c2a6433dd5d8c438dc419ca 100644 (file)
@@ -1,11 +1,10 @@
 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
+from tlib import xmlw
 
-import sys
-import os
+import sys, os
 
-import utils
 import config
 
 
@@ -16,12 +15,7 @@ def invalidError(text):
        sys.exit(1)
 
 
-def reloadConfig():
-       # Find out where the config file is
-       configFile = config.configFile
-       if len(sys.argv) == 2:
-               configFile = sys.argv[1]
-
+def importFile(configFile):
        # Check the file exists
        if not os.path.isfile(configFile):
                print "Configuration file not found. You need to create a config.xml file in the PyMSNt directory."
@@ -29,7 +23,7 @@ def reloadConfig():
 
        # Get ourself a DOM
        try:
-               root = utils.parseFile(configFile)
+               root = xmlw.parseFile(configFile)
        except Exception, e:
                invalidError("Error parsing configuration file: " + str(e))
 
@@ -62,8 +56,16 @@ def reloadConfig():
                        print "Tag %s in your configuration file is not a defined tag. Ignoring!" % (tag)
        
 
-       # Apply some things immediately
-       from tlib.msn import msnw
-       msnw.GETALLAVATARS = config.getAllAvatars
+def importOptions(options):
+       for o in options:
+               if hasattr(config, o):
+                       setattr(config, o, options[0])
+               else:
+                       print "Option %s is not a defined option. Ignoring!" % (tag)
 
+def reloadConfig(file=None, options=None):
+       if file:
+               importFile(file)
+       if options:
+               importOptions(options)