diff --git a/conf/jcl.conf b/conf/jcl.conf new file mode 100644 index 0000000..d97a80b --- /dev/null +++ b/conf/jcl.conf @@ -0,0 +1,21 @@ +[jabber] +server: localhost +port: 5347 +secret: secret +service_jid: jcl.localhost +#supported language: en, fr (See src/jcl/lang.py to add more) +language: en + +[db] +#type: mysql +type: sqlite +#host: root@localhost +host: +name: /var/spool/jabber/jcl.db +#url: %(type)%(host)%(name)?debug=1&debugThreading=1 +db_url: %(type)s://%(host)s%(name)s + +[component] +pid_file: /var/run/jabber/jcl.pid + + diff --git a/src/jcl.py b/src/jcl.py new file mode 100644 index 0000000..5e2141f --- /dev/null +++ b/src/jcl.py @@ -0,0 +1,34 @@ +## +## jcl.py +## Login : David Rousselie +## Started on Fri May 18 15:19:20 2007 David Rousselie +## $Id$ +## +## Copyright (C) 2007 David Rousselie +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## + +import sys +reload(sys) +sys.setdefaultencoding('utf-8') +del sys.setdefaultencoding + +import jcl +from jcl.runner import JCLRunner + +if __name__ == '__main__': + runner = JCLRunner("JCL", jcl.version) + runner.configure() + runner.run() diff --git a/src/jcl/__init__.py b/src/jcl/__init__.py index 5f9d567..335ea82 100644 --- a/src/jcl/__init__.py +++ b/src/jcl/__init__.py @@ -1,2 +1,5 @@ """JCL module""" __revision__ = "" + +version = "0.1.0" + diff --git a/src/jcl/jabber/component.py b/src/jcl/jabber/component.py index 50e45cb..6c90d1c 100644 --- a/src/jcl/jabber/component.py +++ b/src/jcl/jabber/component.py @@ -62,7 +62,6 @@ VERSION = "0.1" ############################################################################### class JCLComponent(Component, object): """Implement default JCL component behavior: - - regular interval behavior - Jabber register process (add, delete, update accounts) - Jabber presence handling - passwork request at login diff --git a/src/jcl/runner.py b/src/jcl/runner.py new file mode 100644 index 0000000..979f7bc --- /dev/null +++ b/src/jcl/runner.py @@ -0,0 +1,208 @@ +## +## runner.py +## Login : David Rousselie +## Started on Thu May 17 22:02:08 2007 David Rousselie +## $Id$ +## +## Copyright (C) 2007 David Rousselie +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## + +import logging +import os +import sys +from ConfigParser import ConfigParser +from getopt import gnu_getopt + +from sqlobject import * + +from jcl.model import account +from jcl.model.account import Account, PresenceAccount + +class JCLRunner(object): + def __init__(self, component_name, component_version): + """ + options: list of tuples: + - short_opt: same as getopt + - long_opt: same as getopt + """ + self.component_name = component_name + self.component_version = component_version + self.config_file = None + self.server = "localhost" + self.port = 5347 + self.secret = "secret" + self.service_jid = "jcl.localhost" + self.language = "en" + self.db_url = "sqlite:///var/spool/jabber/jcl.db" + self.pid_file = "/var/run/jabber/jcl.pid" + self.options = [("c:", "config-file=", None, \ + " FILE\t\t\t\tConfiguration file to use", \ + lambda arg: self.set_attr("config_file", arg)), \ + ("S:", "server=", "jabber", \ + " SERVER_ADDRESS\t\t\tAddress of the Jabber server", \ + lambda arg: self.set_attr("server", arg)), \ + ("P:", "port=", "jabber", \ + " PORT\t\t\t\t\tPort of the Jabber server to connect the component", \ + lambda arg: self.set_attr("port", int(arg))), \ + ("s:", "secret=", "jabber", \ + " SECRET\t\t\t\tComponent password to connect to the Jabber server", \ + lambda arg: self.set_attr("secret", arg)), \ + ("j:", "service-jid=", "jabber", \ + " JID\t\t\t\tJID of the component", \ + lambda arg: self.set_attr("service_jid", arg)), \ + ("l:", "language=", "jabber", \ + " LANG\t\t\t\tDefault Language of the component", \ + lambda arg: self.set_attr("language", arg)), \ + ("u:", "db-url=", "db", \ + " URL\t\t\t\tDatabase URL", \ + lambda arg: self.set_attr("db_url", arg)), \ + ("p:", "pid-file=", "component", \ + " FILE\t\t\t\tPath of the PID file", \ + lambda arg: self.set_attr("pid_file", arg)), \ + ("d", "debug", None, \ + "\t\t\t\t\tEnable debug traces", \ + lambda arg: self.set_attr("debug", True)), \ + ("h", "help", None, \ + "\t\t\t\t\tThis help", \ + lambda arg: self.print_help())] + self.logger = logging.getLogger() + self.logger.addHandler(logging.StreamHandler()) + self.__debug = False + + def set_attr(self, attr, value): + setattr(self, attr, value) + + def __configure_commandline_args(self, shortopts, longopts, cleanopts): + (opts, args) = gnu_getopt(sys.argv[1:], shortopts, longopts) + commandline_args = {} + for (arg, value) in opts: + clean_arg = arg.lstrip('-') + if cleanopts.has_key(clean_arg): + commandline_args[clean_arg] = value + return commandline_args + + def __apply_commandline_args(self, commandline_args, cleanopts): + for arg in commandline_args: + value = commandline_args[arg] + self.logger.debug("Applying argument " + arg + " = " + \ + value) + cleanopts[arg][1](value) + + def __apply_configfile(self, commandline_args, cleanopts): + if commandline_args.has_key("config_file"): + self.config_file = commandline_args["config_file"] + elif commandline_args.has_key("c"): + self.config_file = commandline_args["c"] + if self.config_file is not None: + self.config = ConfigParser() + self.logger.debug("Loading config file " + self.config_file) + read_file = self.config.read(self.config_file) + if read_file == []: + raise IOError(2, "No such file or directory", str(self.config_file)) + for opt in cleanopts: + if len(opt) > 1: + (section, set_func) = cleanopts[opt] + if section is not None: + attr = opt.replace("-", "_") + config_property = self.config.get(section, attr) + self.logger.debug("Setting " + attr + " = " + \ + config_property + \ + " from configuration file " + \ + self.config_file) + set_func(config_property) + + def configure(self): + """Apply configuration from command line and configuration file. + command line arguments override configuration file properties. + """ + shortopts = "" + longopts = [] + cleanopts = {} + for option in self.options: + shortopts += option[0] + longopts += [option[1]] + cleanopts[option[0][0]] = (option[2], option[4]) + if option[1][-1:] == '=': + cleanopts[option[1][:-1]] = (option[2], option[4]) + else: + cleanopts[option[1]] = (option[2], option[4]) + + commandline_args = self.__configure_commandline_args(shortopts, longopts, cleanopts) + if commandline_args.has_key("debug") or commandline_args.has_key("d"): + self.debug = True + self.__apply_configfile(commandline_args, cleanopts) + self.__apply_commandline_args(commandline_args, cleanopts) + + def _get_help(self): + help = self.component_name + " v" + self.component_version + " help:\n" + for option in self.options: + if option[1][-1:] == '=': + long_option = option[1][:-1] + else: + long_option = option[1] + help += "\t-" + option[0][0] + ", --" + long_option + option[3] + "\n" + return help + + def print_help(self): + print self._get_help() + sys.exit(0) + + def get_debug(self): + return self.__debug + + def set_debug(self, value): + self.__debug = value + if self.__debug: + self.logger.setLevel(logging.DEBUG) + else: + self.logger.setLevel(logging.CRITICAL) + + debug = property(get_debug, set_debug) + + + def setup_db(self): + Account.createTable(ifNotExists = True) + PresenceAccount.createTable(ifNotExists = True) + + def setup_pidfile(self): + pidfile = open(self.pid_file, "w") + pidfile.write(str(os.getpid())) + pidfile.close() + + def _run(self, run_func): + try: + self.setup_pidfile() + account.hub.threadConnection = connectionForURI(self.db_url) + self.setup_db() + del account.hub.threadConnection + self.logger.debug(self.component_name + " v" + \ + self.component_version + " is starting ...") + run_func() + self.logger.debug(self.component_name + " is exiting") + finally: + os.remove(self.pid_file) + + def run(self): + def run_func(): + component = JCLComponent(jid = self.service_jid, \ + secret = self.secret, \ + server = self.server, \ + port = self.port, \ + db_connection_str = self.db_url, \ + lang = Lang(self.language)) + component.run() + self._run(run_func) + diff --git a/src/jcl/tests/__init__.py b/src/jcl/tests/__init__.py index e6219d6..db400bf 100644 --- a/src/jcl/tests/__init__.py +++ b/src/jcl/tests/__init__.py @@ -3,13 +3,14 @@ __revision__ = "" import unittest -from jcl.tests import lang +from jcl.tests import lang, runner from jcl.jabber import tests as jabber from jcl.model import tests as model def suite(): suite = unittest.TestSuite() suite.addTest(lang.suite()) + suite.addTest(runner.suite()) suite.addTest(jabber.suite()) suite.addTest(model.suite()) return suite diff --git a/src/jcl/tests/jcl.conf b/src/jcl/tests/jcl.conf new file mode 100644 index 0000000..ed1d5dd --- /dev/null +++ b/src/jcl/tests/jcl.conf @@ -0,0 +1,21 @@ +[jabber] +server: test_localhost +port: 42 +secret: test_secret +service_jid: test_jcl.localhost +#supported language: en, fr (See src/jmc/lang.py to add more) +language: test_en + +[db] +#type: mysql +type: test_sqlite +#host: root@localhost +host: root@localhost +name: /var/spool/jabber/test_jcl.db +#url: %(type)%(host)%(name)?debug=1&debugThreading=1 +db_url: %(type)s://%(host)s%(name)s + +[component] +pid_file: /var/run/jabber/test_jcl.pid + + diff --git a/src/jcl/tests/runner.py b/src/jcl/tests/runner.py new file mode 100644 index 0000000..58a0c5b --- /dev/null +++ b/src/jcl/tests/runner.py @@ -0,0 +1,146 @@ +## +## runner.py +## Login : David Rousselie +## Started on Fri May 18 13:43:37 2007 David Rousselie +## $Id$ +## +## Copyright (C) 2007 David Rousselie +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +## + +import unittest +import sys +import os + +from sqlobject import * + +import jcl +from jcl.runner import JCLRunner + +from jcl.model import account +from jcl.model.account import Account, PresenceAccount + +if sys.platform == "win32": + DB_PATH = "/c|/temp/test.db" +else: + DB_PATH = "/tmp/test.db" +DB_URL = "sqlite://" + DB_PATH# + "?debug=1&debugThreading=1" + +class JCLRunner_TestCase(unittest.TestCase): + def setUp(self): + self.runner = JCLRunner("JCL", jcl.version) + + def tearDown(self): + self.runner = None + sys.argv = [""] + + def test_configure_default(self): + self.runner.configure() + self.assertEquals(self.runner.config_file, None) + self.assertEquals(self.runner.server, "localhost") + self.assertEquals(self.runner.port, 5347) + self.assertEquals(self.runner.secret, "secret") + self.assertEquals(self.runner.service_jid, "jcl.localhost") + self.assertEquals(self.runner.language, "en") + self.assertEquals(self.runner.db_url, "sqlite:///var/spool/jabber/jcl.db") + self.assertEquals(self.runner.pid_file, "/var/run/jabber/jcl.pid") + self.assertFalse(self.runner.debug) + + def test_configure_configfile(self): + self.runner.config_file = "src/jcl/tests/jcl.conf" + self.runner.configure() + self.assertEquals(self.runner.server, "test_localhost") + self.assertEquals(self.runner.port, 42) + self.assertEquals(self.runner.secret, "test_secret") + self.assertEquals(self.runner.service_jid, "test_jcl.localhost") + self.assertEquals(self.runner.language, "test_en") + self.assertEquals(self.runner.db_url, "test_sqlite://root@localhost/var/spool/jabber/test_jcl.db") + self.assertEquals(self.runner.pid_file, "/var/run/jabber/test_jcl.pid") + self.assertFalse(self.runner.debug) + + def test_configure_commandline_shortopt(self): + sys.argv = ["", "-c", "src/jcl/tests/jcl.conf", \ + "-S", "test2_localhost", \ + "-P", "43", \ + "-s", "test2_secret", \ + "-j", "test2_jcl.localhost", \ + "-l", "test2_en", \ + "-u", "sqlite:///tmp/test_jcl.db", \ + "-p", "/tmp/test_jcl.pid"] + self.runner.configure() + self.assertEquals(self.runner.server, "test2_localhost") + self.assertEquals(self.runner.port, 43) + self.assertEquals(self.runner.secret, "test2_secret") + self.assertEquals(self.runner.service_jid, "test2_jcl.localhost") + self.assertEquals(self.runner.language, "test2_en") + self.assertEquals(self.runner.db_url, "sqlite:///tmp/test_jcl.db") + self.assertEquals(self.runner.pid_file, "/tmp/test_jcl.pid") + self.assertFalse(self.runner.debug) + + def test_configure_commandline_longopt(self): + sys.argv = ["", "--config-file", "src/jcl/tests/jcl.conf", \ + "--server", "test2_localhost", \ + "--port", "43", \ + "--secret", "test2_secret", \ + "--service-jid", "test2_jcl.localhost", \ + "--language", "test2_en", \ + "--db-url", "sqlite:///tmp/test_jcl.db", \ + "--pid-file", "/tmp/test_jcl.pid"] + self.runner.configure() + self.assertEquals(self.runner.server, "test2_localhost") + self.assertEquals(self.runner.port, 43) + self.assertEquals(self.runner.secret, "test2_secret") + self.assertEquals(self.runner.service_jid, "test2_jcl.localhost") + self.assertEquals(self.runner.language, "test2_en") + self.assertEquals(self.runner.db_url, "sqlite:///tmp/test_jcl.db") + self.assertEquals(self.runner.pid_file, "/tmp/test_jcl.pid") + self.assertFalse(self.runner.debug) + + def test_setup_pidfile(self): + try: + self.runner.pid_file = "/tmp/jcl.pid" + self.runner.setup_pidfile() + pidfile = open("/tmp/jcl.pid", "r") + pid = int(pidfile.read()) + pidfile.close() + self.assertEquals(pid, os.getpid()) + finally: + os.remove("/tmp/jcl.pid") + + def test__run(self): + self.runner.pid_file = "/tmp/jcl.pid" + self.runner.db_url = DB_URL + def do_nothing(): + pass + self.runner._run(do_nothing) + account.hub.threadConnection = connectionForURI(self.runner.db_url) + # dropTable should succeed because tables should exist + Account.dropTable() + PresenceAccount.dropTable() + del account.hub.threadConnection + os.unlink(DB_PATH) + self.assertFalse(os.access("/tmp/jcl.pid", os.F_OK)) + + def test__get_help(self): + self.assertNotEquals(self.runner._get_help(), None) + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(JCLRunner_TestCase, 'test')) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') +