merging from devel
authorSandro Knauß <knauss@netzguerilla.net>
Thu, 27 Sep 2012 17:15:46 +0200
changeset 302 3f4bdea2abbf
parent 90 eb04ac3a8327 (current diff)
parent 301 d5ebbcccc41b (diff)
child 303 9708742ff89c
merging from devel
MyIro.inst
README
doc/css/960.css
doc/css/reset.css
doc/css/style-ie.css
doc/css/style.css
doc/current.html
doc/images/favicon.png
doc/images/netzguerilla-3.png
doc/impressum.html
doc/index.html
doc/new.html
doc/tmpl/current.html
doc/tmpl/impressum.html
doc/tmpl/new.html
iro/.eric4project/iro.e4p
iro/.eric4project/iro.e4q
iro/.eric4project/iro.e4t
iro/MyIro_daemon.inst
iro/acounting.py
iro/anbieter/FAX_de.py
iro/anbieter/__init__.py
iro/anbieter/anbieter.py
iro/anbieter/content.py
iro/anbieter/geonet.py
iro/anbieter/gsm0338.py
iro/anbieter/sipgate.py
iro/anbieter/smstrade.py
iro/anbieter/smtp.py
iro/anbieter/telnumber.py
iro/anbieter/tests/testTelnumber.py
iro/database.py
iro/dump_test_log.py
iro/iro.api
iro/iro.e4p
iro/iro.py
iro/job.py
iro/joblist.py
iro/merlin
iro/merlin_daemon
iro/newuser.py
iro/providerlist.py
iro/tests/__init__.py
iro/tests/stopableServer.py
iro/tests/test.pdf
iro/tests/testJob.py
iro/tests/testWorker.py
iro/tests/testXMLRPCServer.py
iro/tests/testloglock.py
iro/user.py
iro/worker.py
iro/xmlrpc/AuthentificateXMLRPCServer.py
iro/xmlrpc/SecureAuthentificateXMLRPCServer.py
iro/xmlrpc/SecureUserdbXMLRPCServer.py
iro/xmlrpc/SecureXMLRPCServer.py
iro/xmlrpc/__init__.py
--- a/.hgignore	Wed Dec 21 22:07:48 2011 +0100
+++ b/.hgignore	Thu Sep 27 17:15:46 2012 +0200
@@ -3,6 +3,8 @@
 *.pyc
 *.conf
 .*.swp
+.*.swo
+.coverage
 *orig
 *.log
 MyIro
@@ -12,3 +14,14 @@
 iro.egg-info/*
 dist/*
 build/*
+_trial_temp/*
+*.egg/*
+doc/_build/*
+twisted/plugins/dropin.cache
+*.db
+*.old
+_build/
+web/dev/
+web/*.html
+web/files/*.tar.gz
+web.tar.gz
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE.txt	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,20 @@
+Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+
+This file is part of Iro.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+#Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MANIFEST.in	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,6 @@
+include *.txt
+include MANIFEST.in
+include iro.conf.inst
+recursive-include extras *
+recursive-include bin *
+
--- a/MyIro.inst	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-#!/usr/bin/python
-from iro import start,User,Admin
-import logging
-from logging.handlers import WatchedFileHandler
-format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s'
-fileHandler=WatchedFileHandler("/var/log/MyIro.log")
-fileHandler.setFormatter(logging.Formatter(format))
-fileHandler.setLevel(logging.INFO)
-logging.getLogger().setLevel(logging.INFO)
-logging.getLogger().addHandler(fileHandler)
-
-userlist=[{"name":"blablub","password":"foo",  "class":User},
-        {"name":"admin","password":"admin", "class": Admin},
-        ]
-start(userlist)
--- a/README	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-Zum installieren braucht Iro noch:
-
-- Diese wird in ./iro.conf, ~/.iro.conf und /etc/iro/iro.conf gesucht.
-Ein Beispiel ist in iro.conf.inst abgelegt.
-- Desweitern muss noch ein Script da sein (Bespiel in MyIro.inst), in dem die Benutzerdatenbank angegeben wird).
-- zuguter letzt muss noch eine Datenbank angelegt werden. Das Schema ist in iro.sql abgelegt.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.txt	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,44 @@
+Iro is a non blocking server for sending a message to a bunsh of recipients. It can handle different types of message and can be extended easially.
+It was initially written for sms backend of http://castorticker.de. There are different backends are supported by Iro. 
+Iro helps you for these kinds of tasks: You want to send a message to some recipient over a different backend, than for other recipients.
+Or you want to define fallback strategies: If one messages can't be send over this backend use another.
+Because Iro also knows users, you can create bills for different clients.
+
+Installation of Iro
+===================
+
+Install the module via ``setup.py install`` und ran ``iro-install``. That will create a sample configuration file named ``iro.conf``.
+Update the configuration file and run ``iro-install install``  afterwards. That will create the Database and Offers for you.
+After that you have to `adding_user`. Now you are ready to start Iro ``twisted iro``.
+
+
+.. _adding_user:
+
+Add User and Userrights
+-----------------------
+
+You'll need user and right to spefific Offers, that a user can send with a specific Offer. Till now, it is the only way to add/modify user/userrights. 
+
+ 
+You can add users and userright via SQL::
+
+  INSERT INTO user (name,apikey,ng_kunde) VALUES("test","a1b2c3");
+  INSERT INTO userrights (user,offer,default) VALUES ("test",OFFERNAME,NULL);
+
+or use python/ipython::
+
+  >>> import iro.model.schema
+  >>> from iro.model.schema import *
+  >>> from sqlalchemy import create_engine
+  >>> from iro.model.utils import WithSession
+  >>> engine = create_engine(DBURL)
+  >>> with WithSession(engine) as session:
+  ...   u = User(name="test",apikey="a1a2c3")      
+  ...   session.add(u)
+  ...   o = session.query(Offer).filter_by(name=OFFERNAME).first()
+  ...   u.rights.append(Userright(o, default=None))
+  ...   session.commit()
+  >>>
+
+.. note::
+  Please make sure that, the apikey only using *hex digest [0-9a-f]*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/iro-adduser	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,104 @@
+#!/usr/bin/env python2.7
+
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+#
+# This file is part of Iro.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.python import log
+
+from sqlalchemy import create_engine, pool
+from sqlalchemy.exc import ArgumentError
+import sys,os
+import random
+
+import logging
+
+from iro.controller.pool import dbPool
+from iro import config, __version__, install
+from iro.model.schema import Offer, User, Userright
+from iro.model.utils import WithSession
+
+observer = log.PythonLoggingObserver()
+observer.start()
+
+logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s')
+
+import argparse
+
+parser = argparse.ArgumentParser(description='Iro adduser.')
+parser.add_argument('-v', '--version', action='version', version="%s %s"%(os.path.basename(sys.argv[0]),__version__))
+parser.add_argument('name', help="username")
+parser.add_argument('offers', nargs='+', help="offers for user")
+args = parser.parse_args()
+
+if not install.checkConfig():
+    logging.error("iro.conf is not in right format.")
+    logging.info("Please edit iro.conf")
+    sys.exit(1)
+
+config.init()
+
+try:
+    engine = create_engine(config.main.dburl,
+           poolclass = pool.SingletonThreadPool,  pool_size=dbPool.maxthreads, )
+except ArgumentError:
+    logging.error("Can't connect to database")
+    logging.info("Please edit iro.conf")
+    sys.exit(1)
+
+
+if not install.checkDatabaseConnection():
+    logging.error("Can't connect to database")
+    logging.info("Please edit iro.conf")
+    sys.exit(1)
+
+if not install.checkDatabase():
+    logging.error("Database not in right format.")
+    logging.info("Please edit iro.conf and run iro-install --install")
+    sys.exit(1)
+
+
+#test offers
+with WithSession(engine) as session:
+        available_offers = [i[0] for i in session.query(Offer.name)]
+
+error = False
+for o in args.offers:
+    if o not in available_offers:
+        logging.error("Unknown offer: '%s'"%o)
+	error = True
+
+if error:
+    sys.exit(1)
+
+#create apikey
+apikey = "".join([random.choice("0123456789abcdef") for i in range(15)])
+
+#adduser to database
+with WithSession(engine, False) as session:
+    u = User(name=args.name, apikey=apikey)
+    session.add(u)
+    for i,offer in enumerate(args.offers):
+        o = session.query(Offer).filter_by(name=offer).first()
+        u.rights.append(Userright(o,default=i+1))
+    session.commit()
+logging.info("created user %s:"% args.name)
+logging.info("apikey = %s"% apikey)
+logging.info("offers = %s"% ", ".join(args.offers))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/iro-install	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,97 @@
+#!/usr/bin/env python2.7
+
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.python import log
+
+from sqlalchemy import create_engine, pool
+from sqlalchemy.exc import ArgumentError
+import sys,os
+
+import logging
+
+from iro.controller.pool import dbPool
+from iro import config, __version__, install
+
+observer = log.PythonLoggingObserver()
+observer.start()
+
+logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s')
+
+import argparse
+
+parser = argparse.ArgumentParser(description='Iro installer.')
+parser.add_argument('-v', '--version', action='version', version="%s %s"%(os.path.basename(sys.argv[0]),__version__))
+parser.add_argument('--install', action='store_true',
+                           help='will create the right database layout.')
+parser.add_argument('--update', action='store_true',
+                           help='will install all routes and providers.')
+args = parser.parse_args()
+
+if not install.checkConfig():
+    install.createSampleConfig()
+    logging.info("Please edit iro.conf")
+    sys.exit(1)
+
+config.init()
+
+try:
+    engine = create_engine(config.main.dburl,
+           poolclass = pool.SingletonThreadPool,  pool_size=dbPool.maxthreads, )
+except ArgumentError:
+    logging.error("Can't connect to database")
+    logging.info("Please edit iro.conf")
+    sys.exit(1)
+
+
+if not install.checkDatabaseConnection():
+    logging.error("Can't connect to database")
+    logging.info("Please edit iro.conf")
+    sys.exit(1)
+    
+if not install.checkDatabase():
+    logging.error("Database not in right format.")
+    if args.install:
+        install.createDatabase()
+        logging.info("Database layout created.")
+    else:
+        logging.info("Please edit iro.conf and run %s --install"%os.path.basename(sys.argv[0]))
+        sys.exit(1)
+
+routes = [ s for s in config.configParser.sections() if not s in ["main",]]
+ao =  install.getAllRoutes(routes, False)
+for o in  ao["orphand"]:
+    logging.info("Offer(%s) is orphand (no route using this offer)."%o)
+if ao["added"]:
+    if args.install or args.update:
+        ao = install.getAllRoutes(routes,True)
+        for a in  ao["added"]:
+            logging.info("Added Offer(%s)"%a)
+        logging.info('Updated offerlist.')
+    else:
+        logging.warning('offerlist is not up-to-date.')
+        logging.info("Please run %s --update"%os.path.basename(sys.argv[0]))
+        sys.exit(1)
+
+logging.info("You can just start your iro server.")
+
+
--- a/createdoc.py	Wed Dec 21 22:07:48 2011 +0100
+++ b/createdoc.py	Thu Sep 27 17:15:46 2012 +0200
@@ -1,14 +1,73 @@
 #!/usr/bin/env python2.7
+
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
 # -*- coding: utf-8 -*- 
 
 from genshi.template import TemplateLoader
-
+from genshi import Markup
 loader = TemplateLoader('doc/tmpl', auto_reload=True)
 
+
+from glob import glob
 import re
+import os.path
 import inspect
-from iro.user import User as Current
-from iro.newuser import User as New
+from pkg_resources import parse_version
+
+from docutils.core import publish_doctree, publish_from_doctree, publish_string
+from docutils.utils import new_document
+from docutils.writers.html4css1 import Writer,HTMLTranslator
+
+from iro.view.xmlrpc import TwistedInterface as Current 
+from iro import __version__
+from createerm import createSchemaPlot, tables, tables_cls
+
+#redering text with rest syntax
+class NoHeaderHTMLTranslator(HTMLTranslator):
+    def __init__(self, document):
+        HTMLTranslator.__init__(self,document)
+        self.body_prefix = []
+        self.body_suffix = []
+
+_w = Writer()
+_w.translator_class = NoHeaderHTMLTranslator
+_w.visitor_attributes = ("html_body",)
+
+def d():
+    subs = _w.interpolation_dict()
+    return "%(html_body)s"%subs
+
+_w.apply_template = d
+
+def reSTify(s):
+    d = new_document("")
+    if s.tagname == "paragraph":
+        d.append(s[0])
+    else:
+        d.append(s)
+
+    return publish_from_doctree(d, writer=_w)
+
 
 
 
@@ -30,108 +89,145 @@
 
 
 class Keyword():
-    def __init__(self,name,typ,description):
+    def __init__(self, name=None, typ=None, description=None):
         self.name=name
         self.typ=typ
         self.description=description
 
-def section(text):
-    ret={}
-    li=[]
-    kw=None
-    for line in text.split("\n"):
-        if re.match("^\s*$",line):
-            continue
-
-        if line[0] not in (" ","\t"):
-            if kw:
-                ret[kw.name]=kw
-                li.append(kw)
-            l=re.match(r"^(?P<name>[a-zA-Z0-9-_.]*)\[(?P<typ>[a-zA-Z0-9-_|]*)\]:(?P<d>.*)$",line)
-            kw=Keyword(name=l.group("name"),typ=l.group("typ"),description=l.group("d"))
-        else:
-            kw.description+="\n"+line.strip()
-    if kw:
-        ret[kw.name]=kw
-        li.append(kw)
-    return ret,li
-
-
+    def __repr__(self):
+        return '<Keyword("%s", "%s", "%s")>'%(self.name, self.typ, self.description)
 
 def keywords(f):
-    doc=f.__doc__
-    kwds=re.search("Keywords:\n(?P<keywords>(?P<whitespace>\s*)(.+\n)*)\n",doc)
-    k=kwds.group("keywords")
-    #get rid of beginning whitespaces
-    k=re.sub(re.compile(r"^"+kwds.group("whitespace"),re.M),"",k)
-    return section(k)
+    NORMAL = 0
+    TYPE = 1
+    pd = publish_doctree(f.__doc__.decode('utf8'))
+
+    kws={}
+    for child in pd[1][0]:
+        kw = Keyword()
+        ftyp = NORMAL
+        for sc in child:
+            if sc.tagname == "field_name":
+                p = sc.astext().split()
+                if p[0] in ["param",]:
+                    if len(p) == 3:             #param typ name
+                        kw.name = p[2]
+                        kw.typ = p[1]
+                    if len(p) == 2:
+                        kw.name = p[1]
+                elif p[0] == "type":
+                    kw = kws[p[1]]
+                    ftyp=TYPE
+                elif p[0] in ["return","rtyp"]:
+                    break
+                else:
+                    raise Exception("Unknown field_name: %s"%(p[0]))
+            if sc.tagname == "field_body":
+                if ftyp == NORMAL:
+                    kw.description = Markup(reSTify(sc[0]))
+                if ftyp == TYPE:
+                    kw.typ = sc[0][0]
+        else:
+            kws[kw.name] = kw
+    return kws
 
 def ret(f):
-    doc=f.__doc__
-    kwds=re.search("Return:\n(?P<ret>(?P<whitespace>\s*)(.+\n)*)\n",doc)
-    k=kwds.group("ret")
-    #get rid of beginning whitespaces
-    k=re.sub(re.compile(r"^"+kwds.group("whitespace"),re.M),"",k)
-    return section(k)
-
-
-
+    NORMAL = 0
+    TYPE = 1
+    
+    pd = publish_doctree(f.__doc__.decode('utf8'))
+    for child in pd[1][0]:
+        kw = Keyword(name="return")
+        ftyp = NORMAL
+        for sc in child:
+            if sc.tagname == "field_name":
+                p = sc.astext().split()
+                if p[0] == "return":
+                    if len(p) == 2:
+                        kw.typ = p[1]
+                elif p[0] == "rtype":
+                    ftyp=TYPE
+                elif p[0] in ["param","type"]:
+                    break
+                else:
+                    raise Exception("Unknown field_name: %s"%(p[0]))
+            if sc.tagname == "field_body":
+                if ftyp == NORMAL:
+                    kw.description = Markup(reSTify(sc[0]))
+                if ftyp == TYPE:
+                    kw.typ = sc[0][0]
+        else:
+            return kw
+    
+    raise Exception("no return description")
+ 
 class Arg():
     def __init__(self,name,f):
         self.name=name
-        k,_ = keywords(f)
+        k = keywords(f)
         kwd=k[name]
         self.typ=kwd.typ
         self.description=kwd.description
 
-        
-
 class Method(Link):
     def __init__(self,name,methods):
         title=name[0].upper()+name[1:]
         Link.__init__(self,name,title)
         m=methods[name]
         (args, varargs, keywords, defaults)=inspect.getargspec(m)
-        args= [b for b in args if b is not "self"]
+        a=[]
+        for b in args:
+            if b in ("self","session"):
+                continue
+            else:
+                a.append(b)
+        
+        args = a
         self.func_line=inspect.formatargspec(args, varargs, keywords, defaults)
-        self.description = m.__doc__.split("\n")[0]
+        pd = publish_doctree(m.__doc__)
+        if pd[0].tagname == "paragraph":
+            self.description = pd[0].astext()
         self.args=[Arg(a,m) for a in args]
-        _, self.rets=ret(m)
+        self.rets=[ret(m)]
+
+class Table(Link):
+    def __init__(self,cls):
+        name=cls.__name__
+        self.tablename=cls.__tablename__
+        title=self.tablename[0].upper()+self.tablename[1:]
+        Link.__init__(self,name,title)
+        
+        self.description = Markup(publish_string(cls.__doc__,writer=_w))
+
+class File:
+    def __init__(self,path):
+        self.version = re.search("/iro-(.*).tar.gz",path).group(1)
+        self.name = os.path.basename(path)
 
 
 def main():
     sites=[Site("index.html","Iro"),
            Site("current.html","API Documentation"),
-           Site("new.html","geplante API Documentation"),
-           Site("impressum.html","Impressum"),
+           Site("database.html","Datenbase Schema"),
+           Site("about.html","About us"),
            ]
 
-    current_methods = dict(inspect.getmembers(Current(None,None)))
-    current=[
-            Method("startSMS",current_methods),
-            Method("startFAX",current_methods),
-            Method("startMail",current_methods),
-            
-            Method("status",current_methods),
-            Method("stop",current_methods),
-            
-            Method("getProvider",current_methods),
-            Method("getDefaultProvider",current_methods),
-            ]
+    files=[File(f) for f in glob("web/files/*tar.gz")]
+    files.sort(key=lambda a:parse_version(a.version),reverse=True)
+    code ="files/iro-%s.tar.gz"%__version__
 
-    new_methods = dict(inspect.getmembers(New()))
-    newm=[
-            Method("sms",new_methods),
-            Method("fax",new_methods),
-            Method("mail",new_methods),
-            
-            Method("status",new_methods),
-            Method("stop",new_methods),
-            
-            Method("routes",new_methods),
-            Method("defaultRoute",new_methods),
-            ]
+    current_methods = dict(inspect.getmembers(Current()))
+    current=[ Method(i,current_methods) for i in Current().listMethods() if i  != "listMethods" ]
+    
+    t = [Table(tables_cls[str(f)]) for f in tables]
+    createSchemaPlot('web/images/db-schema.svg')
 
+    gd = {"sites":sites,
+          "current":current,
+          "tables": t,
+          "code":code,
+          "files":files
+        }
 
     for site in sites:
         print("generiere %s" % site.name)
@@ -139,8 +235,8 @@
         def a(s):
             if s == site:
                 return {"class":"menu active"}
-        stream = tmpl.generate(sites=sites,active=a,current=current,new=newm)
-        with open('doc/'+site.name, "w") as g:
+        stream = tmpl.generate(active=a, **gd)
+        with open('web/'+site.name, "w") as g:
             g.write(stream.render('html', doctype='html'))
 
 if __name__ == '__main__':
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/createerm.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,71 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from iro.model import schema
+
+tables = schema.Base.metadata.sorted_tables
+
+tables_cls={}
+for i in dir(schema):
+    if i.startswith("__") or i == "Base":
+        continue
+    try:
+        a = getattr(schema,i)
+        tables_cls[a.__tablename__] = a
+    except AttributeError:
+        pass
+
+
+#schema plot
+def createSchemaPlot(fname):
+    from sqlalchemy_schemadisplay3 import create_schema_graph
+    graph = create_schema_graph(metadata=schema.Base.metadata,
+        show_datatypes=True, # The image too large if datatypes shown
+        show_indexes=True, # ditto for indexes
+        rankdir='LR', # From left to right (instead of top to bottom)
+        concentrate=True, # Don't try to join the relation lines together
+    )
+
+    graph.set_size('6.5,10')
+    #graph.set_ratio("fill")
+    graph.write_svg(fname)
+
+#umlplot
+def createUMLPlot(fname):
+    from sqlalchemy_schemadisplay3 import create_uml_graph
+    from sqlalchemy.orm import class_mapper
+    mappers = []
+    for attr in dir(schema.model):
+        if attr[0] == '_': continue
+        try:
+            cls = getattr(schema.model, attr)
+            mappers.append(class_mapper(cls))
+        except:
+            pass
+    #pass them to the function and set some formatting options
+    graph = create_uml_graph(mappers,
+        show_operations=False, # not necessary in this case
+        show_multiplicity_one=True, # some people like to see the ones
+        show_attributes=True,
+    )
+    graph.set_size('6,5')
+    graph.set_ratio("fill")
+    graph.write_png('test.png')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/conf.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,267 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*-
+#
+# Iro documentation build configuration file, created by
+# sphinx-quickstart on Sat Mar 24 12:06:24 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os, re 
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('..'))
+from iro import __version__
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode','sphinx.ext.extlinks']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Iro'
+copyright = u'2012, netzguerilla.net'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version =  re.match("^([0-9\.]+)",__version__).groups()[0]
+# The full version, including alpha/beta/rc tags.
+release = __version__
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Irodoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'Iro.tex', u'Iro Documentation',
+   u'Sandro Knauß', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'iro', u'Iro Documentation',
+     [u'Sandro Knauß'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'Iro', u'Iro Documentation',
+   u'Sandro Knauß', 'Iro', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+autoclass_content = 'both'
+
+extlinks = {'irofile': ("../files/iro-%s.tar.gz","Iro ")}
--- a/doc/css/960.css	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-.container_12,.container_16{margin-left:auto;margin-right:auto;width:960px}.grid_1,.grid_2,.grid_3,.grid_4,.grid_5,.grid_6,.grid_7,.grid_8,.grid_9,.grid_10,.grid_11,.grid_12,.grid_13,.grid_14,.grid_15,.grid_16{display:inline;float:left;margin-left:10px;margin-right:10px}.push_1,.pull_1,.push_2,.pull_2,.push_3,.pull_3,.push_4,.pull_4,.push_5,.pull_5,.push_6,.pull_6,.push_7,.pull_7,.push_8,.pull_8,.push_9,.pull_9,.push_10,.pull_10,.push_11,.pull_11,.push_12,.pull_12,.push_13,.pull_13,.push_14,.pull_14,.push_15,.pull_15{position:relative}.container_12 .grid_3,.container_16 .grid_4{width:220px}.container_12 .grid_6,.container_16 .grid_8{width:460px}.container_12 .grid_9,.container_16 .grid_12{width:700px}.container_12 .grid_12,.container_16 .grid_16{width:940px}.alpha{margin-left:0}.omega{margin-right:0}.container_12 .grid_1{width:60px}.container_12 .grid_2{width:140px}.container_12 .grid_4{width:300px}.container_12 .grid_5{width:380px}.container_12 .grid_7{width:540px}.container_12 .grid_8{width:620px}.container_12 .grid_10{width:780px}.container_12 .grid_11{width:860px}.container_16 .grid_1{width:40px}.container_16 .grid_2{width:100px}.container_16 .grid_3{width:160px}.container_16 .grid_5{width:280px}.container_16 .grid_6{width:340px}.container_16 .grid_7{width:400px}.container_16 .grid_9{width:520px}.container_16 .grid_10{width:580px}.container_16 .grid_11{width:640px}.container_16 .grid_13{width:760px}.container_16 .grid_14{width:820px}.container_16 .grid_15{width:880px}.container_12 .prefix_3,.container_16 .prefix_4{padding-left:240px}.container_12 .prefix_6,.container_16 .prefix_8{padding-left:480px}.container_12 .prefix_9,.container_16 .prefix_12{padding-left:720px}.container_12 .prefix_1{padding-left:80px}.container_12 .prefix_2{padding-left:160px}.container_12 .prefix_4{padding-left:320px}.container_12 .prefix_5{padding-left:400px}.container_12 .prefix_7{padding-left:560px}.container_12 .prefix_8{padding-left:640px}.container_12 .prefix_10{padding-left:800px}.container_12 .prefix_11{padding-left:880px}.container_16 .prefix_1{padding-left:60px}.container_16 .prefix_2{padding-left:120px}.container_16 .prefix_3{padding-left:180px}.container_16 .prefix_5{padding-left:300px}.container_16 .prefix_6{padding-left:360px}.container_16 .prefix_7{padding-left:420px}.container_16 .prefix_9{padding-left:540px}.container_16 .prefix_10{padding-left:600px}.container_16 .prefix_11{padding-left:660px}.container_16 .prefix_13{padding-left:780px}.container_16 .prefix_14{padding-left:840px}.container_16 .prefix_15{padding-left:900px}.container_12 .suffix_3,.container_16 .suffix_4{padding-right:240px}.container_12 .suffix_6,.container_16 .suffix_8{padding-right:480px}.container_12 .suffix_9,.container_16 .suffix_12{padding-right:720px}.container_12 .suffix_1{padding-right:80px}.container_12 .suffix_2{padding-right:160px}.container_12 .suffix_4{padding-right:320px}.container_12 .suffix_5{padding-right:400px}.container_12 .suffix_7{padding-right:560px}.container_12 .suffix_8{padding-right:640px}.container_12 .suffix_10{padding-right:800px}.container_12 .suffix_11{padding-right:880px}.container_16 .suffix_1{padding-right:60px}.container_16 .suffix_2{padding-right:120px}.container_16 .suffix_3{padding-right:180px}.container_16 .suffix_5{padding-right:300px}.container_16 .suffix_6{padding-right:360px}.container_16 .suffix_7{padding-right:420px}.container_16 .suffix_9{padding-right:540px}.container_16 .suffix_10{padding-right:600px}.container_16 .suffix_11{padding-right:660px}.container_16 .suffix_13{padding-right:780px}.container_16 .suffix_14{padding-right:840px}.container_16 .suffix_15{padding-right:900px}.container_12 .push_3,.container_16 .push_4{left:240px}.container_12 .push_6,.container_16 .push_8{left:480px}.container_12 .push_9,.container_16 .push_12{left:720px}.container_12 .push_1{left:80px}.container_12 .push_2{left:160px}.container_12 .push_4{left:320px}.container_12 .push_5{left:400px}.container_12 .push_7{left:560px}.container_12 .push_8{left:640px}.container_12 .push_10{left:800px}.container_12 .push_11{left:880px}.container_16 .push_1{left:60px}.container_16 .push_2{left:120px}.container_16 .push_3{left:180px}.container_16 .push_5{left:300px}.container_16 .push_6{left:360px}.container_16 .push_7{left:420px}.container_16 .push_9{left:540px}.container_16 .push_10{left:600px}.container_16 .push_11{left:660px}.container_16 .push_13{left:780px}.container_16 .push_14{left:840px}.container_16 .push_15{left:900px}.container_12 .pull_3,.container_16 .pull_4{left:-240px}.container_12 .pull_6,.container_16 .pull_8{left:-480px}.container_12 .pull_9,.container_16 .pull_12{left:-720px}.container_12 .pull_1{left:-80px}.container_12 .pull_2{left:-160px}.container_12 .pull_4{left:-320px}.container_12 .pull_5{left:-400px}.container_12 .pull_7{left:-560px}.container_12 .pull_8{left:-640px}.container_12 .pull_10{left:-800px}.container_12 .pull_11{left:-880px}.container_16 .pull_1{left:-60px}.container_16 .pull_2{left:-120px}.container_16 .pull_3{left:-180px}.container_16 .pull_5{left:-300px}.container_16 .pull_6{left:-360px}.container_16 .pull_7{left:-420px}.container_16 .pull_9{left:-540px}.container_16 .pull_10{left:-600px}.container_16 .pull_11{left:-660px}.container_16 .pull_13{left:-780px}.container_16 .pull_14{left:-840px}.container_16 .pull_15{left:-900px}.clear{clear:both;display:block;overflow:hidden;visibility:hidden;width:0;height:0}.clearfix:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}* html .clearfix,*:first-child+html .clearfix{zoom:1}
\ No newline at end of file
--- a/doc/css/reset.css	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0}
\ No newline at end of file
--- a/doc/css/style-ie.css	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-#head #menu {
-	
-	width: 750px;
-	
-}
-
-	#head #menu li {
-		
-		display: inline;
-		position: relative;
-		float: left;
-		margin: 0px;
-		padding: 0px;
-		
-	}
\ No newline at end of file
--- a/doc/css/style.css	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,975 +0,0 @@
-
-body {
-	
-	background-color: #eee;
-	color: #111;
-	margin: 0px;
-	padding: 0px;
-	font-family: "DroidSans", "Trebuchet MS", sans-serif;
-	line-height: 1.6em;
-	font-size: 1.3em;
-	
-}
-
-	#head-container {
-		
-		display: block;
-		position: relative;
-		width: 100%;
-		height: 90px;
-		
-		background: #111 url(../images/head.png) top left repeat-x;
-		
-	}
-	
-		#head {
-			
-			display: block;
-			position: relative;
-			width: 960px;
-			height: 90px;
-			margin: 0px auto;			
-			
-		}
-			
-			#head #logo {
-			
-				display: block;
-				position: absolute;
-				left: 0px;
-				bottom: 10px;
-				width: 200px;
-				height: 70px;
-			
-			}
-
-				#head #logo a {
-					
-					display: block;
-					position: relative;
-					width: 200px;
-					padding-left: 70px;
-					height: 70px;
-					background: url(../images/netzguerilla-3.png) 0px 0px no-repeat;
-					text-decoration: none;
-					
-				}
-
-				#head #logo a span {
-			
-					display: none;
-			
-				}
-				
-			#head #menu {
-				
-				display: block;
-				position: absolute;
-				right: 0px;
-				bottom: 0px;
-				margin: 0px;
-				padding: 0px;
-				
-			}
-
-				#head #menu li {
-					
-					display: inline-block;
-					position: relative;
-					margin: 0px;
-					padding: 0px;
-					
-				}
-				
-					#head #menu li a {
-					
-						display: block;
-						position: relative;
-						
-						color: #c03;
-						text-decoration: none;
-						text-transform: uppercase;
-						padding: 0px 7px;
-						background: #333 url(../images/fade-top.png) bottom left repeat-x;
-						margin-left: 5px;
-						font-size: .8em;
-						font-weight: bold;
-						
-						-webkit-border-top-left-radius: 4px;
-						-webkit-border-top-right-radius: 4px;
-						-moz-border-radius-topleft: 4px;
-						-moz-border-radius-topright: 4px;
-						border-top-left-radius: 4px;
-						border-top-right-radius: 4px;
-						
-					}
-					
-						#head #menu li a:hover {
-						
-							background: #c03 url(../images/fade-top.png) bottom left repeat-x;
-							color: #fff;
-							
-						}
-
-						#head #menu li a.active {
-							
-							background-color: #eee;
-							background-image: none;
-							color: #000;
-							
-						}
-					
-	#content-container {
-		
-		display: block;
-		position: relative;
-		
-	}
-		#content { 
-		
-			padding: 20px 0px;		
-			
-		}
-	
-		#main {}
-		
-			#main h2 {
-				
-				display: block;
-				position: relative;
-				color: #a01;
-				font-size: 1.8em;
-				line-height: 1.4em;
-				margin-bottom: 15px;
-				text-shadow: 0px 0px 5px #fff;
-				
-			}
-			
-			#main h3 {
-			
-				display: block;
-				position: relative;
-				color: #b02;
-				font-size: 1.4em;
-				line-height: 1.4em;
-				margin: 10px 0px;
-			
-			}
-			
-			#main h4 {
-			
-				display: block;
-				position: relative;
-				color: #a01;
-				font-size: 1.3em;
-				line-height: 1.4em;
-				margin: 10px 0px;
-			
-			}
-			
-			#main h5 {
-			
-				display: block;
-				position: relative;
-				color: #900;
-				font-size: 1.2em;
-				line-height: 1.4em;
-				margin: 10px 0px;
-			
-			}
-			
-			#main .item {
-				
-				display: block;
-				position: relative;
-				
-				padding: 30px 40px;
-				
-				border-radius: 15px;
-				-o-border-radius: 15px;
-				-moz-border-radius: 15px;
-				-webkit-border-radius: 15px;
-
-				-moz-box-shadow: 0px 0px 20px #eee;
-				-o-box-shadow: 0px 0px 20px #eee;
-				-webkit-box-shadow: 0px 0px 20px #eee;
-				box-shadow: 0px 0px 20px #eee;
-				
-				background-color: #fff;
-				margin-bottom: 20px;
-				font-size: .9em;
-				
-			}
-			
-				#main .item p {
-					
-					margin: 5px 0px;
-					
-				}
-						
-				#main .item a {
-					
-					text-decoration: none;
-					color: #b02;
-					
-				}
-				
-				#main .item ol {
-					
-					list-style-type: none;
-					list-style-position: inside;
-					
-				}
-				
-					#main .item ol li {
-						
-						margin-left: 2em;
-						
-					}
-																			
-				#main .item table {
-
-					width: 100%;
-					margin: 0px;
-
-					-webkit-border-radius: 5px;
-					-moz-border-radius: 5px;
-					border-radius: 5px;
-
-					-moz-box-shadow: 0px 0px 15px #ccc;
-					-o-box-shadow: 0px 0px 15px #ccc;
-					-webkit-box-shadow: 0px 0px 15px #ccc;
-					box-shadow: 0px 0px 15px #ccc;
-
-
-				}
-				
-					#main .item table.docs {
-				
-						font-size: .8em;
-
-					}
-
-					#main .item table.docs td {
-				
-						font-weight: bold;
-						padding-right: 0.2em;
-						min-width: 120px;
-
-					}
-
-					#main .item table.docs td+td {
-				
-						font-weight: normal;
-						padding-right: 0.2em;
-						min-width: 80px;
-
-					}
-
-					#main .item table.docs td+td+td {
-					
-						font-weight: normal;
-						padding-right: 0em;
-						white-space: normal;
-
-					}
-
-					#main .item table td {
-
-						background-color: #eee;
-						line-height: 1.7em;
-
-					}
-
-					#main .item table thead tr td {
-
-						background-color: #c03;
-						color: #fff;
-						font-size: .8em;
-						padding: 5px;
-
-					}
-
-					#main .item table tbody tr td {
-
-						color: #222;
-						vertical-align: top;
-
-					}
-
-					#main .item table tbody tr td.content {
-
-						padding: 10px;
-						font-size: .8em;
-						
-					}
-
-					#main .item table tbody tr td.content strong.title {
-
-						display: inline-block;
-						position: relative;
-						max-width: 140px;
-						overflow: hidden;
-						
-					}
-
-					#main .item table tbody tr td.content span.meta {
-
-						display: block;
-						position: relative;
-						max-width: 140px;
-						overflow: hidden;
-						line-height: 1.4em;
-						font-size: .8em;
-						
-					}
-
-					#main .item table tbody tr td.content span.hate {
-
-						display: block;
-						position: relative;
-						line-height: 1.4em;
-						border: 1px solid #ccc;
-						padding: 10px;
-						text-align: justify;
-						
-					}
-					#main .item table tbody tr:nth-child(odd) td {
-
-						background-color: #f9f9f9;
-
-					}
-
-					#main .item table tbody tr:nth-child(even) td {
-
-						background-color: #f6f6f6;
-
-					}
-
-					#main .item table tr td:first-child {
-
-						padding-left: 5px;
-
-					}
-
-					#main .item table tr td:last-child {
-
-						padding-right: 5px;
-
-					}
-
-					#main .item table tr td.controls {
-
-						width: 80px;
-						text-align: right;
-
-					}
-
-					#main .item table tr td.controls a {
-
-						display: inline-block;
-						width: 20px;
-						height: 20px;
-						overflow: hidden;
-						text-indent: 30px;
-						background-color: #eee;
-
-						-moz-box-shadow: 0px 0px 5px #ccc;
-						-o-box-shadow: 0px 0px 5px #ccc;
-						-webkit-box-shadow: 0px 0px 5px #ccc;
-						box-shadow: 0px 0px 5px #ccc;
-
-						-webkit-border-radius: 3px;
-						-moz-border-radius: 3px;
-						border-radius: 3px;
-
-						vertical-align: bottom;
-						margin: 0px 0px 4px 2px;
-
-						background-image: url(../images/icons/bug.png);
-						background-position: center center;
-						background-repeat: no-repeat;
-
-					}	
-
-						#main .item table tr td.controls a:hover {
-
-							background-color: #c03;
-
-						}
-
-						#main .item table tr td.controls a.edit {
-
-							background-image: url(../images/icons/pencil.png);		
-
-						}
-
-						#main .item table tr td.controls a.delete {
-
-							background-image: url(../images/icons/bin.png);		
-
-						}
-
-						#main .item table tr td.controls a.details {
-
-							background-image: url(../images/icons/zoom.png);		
-
-						}
-
-						#main .item table tr td.controls a.accept {
-
-							background-image: url(../images/icons/accept.png);		
-
-						}
-
-					/* round corners */
-
-					#main .item table thead tr:first-child td:first-child {
-
-						-webkit-border-top-left-radius: 5px;
-						-moz-border-radius-topleft: 5px;
-						border-top-left-radius: 5px;								
-
-					}
-
-					#main .item table thead tr:first-child td:last-child {
-
-						-webkit-border-top-right-radius: 5px;
-						-moz-border-radius-topright: 5px;
-						border-top-right-radius: 5px;								
-
-					}
-
-					#main .item table tbody tr:last-child td:first-child {
-
-						-webkit-border-bottom-left-radius: 5px;
-						-moz-border-radius-bottomleft: 5px;
-						border-bottom-left-radius: 5px;								
-
-					}
-
-					#main .item table tbody tr:last-child td:last-child {
-
-						-webkit-border-bottom-right-radius: 5px;
-						-moz-border-radius-bottomright: 5px;
-						border-bottom-right-radius: 5px;								
-
-					}
-					
-					#main .item code.copyme {
-						
-						display: block;
-						margin: 0px 0px 10px;
-						padding: 10px;
-						line-height: 1em;
-						border: 1px solid #ccc;
-						background-color: #eee;
-						
-					}
-					
-				/* main view */
-				
-				#main .hate-pagination {
-					
-					display: block;
-					position: relative;
-					line-height: 1.2em;
-					padding: 10px 0px;
-					
-				}
-
-					#main .hate-pagination-back {
-					
-						display: block;
-						position: relative;
-						width: 300px;
-						text-align: left;
-						float: left;
-					
-					}
-
-					#main .hate-pagination-forward {
-					
-						display: block;
-						position: relative;
-						width: 300px;
-						text-align: right;
-						float: right;
-					
-					}
-
-				#main .ad-item {
-
-					display: block;
-					position: relative;
-					border-bottom: 1px solid #ccc;
-					line-height: 1.2em;
-					padding: 20px 0px;
-					text-align: center;
-
-				}
-
-				#main .hate-item {
-					
-					display: block;
-					position: relative;
-					border-bottom: 1px solid #ccc;
-					line-height: 1.2em;
-					padding: 10px 0px;
-					
-				}
-				
-					#main .hate-item .hate-item-icon {
-				
-						display: block;
-						position: relative;
-						width: 48px;
-						padding: 5px 0px 5px 10px;
-						float: left;
-				
-					}
-
-					#main .hate-item .hate-item-main {
-					
-						display: block;
-						position: relative;
-						width: 550px;
-						float: right;
-					
-					}
-				
-					#main .hate-item .hate-item-author {
-					
-						display: block;
-						position: relative;
-						font-size: .8em;
-						font-weight: bold;
-						color: #222;
-					
-					}
-
-						#main .hate-item .hate-item-author a {
-						
-							color: #222;
-							
-						}
-				
-					#main .hate-item .hate-item-content {
-					
-						display: block;
-						position: relative;
-						font-size: .8em;
-						font-weight: normal;
-						color: #444;
-					
-					}
-				
-					#main .hate-item .hate-item-meta {
-					
-						display: block;
-						position: relative;
-						font-size: .6em;
-						color: #666;
-					
-					}
-				
-					#main .hate-item .hate-item-meta a {
-
-						color: #666;
-
-					}
-					
-					#main .hate-item .hate-item-meta a.facepalm {
-
-						display: inline-block;
-						padding-left: 20px;
-						line-height: 16px;
-						background: url(../images/icons/facepalm.png) top left no-repeat;
-
-					}
-					
-					#main .hate-item:hover .hate-item-meta a.facepalm {
-
-						background: url(../images/icons/facepalm-hl.png) top left no-repeat;
-						
-					}
-
-					#main .hate-item .hate-item-meta span.godwin {
-
-						display: inline-block;
-						padding-left: 20px;
-						line-height: 16px;
-						background: url(../images/icons/godwinbonus.png) top left no-repeat;
-
-					}					
-					
-				#main .hate-item:hover {
-					
-					background-color: #fce;
-					
-				}
-					
-					#main .hate-item:hover a {
-					
-						color: #c03;
-					
-					}
-					
-				#main .hate-detail {
-
-					display: block;
-					position: relative;
-					border-bottom: 1px solid #ccc;
-					line-height: 1.2em;
-					padding: 10px 0px;
-
-				}
-
-					#main .hate-detail .hate-item-icon {
-
-						display: block;
-						position: relative;
-						width: 48px;
-						padding: 5px 0px;
-						float: left;
-
-					}
-
-					#main .hate-detail .hate-item-main {
-
-						display: block;
-						position: relative;
-
-					}
-
-					#main .hate-detail .hate-item-author {
-
-						display: block;
-						position: relative;
-						font-size: 1.2em;
-						line-height: 50px;
-						font-weight: bold;
-						color: #222;
-						width: 560px;
-						float: right;
-
-					}
-
-					#main .hate-detail .hate-item-content {
-
-						display: block;
-						position: relative;
-						font-size: 1.2em;
-						line-height: 1.4em;
-						font-weight: normal;
-						color: #444;
-						padding: 10px 0px;
-
-					}
-
-					#main .hate-detail .hate-item-meta {
-
-						display: block;
-						position: relative;
-						font-size: .8em;
-						color: #666;
-
-					}
-
-					#main .hate-detail .hate-item-meta a {
-
-						color: #666;
-
-					}
-
-						#main .hate-detail .hate-item-meta a.facepalm {
-
-							display: inline-block;
-							padding-left: 20px;
-							line-height: 16px;
-							background: url(../images/icons/facepalm.png) top left no-repeat;
-
-						}
-					
-						#main .hate-detail:hover .hate-item-meta a.facepalm {
-
-							background: url(../images/icons/facepalm-hl.png) top left no-repeat;
-						
-						}
-						
-						#main .hate-detail .hate-item-meta span.godwin {
-
-							display: inline-block;
-							padding-left: 20px;
-							line-height: 16px;
-							background: url(../images/icons/godwinbonus.png) top left no-repeat;
-
-						}					
-						
-
-				/* special stuff */
-				
-				#main span.redacted {
-					
-					display: inline-block;
-					background-color: #333;
-					color: #c03;
-					line-height: 1.1em;
-					padding: 0px 5px;
-					
-				}
-				
-				#main code.apikey {
-					
-					font-size: 1.1em; 
-					background-color: #a01; 
-					color: #fff; 
-					display: inline-block; 
-					padding: 0px 5px;
-					
-				}
-				
-				#main a.bookmarklet {
-					
-					font-size: 1.4em; 
-					background-color: #ccc; 
-					color: #666; 
-					border: 1px solid #666;
-					display: inline-block; 
-					padding: 5px 10px;					
-					
-					-webkit-border-radius: 10px;
-					-moz-border-radius: 10px;
-					border-radius: 10px;
-					
-				}
-				
-			#main form input[type=text],
-			#main form input[type=password] {
-
-				display: block;
-				position: relative;
-				border: 1px solid #999;
-				font-size: .9em;
-				padding: 3px;
-
-				border-radius: 5px;
-				-o-border-radius: 5px;
-				-moz-border-radius: 5px;
-				-webkit-border-radius: 5px;
-
-			}
-
-			#main form input[type=submit] {
-
-				display: block;
-				position: relative;
-				border: 1px solid #ccc;
-				color: #111;
-				font-size: .9em;
-				padding: 2px;
-
-				border-radius: 5px;
-				-o-border-radius: 5px;
-				-moz-border-radius: 5px;
-				-webkit-border-radius: 5px;
-
-
-			}
-
-			#main form span.input {
-
-				display: inline-block;
-				position: relative;
-				border: 1px solid #999;
-				font-size: .9em;
-				padding: 0px 2px;
-				background-color: #fff;
-				border-radius: 5px;
-				-o-border-radius: 5px;
-				-moz-border-radius: 5px;
-				-webkit-border-radius: 5px;
-
-
-			}
-
-				#main form span.input input[type=text] {
-
-					display: inline-block;
-					position: relative;
-					border-width: 0px;
-					font-size: 1em;
-					padding: 0px;
-					background-color: #fff;
-
-				}
-
-				#main form span.input input[type=text].right {
-
-					text-align: right;
-
-				}
-
-				#main form span.input input.size-2 {
-
-					width: 1.5em !important;
-
-				}
-
-				#main form span.input input.size-4 {
-
-					width: 2.5em !important;
-
-				}
-
-			#main form label {
-
-				display: block;
-				position: relative;
-				font-size: .75em;
-				color: #666;
-
-			}
-
-			#main form textarea {
-
-				display: block;
-				position: relative;
-				border: 1px solid #999;
-				font-size: .9em;
-				font-family: sans-serif;
-				padding: 3px;
-
-				border-radius: 5px;
-				-o-border-radius: 5px;
-				-moz-border-radius: 5px;
-				-webkit-border-radius: 5px;
-
-			}
-
-
-			#main form fieldset {
-
-				border: 1px solid #ccc;
-				padding: 5px;
-				margin: 5px 0px;
-
-			}
-
-				#main form fieldset legend {
-
-					font-size: .75em;
-					color: #666;
-
-				}
-				
-		#sidebar {}
-		
-			#sidebar h3 {
-				
-				display: block;
-				position: relative;
-				color: #fff;
-				font-size: 1.2em;
-				line-height: 1.4em;
-				margin: 5px 0px;
-				
-			}
-
-			#sidebar .item {
-				
-				display: block;
-				position: relative;
-				
-				padding: 0px 10px;
-				
-				border-radius: 10px;
-				-o-border-radius: 10px;
-				-moz-border-radius: 10px;
-				-webkit-border-radius: 10px;
-
-				-moz-box-shadow: 0px 0px 20px #eee;
-				-o-box-shadow: 0px 0px 20px #eee;
-				-webkit-box-shadow: 0px 0px 20px #eee;
-				box-shadow: 0px 0px 20px #eee;
-				
-				background-color: #333;
-				margin-bottom: 20px;
-				font-size: .8em;			
-				
-			}
-			
-				#sidebar .center {
-				
-					text-align: center;
-				
-				}
-			
-				#sidebar .counter-item .content {
-					
-					font-size: 3em;
-					color: #c03;
-					font-weight: bold;
-					line-height: 1em;
-					padding-bottom: 5px;
-					text-align: center;
-					
-				}
-				
-				#sidebar .submenu-item {
-					
-					background-color: #ddd;
-					
-				}
-
-					#sidebar .submenu-item ul {}
-
-						#sidebar .submenu-item ul li {}
-
-							#sidebar .submenu-item ul li+li {
-								
-								border-top: 1px dotted #999;
-								
-							}
-
-							#sidebar .submenu-item ul a {
-								
-								color: #c03;
-								text-decoration: none;
-								text-transform: uppercase;
-								
-							}
-							
-								#sidebar .submenu-item ul a:hover {
-									
-									color: #a01;
-									
-								}
-
-	#foot-container {
-		
-		display: block;
-		position: relative;
-		background: #222;
-		color: #ddd;
-		
-	}
-	
-		#foot {
-			
-			display: block;
-			position: relative;
-			width: 960px;
-			margin: 0px auto;
-			padding: 20px;
-			font-size: .8em;
-			
-		}
-		
-			#foot a {
-				
-				text-decoration: none;
-				color: #c03;
-				
-			}
-
--- a/doc/current.html	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,380 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-	
-
-	<head>
-		<title>Iro ·  API docs</title>
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-		<meta charset="utf-8">
-		<meta name="description" content="">
-		<meta name="keywords" content="">
-		<link rel="shortcut icon" type="image/x-icon" href="images/favicon.png">
-		<link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
-		<link rel="stylesheet" href="css/960.css" type="text/css" media="screen">
-		<link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
-		<!--[if IE]><link rel="stylesheet" href="css/style-ie.css" type="text/css"  media="screen" /><![endif] -->
-	</head>
-	<body>
-	<div id="head-container">
-		<div id="head">
-			<h1 id="logo"><a href="index.html" class="logo" title="Netzguerilla"><span>Netzguerilla</span></a></h1>
-			<ul id="menu">
-				<li><a href="index.html" class="menu">Iro</a></li><li><a href="current.html" class="menu active">API Documentation</a></li><li><a href="new.html" class="menu">geplante API Documentation</a></li><li><a href="impressum.html" class="menu">Impressum</a></li>
-			</ul>
-		</div>
-	</div>
-	<div id="content-container">
-		<div id="content" class="container_12">
-			<div id="main" class="grid_9">
-				<h2>API Dokumentation</h2>
-				<div class="item">
-			<p>
-			</p>
-			<ol>
-				<li value="1">1. <a href="#api-intro">Einführung</a></li>
-				<li value="2">2.
-					<a href="#api-interfaces">Interfaces</a>
-					<ol>
-						<li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
-					</ol>
-				</li>
-				<li value="3">3.
-					<a href="#api-methods">Methoden</a>
-					<ol>
-						<li value="3.1">3.1 <a href="#method-startSMS">StartSMS</a></li><li value="3.2">3.2 <a href="#method-startFAX">StartFAX</a></li><li value="3.3">3.3 <a href="#method-startMail">StartMail</a></li><li value="3.4">3.4 <a href="#method-status">Status</a></li><li value="3.5">3.5 <a href="#method-stop">Stop</a></li><li value="3.6">3.6 <a href="#method-getProvider">GetProvider</a></li><li value="3.7">3.7 <a href="#method-getDefaultProvider">GetDefaultProvider</a></li>
-					</ol>
-				</li>
-			</ol>
-		</div><div class="item" id="api-intro">
-			<h3>Einführung</h3>
-			<p>
-				Die Iro API enthält Funktion, die für den Massenversand nützlich sind.
-			</p>
-		</div><div class="item" id="api-interfaces">
-			<h3>Interfaces</h3>
-			<p>
-				Die Iro API stellt zur Zeit nur ein Interfaces bereit.
-			</p>
-			<div class="item" id="interface-xmlrpc">
-				<h4>XML-RPC</h4>
-				<p>
-				Interface-URI: <code>https://<em>&lt;benutzer&gt;</em>:<em>&lt;passwort&gt;</em>@localhost:8000</code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird dabei im <code>&lt;methodName /&gt;</code> übergeben.
-				</p>
-			</div>
-		</div><div class="item" id="api-methods">
-			<h3>Methoden</h3>
-			<div class="item" id="method-startSMS">
-				<h4>StartSMS</h4>
-				<p><code>startSMS(message, recipients, provider='default')</code></p>
-				<p>Versendet eine SMS.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>message</td>
-							<td>string</td>
-							<td> Nachricht</td>
-						</tr><tr>
-							<td>recipients</td>
-							<td>list</td>
-							<td> eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)</td>
-						</tr><tr>
-							<td>provider</td>
-							<td>string</td>
-							<td> Provider über den geschickt werden soll</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Die ID des Auftrages</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-startFAX">
-				<h4>StartFAX</h4>
-				<p><code>startFAX(subject, fax, recipients, provider='default')</code></p>
-				<p>Versendet ein FAX.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>subject</td>
-							<td>string</td>
-							<td> der Betreff</td>
-						</tr><tr>
-							<td>fax</td>
-							<td>string</td>
-							<td> das pdf base64 kodiert</td>
-						</tr><tr>
-							<td>recipients</td>
-							<td>list</td>
-							<td> eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)</td>
-						</tr><tr>
-							<td>provider</td>
-							<td>string</td>
-							<td> Provider über den geschickt werden soll</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Die ID des Auftrages</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-startMail">
-				<h4>StartMail</h4>
-				<p><code>startMail(subject, body, recipients, frm, provider='default')</code></p>
-				<p>Versendet eine Email.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>subject</td>
-							<td>string</td>
-							<td> der Betreff</td>
-						</tr><tr>
-							<td>body</td>
-							<td>string</td>
-							<td> der Email Body</td>
-						</tr><tr>
-							<td>recipients</td>
-							<td>list</td>
-							<td> eine Liste von Emailadressen</td>
-						</tr><tr>
-							<td>frm</td>
-							<td>string</td>
-							<td> Die Absender Emailadresse</td>
-						</tr><tr>
-							<td>provider</td>
-							<td>string</td>
-							<td> Provider über den geschickt werden soll</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Die ID des Auftrages</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-status">
-				<h4>Status</h4>
-				<p><code>status(id=None, detailed=False)</code></p>
-				<p>Gibt den aktuellen Status eines Auftrages zurück.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Eine Auftragsnummer</td>
-						</tr><tr>
-							<td>detailed</td>
-							<td>boolean</td>
-							<td> Details ausgeben</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>jobs</td>
-							<td>list</td>
-							<td> Eine Liste der Aufträge.</td>
-						</tr><tr>
-							<td>job.name</td>
-							<td>string</td>
-							<td> Angebener Name</td>
-						</tr><tr>
-							<td>job.status</td>
-							<td>string</td>
-							<td> Status des Auftrages</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-stop">
-				<h4>Stop</h4>
-				<p><code>stop(id)</code></p>
-				<p>Stoppt den angegeben Auftrag.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Eine Auftragsnummer</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-getProvider">
-				<h4>GetProvider</h4>
-				<p><code>getProvider(typ)</code></p>
-				<p>Gibt eine Liste aller verfügbaren Provider zurück.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>typ</td>
-							<td>string</td>
-							<td> Der Typ zu dem die Providerloste ausgeben werden soll
-Einer der Liste ["sms","fax","mail"]</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>providerlist</td>
-							<td>list</td>
-							<td> Eine Liste aller möglichen Provider</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-getDefaultProvider">
-				<h4>GetDefaultProvider</h4>
-				<p><code>getDefaultProvider(typ)</code></p>
-				<p>Gibt den Standardprovider zurück.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>typ</td>
-							<td>string</td>
-							<td> Der Typ zu dem die Providerloste ausgeben werden soll
-Einer der Liste ["sms","fax","mail"]</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>provider</td>
-							<td>string</td>
-							<td> Der Standardprovider für den angeben Typ</td>
-						</tr>
-					</tbody>
-				</table>
-			</div>
-		</div>
-			</div>
-			<div class="clear"></div>
-		</div>
-	</div>
-	<div id="foot-container">
-		<div id="foot">
-			<p>© 2010-2011 <a href="impressum.html">Netzguerilla.net</a>.</p>
-		</div>
-	</div>
-	</body>
-</html>
\ No newline at end of file
Binary file doc/images/favicon.png has changed
Binary file doc/images/netzguerilla-3.png has changed
--- a/doc/impressum.html	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-	
-
-	<head>
-		<title>Iro ·  Impressum</title>
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-		<meta charset="utf-8">
-		<meta name="description" content="">
-		<meta name="keywords" content="">
-		<link rel="shortcut icon" type="image/x-icon" href="images/favicon.png">
-		<link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
-		<link rel="stylesheet" href="css/960.css" type="text/css" media="screen">
-		<link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
-		<!--[if IE]><link rel="stylesheet" href="css/style-ie.css" type="text/css"  media="screen" /><![endif] -->
-	</head>
-	<body>
-	<div id="head-container">
-		<div id="head">
-			<h1 id="logo"><a href="index.html" class="logo" title="Netzguerilla"><span>Netzguerilla</span></a></h1>
-			<ul id="menu">
-				<li><a href="index.html" class="menu">Iro</a></li><li><a href="current.html" class="menu">API Documentation</a></li><li><a href="new.html" class="menu">geplante API Documentation</a></li><li><a href="impressum.html" class="menu active">Impressum</a></li>
-			</ul>
-		</div>
-	</div>
-	<div id="content-container">
-		<div id="content" class="container_12">
-			<div id="main" class="grid_9">
-				<h2>Impressum</h2>
-				<div class="item">
-			<h3>Kontakt</h3>
-			<p>
-				<strong>Netzguerilla.net</strong> Clerc Knauß Vollnhals GbR<br>
-				Manteuffelstraße 40<br>
-				10997 Berlin
-			</p>
-			<p>
-				WWW: <a href="https://netzguerilla.net/">netzguerilla.net</a><br>
-			</p>
-			<p>
-				E-Mail: <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a><br>
-			</p>
-			<p>
-				Telefon: <a href="tel:+493069201075">+49 30 69201075</a><br>
-			</p>
-			<h3>Inhaltlich verantwortlich</h3>
-			<p>
-				<small>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV bzw. §5 TMG sowie beauftragt mit dem Jugendschutz gemäß TMG, JMStV sowie GjSM:</small><br>
-				<strong>Sandro Knauß</strong>, Anschrift wie oben, <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a>
-			</p>
-		</div>
-			</div>
-			<div class="clear"></div>
-		</div>
-	</div>
-	<div id="foot-container">
-		<div id="foot">
-			<p>© 2010-2011 <a href="impressum.html">Netzguerilla.net</a>.</p>
-		</div>
-	</div>
-	</body>
-</html>
\ No newline at end of file
--- a/doc/index.html	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-	
-
-	<head>
-		<title>Iro ·  work in progress</title>
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-		<meta charset="utf-8">
-		<meta name="description" content="">
-		<meta name="keywords" content="">
-		<link rel="shortcut icon" type="image/x-icon" href="images/favicon.png">
-		<link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
-		<link rel="stylesheet" href="css/960.css" type="text/css" media="screen">
-		<link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
-		<!--[if IE]><link rel="stylesheet" href="css/style-ie.css" type="text/css"  media="screen" /><![endif] -->
-	</head>
-	<body>
-	<div id="head-container">
-		<div id="head">
-			<h1 id="logo"><a href="index.html" class="logo" title="Netzguerilla"><span>Netzguerilla</span></a></h1>
-			<ul id="menu">
-				<li><a href="index.html" class="menu active">Iro</a></li><li><a href="current.html" class="menu">API Documentation</a></li><li><a href="new.html" class="menu">geplante API Documentation</a></li><li><a href="impressum.html" class="menu">Impressum</a></li>
-			</ul>
-		</div>
-	</div>
-	<div id="content-container">
-		<div id="content" class="container_12">
-			<div id="main" class="grid_9">
-				<h2>Iro</h2>
-				<div class="item">
-			<p>
-				Iro ist toll, ist aber noch nicht fertig.
-			</p>
-		</div>
-			</div>
-			<div class="clear"></div>
-		</div>
-	</div>
-	<div id="foot-container">
-		<div id="foot">
-			<p>© 2010-2011 <a href="impressum.html">Netzguerilla.net</a>.</p>
-		</div>
-	</div>
-	</body>
-</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/index.rst	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,27 @@
+.. Iro documentation master file, created by
+   sphinx-quickstart on Sat Mar 24 12:06:24 2012.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to Iro's documentation!
+===============================
+
+Contents:
+
+.. toctree::
+   
+   install
+
+Extend Iro:
+
+.. toctree::
+
+   provider
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/install.rst	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,45 @@
+Installation of Iro
+===================
+
+Start with downloading the `source <../index.html#files>`_.
+Afterwards install the module via ``setup.py install`` und ran ``iro-install``. That will create a sample configuration file named ``iro.conf``.
+Update the configuration file and run ``iro-install install``  afterwards. That will create the Database and Offers for you.
+After that you have to :ref:`adding_user`. 
+Now you are ready to start Iro ``twisted iro``.
+
+
+.. _adding_user:
+
+Add User and Userrights
+-----------------------
+
+You'll need user and right to spefific Offers, that a user can send with a specific Offer. Till now, it is the only way to add/modify user/userrights. 
+
+ 
+You can add users and userright via SQL::
+
+  INSERT INTO user (name,apikey,ng_kunde) VALUES("test","a1b2c3");
+  INSERT INTO userrights (user,offer,default) VALUES ("test",OFFERNAME,NULL);
+
+or use python/ipython::
+
+  >>> import iro.model.schema
+  >>> from iro.model.schema import *
+  >>> from sqlalchemy import create_engine
+  >>> from iro.model.utils import WithSession
+  >>> engine = create_engine(DBURL)
+  >>> with WithSession(engine) as session:
+  ...   u = User(name="test",apikey="a1a2c3")      
+  ...   session.add(u)
+  ...   o = session.query(Offer).filter_by(name=OFFERNAME).first()
+  ...   u.rights.append(Userright(o, default=None))
+  ...   session.commit()
+  >>>
+
+.. note::
+  Please make sure that, the apikey only using *hex digest [0-9a-f]*
+
+Sample Configuration
+====================
+
+.. literalinclude:: ../iro.conf.inst
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/iro.controller.rst	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,27 @@
+controller Package
+==================
+
+:mod:`pool` Module
+------------------
+
+.. automodule:: iro.controller.pool
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`task` Module
+------------------
+
+.. automodule:: iro.controller.task
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`viewinterface` Module
+---------------------------
+
+.. automodule:: iro.controller.viewinterface
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/iro.model.rst	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,91 @@
+model Package
+=============
+
+:mod:`model` Package
+--------------------
+
+.. automodule:: iro.model
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`dbdefer` Module
+---------------------
+
+.. automodule:: iro.model.dbdefer
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`decorators` Module
+------------------------
+
+.. automodule:: iro.model.decorators
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`job` Module
+-----------------
+
+.. automodule:: iro.model.job
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`message` Module
+---------------------
+
+.. automodule:: iro.model.message
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`offer` Module
+-------------------
+
+.. automodule:: iro.model.offer
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`pool` Module
+------------------
+
+.. automodule:: iro.model.pool
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`schema` Module
+--------------------
+
+.. automodule:: iro.model.schema
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`status` Module
+--------------------
+
+.. automodule:: iro.model.status
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`user` Module
+------------------
+
+.. automodule:: iro.model.user
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`utils` Module
+-------------------
+
+.. automodule:: iro.model.utils
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/iro.offer.rst	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,51 @@
+offer Package
+=============
+
+:mod:`offer` Package
+--------------------
+
+.. automodule:: iro.offer
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`offer` Module
+-------------------
+
+.. automodule:: iro.offer.offer
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`provider` Module
+----------------------
+
+.. automodule:: iro.offer.provider
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`sipgate` Module
+----------------------
+
+.. automodule:: iro.offer.sipgate
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`smstrade` Module
+----------------------
+
+.. automodule:: iro.offer.smstrade
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`smtp` Module
+------------------
+
+.. automodule:: iro.offer.smtp
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/iro.rst	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,69 @@
+iro Package
+===========
+
+:mod:`iro` Package
+------------------
+
+.. automodule:: iro.__init__
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`config` Module
+--------------------
+
+.. automodule:: iro.config
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`error` Module
+-------------------
+
+.. automodule:: iro.error
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`install` Module
+---------------------
+
+.. automodule:: iro.install
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`main` Module
+------------------
+
+.. automodule:: iro.main
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`telnumber` Module
+-----------------------
+
+.. automodule:: iro.telnumber
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`validate` Module
+----------------------
+
+.. automodule:: iro.validate
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+
+    iro.controller
+    iro.model
+    iro.offer
+    iro.view
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/iro.view.rst	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,19 @@
+view Package
+============
+
+:mod:`xmlrpc` Module
+--------------------
+
+.. automodule:: iro.view.xmlrpc
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`xmlrpc_old` Module
+------------------------
+
+.. automodule:: iro.view.xmlrpc_old
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
--- a/doc/new.html	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,473 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-	
-
-	<head>
-		<title>Iro ·  api docs new</title>
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-		<meta charset="utf-8">
-		<meta name="description" content="">
-		<meta name="keywords" content="">
-		<link rel="shortcut icon" type="image/x-icon" href="images/favicon.png">
-		<link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
-		<link rel="stylesheet" href="css/960.css" type="text/css" media="screen">
-		<link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
-		<!--[if IE]><link rel="stylesheet" href="css/style-ie.css" type="text/css"  media="screen" /><![endif] -->
-	</head>
-	<body>
-	<div id="head-container">
-		<div id="head">
-			<h1 id="logo"><a href="index.html" class="logo" title="Netzguerilla"><span>Netzguerilla</span></a></h1>
-			<ul id="menu">
-				<li><a href="index.html" class="menu">Iro</a></li><li><a href="current.html" class="menu">API Documentation</a></li><li><a href="new.html" class="menu active">geplante API Documentation</a></li><li><a href="impressum.html" class="menu">Impressum</a></li>
-			</ul>
-		</div>
-	</div>
-	<div id="content-container">
-		<div id="content" class="container_12">
-			<div id="main" class="grid_9">
-				<h2>geplante API Dokumentation</h2>
-				<div class="item">
-			<p>
-			</p>
-			<ol>
-				<li value="1">1. <a href="#api-intro">Einführung</a></li>
-				<li value="2">2.
-				<a href="#api-interfaces">Interfaces</a>
-				<ol>
-					<li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
-					<li value="2.1">2.1 <a href="#interface-soap">SOAP</a></li>
-					<li value="2.2">2.2 <a href="#interface-xml">XML</a></li>
-					<li value="2.3">2.3 <a href="#interface-json">JSON</a></li>
-					<li value="2.4">2.4 <a href="#interface-jsonp">JSONP</a></li>
-					<li value="2.5">2.5 <a href="#interface-php">PHP</a></li>
-				</ol>
-				</li>
-				<li value="3">3.
-					<a href="#api-methods">Methoden</a>
-					<ol>
-						<li value="3.1">3.1 <a href="#method-sms">Sms</a></li><li value="3.2">3.2 <a href="#method-fax">Fax</a></li><li value="3.3">3.3 <a href="#method-mail">Mail</a></li><li value="3.4">3.4 <a href="#method-status">Status</a></li><li value="3.5">3.5 <a href="#method-stop">Stop</a></li><li value="3.6">3.6 <a href="#method-routes">Routes</a></li><li value="3.7">3.7 <a href="#method-defaultRoute">DefaultRoute</a></li>
-					</ol>
-				</li>
-			</ol>
-		</div><div class="item" id="api-intro">
-			<h3>Einführung</h3>
-			<p>
-				Die Iro API enthält Funktion, die für den Massenversand nützlich sind.
-			</p>
-		</div><div class="item" id="api-interfaces">
-			<h3>Interfaces</h3>
-			<p>
-				Die Iro API wird über verschiedene Interfaces bereit gestellt, die unterschiedlich angesprochen werden, aber das selbe tun.
-			</p>
-			<div class="item" id="interface-xmlrpc">
-				<h4>XML-RPC</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/xmlrpc</code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird dabei im <code>&lt;methodName /&gt;</code> übergeben.
-				</p>
-			</div>
-			<div class="item" id="interface-soap">
-				<h4>SOAP</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/soap</code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird dabei im <code>&lt;methodName /&gt;</code> übergeben.
-				</p>
-			</div>
-			<div class="item" id="interface-xml">
-				<h4>XML</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/xml/<em>&lt;methode&gt;</em></code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
-				</p>
-				<p>
-					Parameter können via HTTP per GET oder POST im Format <em>application/x-www-form-urlencoded</em> übergeben werden.
-				</p>
-				<p>
-					Die Ausgabe erfolgt als XML Markup.
-				</p>
-			</div>
-			<div class="item" id="interface-json">
-				<h4>JSON</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/json/<em>&lt;methode&gt;</em></code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
-				</p>
-				<p>
-					Parameter können via HTTP per GET oder POST im Format <em>application/x-www-form-urlencoded</em> oder JSON-Objekt übergeben werden.
-				</p>
-				<p>
-					Die Ausgabe erfolgt als JSON-Objekt.
-				</p>
-			</div>
-			<div class="item" id="interface-jsonp">
-				<h4>JSONP</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/jsonp/<em>&lt;methode&gt;</em>?callback=&lt;callback&gt;</code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
-				</p>
-				<p>
-					Der Name für die Callback-Methode wird als Parameter Callback übergeben.
-				</p>
-				<p>
-					Parameter können via HTTP per GET im Format <em>application/x-www-form-urlencoded</em> übergeben werden.
-				</p>
-				<p>
-					Die Ausgabe erfolgt als Javascript-Funktionsaufruf mit einem JSON-Objekt als Parameter.
-				</p>
-			</div>
-		</div><div class="item" id="api-methods">
-			<h3>Methoden</h3>
-			<div class="item" id="method-sms">
-				<h4>Sms</h4>
-				<p><code>sms(apikey, message, recipients, route='default')</code></p>
-				<p>Versendet eine SMS.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>apikey</td>
-							<td>string</td>
-							<td> Der API Key</td>
-						</tr><tr>
-							<td>message</td>
-							<td>string</td>
-							<td> Nachricht</td>
-						</tr><tr>
-							<td>recipients</td>
-							<td>list</td>
-							<td> eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)</td>
-						</tr><tr>
-							<td>route</td>
-							<td>string|list</td>
-							<td> Route über den geschickt werden soll,
-oder eine Liste von Routen, um Fallbacks anzugeben</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Die ID des Auftrages</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-fax">
-				<h4>Fax</h4>
-				<p><code>fax(apikey, subject, fax, recipients, route='default')</code></p>
-				<p>Versendet ein FAX.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>apikey</td>
-							<td>string</td>
-							<td> Der API Key</td>
-						</tr><tr>
-							<td>subject</td>
-							<td>string</td>
-							<td> Der Betreff</td>
-						</tr><tr>
-							<td>fax</td>
-							<td>string</td>
-							<td> Das PDF base64 kodiert</td>
-						</tr><tr>
-							<td>recipients</td>
-							<td>list</td>
-							<td> Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)</td>
-						</tr><tr>
-							<td>route</td>
-							<td>string|list</td>
-							<td> Route über den geschickt werden soll,
-oder eine Liste von Routen, um Fallbacks anzugeben</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Die ID des Auftrages</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-mail">
-				<h4>Mail</h4>
-				<p><code>mail(apikey, subject, body, recipients, frm, route='default')</code></p>
-				<p>Versendet eine Email.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>apikey</td>
-							<td>string</td>
-							<td> Der API Key</td>
-						</tr><tr>
-							<td>subject</td>
-							<td>string</td>
-							<td> Der Betreff</td>
-						</tr><tr>
-							<td>body</td>
-							<td>string</td>
-							<td> Der Email Body</td>
-						</tr><tr>
-							<td>recipients</td>
-							<td>list</td>
-							<td> Eine Liste von Emailadressen</td>
-						</tr><tr>
-							<td>frm</td>
-							<td>string</td>
-							<td> Die Absender Emailadresse</td>
-						</tr><tr>
-							<td>route</td>
-							<td>string|list</td>
-							<td> Route über den geschickt werden soll,
-oder eine Liste von Routen, um Fallbacks anzugeben</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Die ID des Auftrages</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-status">
-				<h4>Status</h4>
-				<p><code>status(apikey, id=None, detailed=False)</code></p>
-				<p>Gibt den aktuellen Status eines Auftrages zurück.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>apikey</td>
-							<td>string</td>
-							<td> Der API Key</td>
-						</tr><tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Eine Auftragsnummer</td>
-						</tr><tr>
-							<td>detailed</td>
-							<td>boolean</td>
-							<td> Details ausgeben</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>jobs</td>
-							<td>list</td>
-							<td> Eine Liste der Aufträge.</td>
-						</tr><tr>
-							<td>job.name</td>
-							<td>string</td>
-							<td> Angebener Name</td>
-						</tr><tr>
-							<td>job.status</td>
-							<td>string</td>
-							<td> Status des Auftrages</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-stop">
-				<h4>Stop</h4>
-				<p><code>stop(apikey, id)</code></p>
-				<p>Stoppt den angegeben Auftrag.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>apikey</td>
-							<td>string</td>
-							<td> Der API Key</td>
-						</tr><tr>
-							<td>id</td>
-							<td>hash</td>
-							<td> Eine Auftragsnummer</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-routes">
-				<h4>Routes</h4>
-				<p><code>routes(apikey, typ)</code></p>
-				<p>Gibt eine Liste aller verfügbaren Provider zurück.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>apikey</td>
-							<td>string</td>
-							<td> Der API Key</td>
-						</tr><tr>
-							<td>typ</td>
-							<td>string</td>
-							<td> Der Typ zu dem die Providerloste ausgeben werden soll
-Einer der Liste ["sms","fax","mail"]</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>providerlist</td>
-							<td>list</td>
-							<td> Eine Liste aller möglichen Provider</td>
-						</tr>
-					</tbody>
-				</table>
-			</div><div class="item" id="method-defaultRoute">
-				<h4>DefaultRoute</h4>
-				<p><code>defaultRoute(apikey, typ)</code></p>
-				<p>Gibt den Standardprovider zurück.</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>apikey</td>
-							<td>string</td>
-							<td> Der API Key</td>
-						</tr><tr>
-							<td>typ</td>
-							<td>string</td>
-							<td> Der Typ zu dem die Providerloste ausgeben werden soll
-Einer der Liste ["sms","fax","mail"]</td>
-						</tr>
-					</tbody>
-				</table>
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>
-					</thead>
-					<tbody>
-						<tr>
-							<td>provider</td>
-							<td>string</td>
-							<td> Der Standardprovider für den angeben Typ</td>
-						</tr>
-					</tbody>
-				</table>
-			</div>
-		</div>
-			</div>
-			<div class="clear"></div>
-		</div>
-	</div>
-	<div id="foot-container">
-		<div id="foot">
-			<p>© 2010-2011 <a href="impressum.html">Netzguerilla.net</a>.</p>
-		</div>
-	</div>
-	</body>
-</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/provider.rst	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,143 @@
+Creating a new Providerbackend for Iro
+======================================
+
+See also class documentation :class:`iro.offer.provider.Provider`.
+
+A very simple provider 
+----------------------
+
+For testing purpose it is nice to create a small provider.
+
+.. code-block:: python
+    :linenos:
+    
+    from iro.offer import providers, Provider
+
+    class TestProvider(Provider):
+        def __init__(self,name):
+            Provider.__init__(self, name, {"sms" : ["a",]})
+    
+    providers["myveryspecialProvider"] = TestProvider
+    
+- *line 3* --  a Provider that supports message type **sms**, and has one route named **a**. 
+- *line 5* -- register the provider type **TestProvider** in the global **providers** dict. Following section in configuraton file will create a new TestProvider object, with ``name="blablub"``::
+
+   [blablub]
+   #see line 5
+   typ = myveryspecialProvider 
+
+
+Normally a new Provider wants to have extra options for configuration file:
+
+.. code-block:: python
+    :linenos:
+    
+    from iro.offer import providers, Provider
+    from iro.config import Option
+
+    def validater(value, field):
+        return value
+
+    class TestProvider(Provider):
+        def __init__(self,name):
+            options =[("key", Option(validater,long="My Option explanation", must=True)),]
+            Provider.__init__(self, name, {"sms" : ["a",]}, options)
+    
+    providers["myveryspecialProvider"] = TestProvider
+ 
+
+in *line 9* we create a item list ( ``[(name,Option),...]`` -- more information about :class:`iro.config.Option`). **validater** have to be a function that returns value, if the value is valid. With this following section in configuration file is possible::
+
+   [balblub]
+   typ = myveryspecialProvider
+   #My Option explanation
+   key = mykey
+
+Ok, now we know to get settings into the provider. But we have to do anything, when user want to send anything. So we have to create a send function.
+
+Creating sipgate provider
+-------------------------
+
+Sipgate supports sending sms and faxes via XML-RPC. so it is easy to create a new providerbackend for iro via sipgate. First we get the XML-RPC Api documention for sipgate (http://www.sipgate.de/beta/public/static/downloads/basic/api/sipgate_api_documentation.pdf). Sipgate uses HTTP Basic Authentification, that's we he create to options for our sipgate provider:
+
+.. code-block:: python
+    :linenos:
+
+    from iro.offer import providers, Provider
+    from iro.config import Option
+
+    class Sipgate(Provider):
+        def __init__(self,name):
+            options =[("username", Option(lambda x,y: x,long="Loginname for sipgate", must=True)),
+                      ("password", Option(lambda x,y: x,long="Password for sipgate", must=True)),]
+            Provider.__init__(self, name, {"sms" : [None], "fax":[None]}, options)
+    
+    providers["sipgate"] = Sipgate
+ 
+- *line 6/7* -- we don't have any ideas what is allowed as username/password, so we create a validator that accepts everything.
+- *line 8* -- sipgate supports fax and sms, but now diffrent routes, that's we use ``None``.
+
+Now we have to possible options to implement the send function. either we implement a  blocking interface or use the recommended solution: twisted non blocking solution. We show here the recommended version.
+
+The Twisted Way (recommended solution)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+First we start to implement the ``fax`` and ``sms`` methods:
+
+.. code-block:: python
+    :linenos:
+    
+    def proxy(self):
+        return Proxy("https://%s:%s@samurai.sipgate.net/RPC2"%(self.username, self.password))
+
+    def sms(self, recipient, sms):
+        args={
+                "TOS" : "text",
+                "Content" : sms.getContent(),
+                "RemoteUri" : "sip:%s%s@sipgate.net"%(recipient.land, recipient.number),
+                }
+        return self.proxy().callRemote("samurai.SessionInitiate",args)
+
+    def fax(self, recipient, fax):
+        args={
+            "TOS" : "fax",
+            "Content" : xmlrpclib.Binary(fax.getAttachment(0)),
+            "RemoteUri" : "sip:%s%s@sipgate.net"%(recipient.land, recipient.number),
+            }
+        return self.proxy().callRemote("samurai.SessionInitiate",args)
+
+The code is straight forward with the API documentation from sipgate. Now we have to implement the heat of the provider the ``send`` method:
+
+.. code-block:: python
+    :linenos:
+
+    def _status(self,value,typ):
+        if typ not  in self.typs.keys():
+            raise NoTyp(typ)
+        return Status(self, None, Decimal("1.00"), 1, value["SessionID"])
+
+    def send(self, typ, recipient, msg):
+        if typ not  in self.typs.keys():
+            raise NoTyp(typ)
+        d = getattr(self,typ)(recipient, msg)
+        d.addCallback(self._status, typ)
+        return d
+
+    def getSendFunc(self, typ, route):
+        """returns :meth:`send` method, if typ and route is valid."""
+
+        Provider.getSendFunc(self, typ, route)
+        return partial(self.send, typ)
+
+Because sipgate doesn't support different routes, we implement a send function without route argument.
+That's why we have to rewrite the ``getSendFunc`` method. It now returns a partial function with only a binded ``typ``.
+
+The ``send`` method first test the for a valid typ (*line 7/8*), than it execute the ``sms`` or ``fax`` method.
+For a valid provider we have to return a :class:`~iro.model.status.Status` object.
+There for we add a callback that returns a :class:`~iro.model.status.Status` object (see ``_status`` method).
+
+Unfortunatelly sipgate doesn't support methods to get the price for one action.
+So we have to set set a fixed price here ``Decimal('1.00')``.
+In the wild we implement new configuration parameters for priceing.
+
+Now the provider is ready to use. For complete source of this tutorial see :class:`iro.offer.sipgate`.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/tmpl/about.html	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+	xmlns:xi="http://www.w3.org/2001/XInclude"
+	xmlns:py="http://genshi.edgewall.org/">
+	<xi:include href="layout.tmpl" />
+	<head>
+		<title>About us</title>
+	</head>
+	<body>
+		<title>About us</title>
+
+		<div class="item">
+			<h3>Contact</h3>
+			<p>
+				<strong>Netzguerilla.net</strong> Clerc Knauß Vollnhals GbR<br />
+				Manteuffelstraße 40<br />
+				10997 Berlin
+			</p>
+			<p>
+				WWW: <a href="https://netzguerilla.net/">netzguerilla.net</a><br />			
+			</p>
+			<p>
+				E-Mail: <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a><br />			
+			</p>
+			<p>
+				Telefon: <a href="tel:+493069201075">+49 30 69201075</a><br />			
+			</p>
+			<h3>German legal notice - Inhaltlich verantwortlich</h3>
+			<p>
+				<small>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV bzw. §5 TMG sowie beauftragt mit dem Jugendschutz gemäß TMG, JMStV sowie GjSM:</small><br />
+				<strong>Sandro Knauß</strong>, Anschrift wie oben, <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a>
+			</p>
+		</div>
+	</body>
+</html>
--- a/doc/tmpl/current.html	Wed Dec 21 22:07:48 2011 +0100
+++ b/doc/tmpl/current.html	Thu Sep 27 17:15:46 2012 +0200
@@ -5,66 +5,91 @@
 	xmlns:py="http://genshi.edgewall.org/">
 	<xi:include href="layout.tmpl" />
 	<head>
-		<title>API docs</title>
+		<title>api docs current</title>
 	</head>
 	<body>
-		<title>API Dokumentation</title>
+		<title>API Documentation</title>
 		<div class="item">
 			<p>
-				
+
 			</p>
 			<ol>
-				<li value="1">1. <a href="#api-intro">Einführung</a></li>
+				<li value="1">1. <a href="#api-intro">Intro</a></li>
 				<li value="2">2. 
-					<a href="#api-interfaces">Interfaces</a>
-					<ol>
-						<li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
-					</ol>
+				<a href="#api-interfaces">Interfaces</a>
+				<ol>
+					<li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
+					<li value="2.1">2.2 <a href="#interface-soap">SOAP</a></li>
+					<li value="2.3">2.3 <a href="#interface-json">JSON</a></li>
+					<li value="2.4">2.4 <a href="#interface-jsonp">JSONP</a></li>
+				</ol>
 				</li>
 				<li value="3">3. 
-					<a href="#api-methods">Methoden</a>
+					<a href="#api-methods">Methods</a>
 					<ol>
 						<li py:for="(key,method) in enumerate(current)" value="3.${key+1}">3.${key+1} <a href="#method-${method.name}">${method.title}</a></li>
 					</ol>				
 				</li>
+
 			</ol>
 		</div>
 		<div class="item" id="api-intro">
-			<h3>Einführung</h3>
+			<h3>Intro</h3>
 			<p>
-				Die Iro API enthält Funktion, die für den Massenversand nützlich sind.
+				Iro API has many methods, that are usefull if you want to send a bunch of messages. This Site describes the API for Iro 1.0.
 			</p>
 		</div>
 		<div class="item" id="api-interfaces">
 			<h3>Interfaces</h3>
 			<p>
-				Die Iro API stellt zur Zeit nur ein Interfaces bereit.
+				You can use diffrent interfaces to get to same result.
 			</p>
 			<div class="item" id="interface-xmlrpc">
 				<h4>XML-RPC</h4>
 				<p>
-				Interface-URI: <code>https://<em>&lt;benutzer&gt;</em>:<em>&lt;passwort&gt;</em>@localhost:8000</code>
+					Interface-URI: <code>http://localhost:8000/xmlrpc</code>
+				</p>
+			</div>
+			<div class="item" id="interface-soap">
+				<h4>SOAP</h4>
+				<p>
+					Interface-URI: <code>http://localhost:8000/soap</code>
+				</p>
+			</div>
+			<div class="item" id="interface-json">
+				<h4>JSON</h4>
+				<p>
+					Interface-URI: <code>http://localhost:8000/json/<em>&lt;methode&gt;</em></code>
 				</p>
 				<p>
-					Die aufgerufene Methode wird dabei im <code>&lt;methodName /&gt;</code> übergeben.
+					Not yet implementet
+				</p>
+			</div>
+			<div class="item" id="interface-jsonp">
+				<h4>JSONP</h4>
+				<p>
+					Interface-URI: <code>http://localhost:8000/jsonp/<em>&lt;methode&gt;</em>?callback=&lt;callback&gt;</code>
+				</p>
+				<p>
+					Not yet implementet
 				</p>
 			</div>
 		</div>
 		<div class="item" id="api-methods">
-			<h3>Methoden</h3>
+			<h3>Methods</h3>
 			<div py:for="method in current" class="item" id="method-${method.name}">
 				<h4>${method.title}</h4>
 				<p><code>${method.name}${method.func_line}</code></p>
 				<p py:content="method.description">
-					Diese Methode at bis jetzt noch keine Beschreibung.
+					No description
 				</p>
 				<h5>Parameter</h5>
 				<table class="docs">
 					<thead>
 						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
+							<td>parameter</td>
+							<td>type</td>
+							<td>description</td>
 						</tr>					
 					</thead>
 					<tbody>
@@ -76,13 +101,13 @@
 					</tbody>
 				</table>
 				<py:if test="method.rets">
-				<h5>Ausgabe</h5>
+				<h5>Return</h5>
 				<table class="docs">
 					<thead>
 						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
+							<td>parameter</td>
+							<td>type</td>
+							<td>description</td>
 						</tr>					
 					</thead>
 					<tbody>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/tmpl/database.html	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+	xmlns:xi="http://www.w3.org/2001/XInclude"
+	xmlns:py="http://genshi.edgewall.org/">
+	<xi:include href="layout.tmpl" />
+	<head>
+		<title>Datenbase</title>
+	</head>
+	<body>
+		<title>Datenbase Schema</title>
+		<div class="item">
+			<p>
+
+			</p>
+			<ol>
+				<li value="1">1. <a href="#schema">Datenbase schema</a></li>
+				<li value="3">3. 
+					<a href="#tables">Tables</a>
+					<ol>
+						<li py:for="(key,table) in enumerate(tables)" value="2.${key+1}">2.${key+1} <a href="#table-${table.tablename}">${table.title}</a></li>
+					</ol>				
+				</li>
+			</ol>
+		</div>
+
+
+		<div class="item" id="schema">
+			<h3>Schema</h3>
+			<img src="images/db-schema.svg" />
+			<p>Overview of used tables.</p>
+		</div>
+		<div class="item" id="tables">
+			<h3>Tables</h3>
+			<div py:for="table in tables" class="item" id="table-${table.tablename}">
+				<h4>${table.title}</h4>
+				<p py:content="table.description">
+					No description available.
+				</p>
+			</div>
+		</div>
+	</body>
+</html>
--- a/doc/tmpl/impressum.html	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
-"http://www.w3.org/TR/html4/strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
-	xmlns:xi="http://www.w3.org/2001/XInclude"
-	xmlns:py="http://genshi.edgewall.org/">
-	<xi:include href="layout.tmpl" />
-	<head>
-		<title>Impressum</title>
-	</head>
-	<body>
-		<title>Impressum</title>
-
-		<div class="item">
-			<h3>Kontakt</h3>
-			<p>
-				<strong>Netzguerilla.net</strong> Clerc Knauß Vollnhals GbR<br />
-				Manteuffelstraße 40<br />
-				10997 Berlin
-			</p>
-			<p>
-				WWW: <a href="https://netzguerilla.net/">netzguerilla.net</a><br />			
-			</p>
-			<p>
-				E-Mail: <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a><br />			
-			</p>
-			<p>
-				Telefon: <a href="tel:+493069201075">+49 30 69201075</a><br />			
-			</p>
-			<h3>Inhaltlich verantwortlich</h3>
-			<p>
-				<small>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV bzw. §5 TMG sowie beauftragt mit dem Jugendschutz gemäß TMG, JMStV sowie GjSM:</small><br />
-				<strong>Sandro Knauß</strong>, Anschrift wie oben, <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a>
-			</p>
-		</div>
-	</body>
-</html>
--- a/doc/tmpl/index.html	Wed Dec 21 22:07:48 2011 +0100
+++ b/doc/tmpl/index.html	Thu Sep 27 17:15:46 2012 +0200
@@ -10,11 +10,59 @@
 	<body>
 		<title>Iro</title>
 		<div class="item">
+			<h3>What it is all about?</h3>
 			<p>
-				Iro ist toll, ist aber noch nicht fertig.
+			Iro is a non blocking server for sending a message to a bunsh of recipients. It can handle different types of message and can be extended easially.
+			It was initially written for sms backend of <a href="http://castorticker.de">castorticker.de</a>. There are diffrent backends are supported by Iro. 
+			Iro helps you for these kinds of tasks: You want to send a message to some recipient over a different backend, than for other recipients.
+			Or you want to define fallback strategies: If one messages can't be send over this backend use another.
+			Because Iro also knows users, you can create bills for different clients.
 			</p>
 		</div>
-	</body>
+		<div class="item" id="backends">
+			<h3>Supported Backends</h3>
+			<p>
+			A backend is a provider, that actually sends the message. 
+			<ul>
+				<li><b>smtp</b> with TLS and SSL</li>
+				<li><b><a href="http://smstrade.de">smstrade</a></b> all diffrent routes are selectable</li>
+				<li><b><a href="http://sipgate.de">sipgate</a></b> fax and sms</li>
+			</ul>
+			</p>
+
+		</div>
+		<div class="item" id="installation">
+			<h3>Installation</h3>
+			<p>
+			Just download <a href="files/${code}">${code}</a> see also installtion instruction under <a href="dev/install.html">Installing Iro</a>.
+			</p>
+
+			<h3>Documentation</h3>
+			<p>
+			If you want just use iro see <a href="current.html">API Documentation</a>.
+			For extending iro see the <a href="dev/index.html">developer documenation</a>.
+			</p>
+		</div>
+		<div class="item" id="files">
+			<h3>Files</h3>
+			<p>
+			<ul> 
+				<li py:for="f in files" value="${f.version}">${f.version} <a href="files/${f.name}">${f.name}</a></li>
+			</ul>
+                        You can also get the code from our <a href="https://netzguerilla.net/iro/hg" >repository</a>, <a href="http://bitbucket.com/hefee/iro" >bitbucket</a>, or <a href="http://github.com/netzguerilla/iro" >github</a>.
+			</p>
+		</div>
+		<div class="item" id="features">
+			<h3>Planned features for 1.0</h3>
+			<p>
+			<ul>   
+				<li>admin interface for creating and modifying users</li>
+				<li>heartbeat</li>
+			</ul>
+			For any further ideas, bugs or anything - send at <a href="mailto:iro@netzgerilla.net">iro@netzgerilla.net</a>.
+			</p>
+		</div>
+</body>
 </html>
 
 	
--- a/doc/tmpl/new.html	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
-"http://www.w3.org/TR/html4/strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
-	xmlns:xi="http://www.w3.org/2001/XInclude"
-	xmlns:py="http://genshi.edgewall.org/">
-	<xi:include href="layout.tmpl" />
-	<head>
-		<title>api docs new</title>
-	</head>
-	<body>
-		<title>geplante API Dokumentation</title>
-		<div class="item">
-			<p>
-
-			</p>
-			<ol>
-				<li value="1">1. <a href="#api-intro">Einführung</a></li>
-				<li value="2">2. 
-				<a href="#api-interfaces">Interfaces</a>
-				<ol>
-					<li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
-					<li value="2.1">2.1 <a href="#interface-soap">SOAP</a></li>
-					<li value="2.2">2.2 <a href="#interface-xml">XML</a></li>
-					<li value="2.3">2.3 <a href="#interface-json">JSON</a></li>
-					<li value="2.4">2.4 <a href="#interface-jsonp">JSONP</a></li>
-					<li value="2.5">2.5 <a href="#interface-php">PHP</a></li>
-				</ol>
-				</li>
-				<li value="3">3. 
-					<a href="#api-methods">Methoden</a>
-					<ol>
-						<li py:for="(key,method) in enumerate(new)" value="3.${key+1}">3.${key+1} <a href="#method-${method.name}">${method.title}</a></li>
-					</ol>				
-				</li>
-
-			</ol>
-		</div>
-		<div class="item" id="api-intro">
-			<h3>Einführung</h3>
-			<p>
-				Die Iro API enthält Funktion, die für den Massenversand nützlich sind.
-			</p>
-		</div>
-		<div class="item" id="api-interfaces">
-			<h3>Interfaces</h3>
-			<p>
-
-				Die Iro API wird über verschiedene Interfaces bereit gestellt, die unterschiedlich angesprochen werden, aber das selbe tun.
-			</p>
-			<div class="item" id="interface-xmlrpc">
-				<h4>XML-RPC</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/xmlrpc</code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird dabei im <code>&lt;methodName /&gt;</code> übergeben.
-				</p>
-			</div>
-			<div class="item" id="interface-soap">
-				<h4>SOAP</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/soap</code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird dabei im <code>&lt;methodName /&gt;</code> übergeben.
-				</p>
-			</div>
-			<div class="item" id="interface-xml">
-				<h4>XML</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/xml/<em>&lt;methode&gt;</em></code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
-				</p>
-				<p>
-					Parameter können via HTTP per GET oder POST im Format <em>application/x-www-form-urlencoded</em> übergeben werden.
-				</p>
-				<p>
-					Die Ausgabe erfolgt als XML Markup.
-				</p>
-			</div>
-			<div class="item" id="interface-json">
-				<h4>JSON</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/json/<em>&lt;methode&gt;</em></code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
-				</p>
-				<p>
-					Parameter können via HTTP per GET oder POST im Format <em>application/x-www-form-urlencoded</em> oder JSON-Objekt übergeben werden.
-				</p>
-				<p>
-					Die Ausgabe erfolgt als JSON-Objekt.
-				</p>
-			</div>
-			<div class="item" id="interface-jsonp">
-				<h4>JSONP</h4>
-				<p>
-					Interface-URI: <code>http://localhost:8000/jsonp/<em>&lt;methode&gt;</em>?callback=&lt;callback&gt;</code>
-				</p>
-				<p>
-					Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
-				</p>
-				<p>
-					Der Name für die Callback-Methode wird als Parameter Callback übergeben.
-				</p>
-				<p>
-					Parameter können via HTTP per GET im Format <em>application/x-www-form-urlencoded</em> übergeben werden.
-				</p>
-				<p>
-					Die Ausgabe erfolgt als Javascript-Funktionsaufruf mit einem JSON-Objekt als Parameter.
-				</p>
-			</div>
-		</div>
-		<div class="item" id="api-methods">
-			<h3>Methoden</h3>
-			<div py:for="method in new" class="item" id="method-${method.name}">
-				<h4>${method.title}</h4>
-				<p><code>${method.name}${method.func_line}</code></p>
-				<p py:content="method.description">
-					Diese Methode at bis jetzt noch keine Beschreibung.
-				</p>
-				<h5>Parameter</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>					
-					</thead>
-					<tbody>
-						<tr py:for="arg in method.args">
-							<td>${arg.name}</td>
-							<td>${arg.typ}</td>
-							<td>${arg.description}</td>
-						</tr>
-					</tbody>
-				</table>
-				<py:if test="method.rets">
-				<h5>Ausgabe</h5>
-				<table class="docs">
-					<thead>
-						<tr>
-							<td>Parameter</td>
-							<td>Typ</td>
-							<td>Beschreibung</td>
-						</tr>					
-					</thead>
-					<tbody>
-						<tr py:for="arg in method.rets">
-							<td>${arg.name}</td>
-							<td>${arg.typ}</td>
-							<td>${arg.description}</td>
-						</tr>
-					</tbody>
-				</table>	
-				</py:if>
-			</div>
-		</div>
-	</body>
-</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/tmpl/old.html	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+	xmlns:xi="http://www.w3.org/2001/XInclude"
+	xmlns:py="http://genshi.edgewall.org/">
+	<xi:include href="layout.tmpl" />
+	<head>
+		<title>API docs old</title>
+	</head>
+	<body>
+		<title>Alte API Dokumentation</title>
+		<div class="item">
+			<p>
+				
+			</p>
+			<ol>
+				<li value="1">1. <a href="#api-intro">Einführung</a></li>
+				<li value="2">2. 
+					<a href="#api-interfaces">Interfaces</a>
+					<ol>
+						<li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
+					</ol>
+				</li>
+				<li value="3">3. 
+					<a href="#api-methods">Methoden</a>
+					<ol>
+						<li py:for="(key,method) in enumerate(old)" value="3.${key+1}">3.${key+1} <a href="#method-${method.name}">${method.title}</a></li>
+					</ol>				
+				</li>
+			</ol>
+		</div>
+		<div class="item" id="api-intro">
+			<h3>Einführung</h3>
+			<p>
+				Die Iro API enthält Funktion, die für den Massenversand nützlich sind.
+			</p>
+		</div>
+		<div class="item" id="api-interfaces">
+			<h3>Interfaces</h3>
+			<p>
+				Die Iro API stellt zur Zeit nur ein Interfaces bereit.
+			</p>
+			<div class="item" id="interface-xmlrpc">
+				<h4>XML-RPC</h4>
+				<p>
+				Interface-URI: <code>https://<em>&lt;benutzer&gt;</em>:<em>&lt;passwort&gt;</em>@localhost:8000</code>
+				</p>
+				<p>
+					Die aufgerufene Methode wird dabei im <code>&lt;methodName /&gt;</code> übergeben.
+				</p>
+			</div>
+		</div>
+		<div class="item" id="api-methods">
+			<h3>Methoden</h3>
+			<div py:for="method in old" class="item" id="method-${method.name}">
+				<h4>${method.title}</h4>
+				<p><code>${method.name}${method.func_line}</code></p>
+				<p py:content="method.description">
+					Diese Methode at bis jetzt noch keine Beschreibung.
+				</p>
+				<h5>Parameter</h5>
+				<table class="docs">
+					<thead>
+						<tr>
+							<td>Parameter</td>
+							<td>Typ</td>
+							<td>Beschreibung</td>
+						</tr>					
+					</thead>
+					<tbody>
+						<tr py:for="arg in method.args">
+							<td>${arg.name}</td>
+							<td>${arg.typ}</td>
+							<td>${arg.description}</td>
+						</tr>
+					</tbody>
+				</table>
+				<py:if test="method.rets">
+				<h5>Ausgabe</h5>
+				<table class="docs">
+					<thead>
+						<tr>
+							<td>Parameter</td>
+							<td>Typ</td>
+							<td>Beschreibung</td>
+						</tr>					
+					</thead>
+					<tbody>
+						<tr py:for="arg in method.rets">
+							<td>${arg.name}</td>
+							<td>${arg.typ}</td>
+							<td>${arg.description}</td>
+						</tr>
+					</tbody>
+				</table>	
+				</py:if>
+			</div>
+		</div>
+	</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extras/iro.tac	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,32 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.application.service import Application
+
+from iro import iro
+
+def get_application():
+    app = Application("Iro")
+    cfg={"config":"iro.conf"}
+    iro.makeService(cfg).setServiceParent(app)
+    return app
+
+application = get_application()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fabfile.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,102 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from fabric.api import local, run, env 
+from fabric.operations import put
+
+from iro import __version__
+import ngdatabase
+
+ngmodules = {"directory": "/home/hefee/hg/ngmod/init",
+        "version":ngdatabase.__version__}
+
+env.use_ssh_config = True
+
+def vbox():
+    env.hosts = ['192.168.56.101']
+    env.directory = '/home/hefee/iro/'
+    env.activate = 'source /home/hefee/iro/virtualenv/bin/activate'
+    env.deploy_user = 'hefee'
+
+
+def hal():
+    env.hosts = ['sandy@hal.netzguerilla.net']
+    env.directory = "iro"
+
+def iro():
+    env.hosts = ['hal']
+    env.directory = '/home/sandy/virtualenv/iro2/'
+    env.activate = 'source /home/sandy/virtualenv/iro2/bin/activate'
+    env.deploy_user = 'sandy'
+
+
+def prepare_deploy():
+    local("python setup.py sdist")
+
+def virtualenv(cmd):
+    run("%s && %s"  % (env.activate, cmd))
+
+def deploy():
+    prepare_deploy()
+    put("dist/iro-%s.tar.gz"%__version__,"%s/dist/"%env.directory)
+    put("%(directory)s/dist/ngmodules-%(version)s.tar.gz"%ngmodules,"%s/dist/"%env.directory)
+    virtualenv("pip install %s/dist/ngmodules-%s.tar.gz"%(env.directory,ngmodules["version"]))
+    virtualenv("pip install -e 'git+git://github.com/hefee/txjsonrpc.git#egg=txjsonrpc'")
+    virtualenv("pip uninstall -y iro")
+    virtualenv("pip install %s/dist/iro-%s.tar.gz"%(env.directory,__version__))
+
+def prepare_tests():
+    put("tests/*","%s/tests/"%env.directory)
+
+def startserver():
+    run("cd %s && tar -xzf dist/iro-%s.tar.gz"%(env.directory,__version__))
+    virtualenv("twistd -ny %s/iro-%s/extras/iro.tac"%(env.directory,__version__))
+
+def testserver():
+   prepare_tests()
+   virtualenv("python %s/tests/xmlrpc.py"%env.directory)
+
+def cleandoc():
+    local("rm -rf _build/* web/dev/* web/*.html")
+
+def sphinxdoc():
+    local("sphinx-build -b html -d _build/doctrees doc web/dev/")
+
+def createweb():
+    prepare_deploy()
+    cleandoc()
+    local("mv dist/iro-%s.tar.gz web/files/"%__version__)
+    local("python createdoc.py")
+    sphinxdoc()
+    local("tar -czf web.tar.gz web")
+
+def pushweb():
+    put("web.tar.gz","/tmp/")
+    run("tar -czf backup/iro.tar.gz iro/*")
+    run("tar -xzf /tmp/web.tar.gz")
+    run("rm -r iro/*")
+    run("mv web/* %s/"%env.directory)
+    run("rmdir web")
+    run("rm /tmp/web.tar.gz")
+
+def web():
+    createweb()
+    pushweb()
--- a/iro.conf.inst	Wed Dec 21 22:07:48 2011 +0100
+++ b/iro.conf.inst	Thu Sep 27 17:15:46 2012 +0200
@@ -1,37 +1,55 @@
-[geonet]
-host = localhost
-port = 25
-send_from =
-user = 
-password =
-
-
-[FAX_de]
-host = localhost
-port = 25
-send_from = 
-user = 
-password =
-
-[sipgate]
-user = 
-password =
-
-[smtp]
-host = localhost
-port = 25
-send_from =
-user = 
-password =
-TLS=No
-SSL=No
-
-[server]
-key=
-cert=
-
-[smstrade]
-key=
-route=basic
-debug=1
-from=
+[main]
+# Connection URL to database
+dburl = 
+
+# Port under that twisted is running
+port = 
+
+[sipgate]
+# One available provider typ.
+typ = sipgate
+
+# Loginname for sipgate
+username = 
+
+# Password for sipgate
+password = 
+
+# price for one sms
+# price_sms = 0.079
+
+# price for one fax
+# price_fax = 0.03
+
+[smstrade]
+# One available provider typ.
+typ = smstrade
+
+# smstrade Gateway Key https://login.smstrade.de/index.php?gateway
+key = 
+
+[smtp]
+# One available provider typ.
+typ = smtp
+
+# Hostname of MTA
+host = 
+
+# Port of the MTA
+# port = 25
+
+# username to login into MTA.
+# user = 
+
+# password to login into MTA.
+# password = 
+
+# use SSL for connection to MTA
+# SSL = False
+
+# use TLS for connection to MTA
+# TLS = False
+
+# Emailaddress from which mail will be sended.
+send_from = 
+
--- a/iro/.eric4project/iro.e4p	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE UserProject SYSTEM "UserProject-4.0.dtd">
-<!-- eric4 user project file for project iro -->
-<!-- Saved: 2009-10-10, 13:26:41 -->
-<!-- Copyright (C) 2009 Sandro Knauß, bugs@sandroknauss.de -->
-<UserProject version="4.0">
-</UserProject>
--- a/iro/.eric4project/iro.e4q	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE UserProject SYSTEM "UserProject-4.0.dtd">
-<!-- eric4 user project file for project iro -->
-<!-- Saved: 2009-11-24, 02:01:06 -->
-<!-- Copyright (C) 2009 Sandro Knauß, bugs@sandroknauss.de -->
-<UserProject version="4.0">
-</UserProject>
\ No newline at end of file
--- a/iro/.eric4project/iro.e4t	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Tasks SYSTEM "Tasks-4.2.dtd">
-<!-- eric4 tasks file for project iro -->
-<!-- Saved: 2009-11-24, 02:01:07 -->
-<Tasks version="4.2">
-</Tasks>
\ No newline at end of file
--- a/iro/MyIro_daemon.inst	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-#! /bin/sh
-NAME="Iro"
-USER=sandy
-HOMEDIR=/home/${USER}
-DEAMON=${HOMEDIR}/bin/MyIro
-DEAMON_OPTS=""
-PID=${HOMEDIR}/var/run/$NAME.pid
-
-test -x $DEAMON || exit 0
-
-. /lib/lsb/init-functions
-
-case "$1" in
-  start)
-	log_daemon_msg "Starting $NAME" $NAME
-	if start-stop-daemon --start --quiet --background --oknodo --pidfile $PID --make-pidfile --user ${USER} --group ${USER} --chdir ${HOMEDIR} --startas $DEAMON -- $DEAMON_OPTS; then
-	    log_end_msg 0
-	else
-	    log_end_msg 1
-	fi
-	;;
-  stop)
-	log_daemon_msg "Stopping $NAME" $NAME
-	if start-stop-daemon --stop --quiet --oknodo --pidfile $PID; then
-	    log_end_msg 0
-	else
-	    log_end_msg 1
-	fi
-	;;
-
-  restart)
-	$0 stop
-	sleep 1
-	$0 start
-	;;
-
-  status)
-	status_of_proc -p $PID $DEAMON $NAME && exit 0 || exit $?
-	;;
-
-  *)
-	log_action_msg "Usage: $0 {start|stop|restart|status}"
-	exit 1
-esac
-
-exit 1
--- a/iro/__init__.py	Wed Dec 21 22:07:48 2011 +0100
+++ b/iro/__init__.py	Thu Sep 27 17:15:46 2012 +0200
@@ -1,1 +1,22 @@
- 
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+__version__='2.0rc0'
--- a/iro/acounting.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from database import Database
-import logging
-logger=logging.getLogger("iro.acounting");
-
-class Acounting(Database):
-    def __init__(self,id, connection):
-        Database.__init__(self,connection)
-        self.id=id
-
-    def setId(self,id, user):
-        self.id=id
-        if user:
-            self.connect()
-            self.cursor.execute ("INSERT INTO %s (id,user) VALUES ('%s','%s')" % (self.connection['overview'], self.id, user))
-            self.disconnect()
-
-
-    def getStatus(self):
-        self.connect()
-        self.cursor.execute ("SELECT status,tel FROM %s WHERE id='%s'" % (self.connection['table'], self.id))
-        ret= self.cursor.fetchall()
-        self.disconnect()
-        return ret
-
-    def addGood(self, good,disconnect=True):
-        if type(good) == list:
-            for i in good:
-                self.addGood(i)
-            if disconnect:
-                self.disconnect()
-        else:
-            self.connect()
-            self.cursor.execute("INSERT INTO %s (id,tel,status) VALUES('%s','%s','sended')" % (self.connection['table'], self.id, good))
-            if disconnect:
-                self.disconnect()
-            
-
-    def addFailed(self, failed,disconnect=True):
-        if type(failed) == list:
-            for i in failed:
-                self.addFailed(i,False)
-            if disconnect:
-                self.disconnect()
-        else:
-            self.connect()
-            self.cursor.execute ("INSERT INTO %s (id,tel,status) VALUES('%s','%s','failed')"%(self.connection['table'], self.id, failed))
-            if disconnect:
-                self.disconnect()
--- a/iro/anbieter/FAX_de.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-from smtp import SMTP
-from geonet import geonet
-from telnumber import telnumber
-
-class FAX_de(geonet):
-    section="FAX_de"
-    default_conf="iro.conf"
-    max_recipients=50
-
-    def __init__(self):
-        self.smtp=SMTP(self.default_conf,self.section)
-
-    def createMailaddress(produkt,number):
-        try:
-            tel=telnumber(number)
-            return "%s00%s%s@fax.de" %(produkt,tel.land,tel.number)
-        except:
-            return number
--- a/iro/anbieter/__init__.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-from sipgate import sipgate
-from geonet import geonet
-from FAX_de import FAX_de
-from smtp import SMTP
-from smstrade import smstrade
-import content
--- a/iro/anbieter/anbieter.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-class anbieter:
-    default_conf = ''    # override this
-    section = 'anbieter'   # override this
-
-    def sendSMS(self,sms,recipients):
-        pass
-    def sendFAX(self,fax,recipients):
-        pass
-    def sendMail(self,mail,recipients):
-        pass
-    
-    def setJob(self, job):
-        self.job=job
--- a/iro/anbieter/content.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-from email.mime.text import MIMEText
-from email.header import Header
-
-class content:
-    def __init__(self,content):
-        self.content=content
-
-    def sendto(self,anbieter,recipients):
-        pass
-
-    def getContent(self):
-        return self.content
-
-    def __eq__(self,other):
-        return self.content == other.content
-
-    def __ne__(self,other):
-        return not self.__eq__(other)
-
-class SMS(content):
-    def __init__(self, cont):
-        content.__init__(self,cont)
-
-    def sendto(self,anbieter,recipients):
-        anbieter.sendSMS(self,recipients)
-
-
-class FAX(content):
-    def __init__(self,header,cont,attachments):
-        content.__init__(self,cont)
-        self.header=header
-        self.attachments=attachments
-
-    def sendto(self,anbieter,recipients):
-        anbieter.sendFAX(self,recipients)
-
-    def getAttachment(self,i):
-        return self.attachments[i]
-
-    def __eq__(self,other):
-        if not  content.__eq__(self,other):
-            return False
-
-        if self.header != other.header:
-            return False
-
-        if len(self.attachments) != len(other.attachments):
-            return False
-
-        for i in range(len(self.attachments)):
-            if self.attachments[i] != other.attachments[i]:
-                return False
-
-        return True
-
-
-
-class Mail(content):
-    def __init__(self, subject, body, frm):
-        con=MIMEText(body.encode("utf-8"), _charset='utf-8')
-        sub=Header(subject.encode('utf-8'), 'utf-8')
-        con['Subject']=sub
-        self.frm=frm
-        content.__init__(self, con)
-
-    def sendto(self,anbieter,recipients):
-        anbieter.sendMail(self,recipients)
-
-    def as_string(self):
-        return self.content.as_string()
-
-    def getFrom(self):
-        return self.frm
-    
-    def __eq__(self,other):
-        if self.as_string() != other.as_string():
-            return False
-
-        if self.frm != other.frm:
-            return False
-
-        return True
--- a/iro/anbieter/geonet.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-from smtp import SMTP
-from anbieter import anbieter
-from telnumber import telnumber
-
-from email import Encoders
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEBase import MIMEBase
-from email.MIMEText import MIMEText
-
-class Mail(MIMEMultipart):
-    def __init__(self,send_from,header,cont,attachments):
-        MIMEMultipart.__init__(self)
-        self['From'] = send_from
-        self['Subject']=header
-        self.attach(MIMEText(cont))
-        for attachment in attachments:
-            part = MIMEBase('application', mimetypes.guess_type(attachment)[0])
-            part.set_payload( open(attachment,"rb").read() )
-            Encoders.encode_base64(part)
-            art.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
-            self.attach(part)
-
-class geonet(anbieter):
-    section="geonet"
-    default_conf="iro.conf"
-    max_recipients=50
-
-    def __init__(self):
-        self.smtp=SMTP(self.default_conf,self.section)
-
-    def createMailaddress(produkt,number):
-        try:
-            tel=telnum(number)
-            return "%s%s@%s.geonet.de" %(tel.land,tel.number,produkt)
-        except:
-            return number
-
-    def sendSMS(self,sms,recipients):
-        recps=[]
-        for recpipient in recipients:
-            recps.append(self.createMailadress(recpipient))
-        self.sendMail(Mail(self.smtp.send_from,sms.content,"",[]),recps)
-
-    def sendFAX(self,fax,recipients):
-        recps=[]
-        for recpipient in recipients:
-            recps.append(self.createMailadress(recpipient))
-        self.sendMail(Mail(self.smtp.send_from,"","",fax.attachments),recps)
--- a/iro/anbieter/gsm0338.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,192 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-import codecs
-import _multibytecodec as mbc 
-
-ENCODING_NAME = "gsm0338"
-
-decoding_map= {
-unichr(0x00):unichr(0x0040), # COMMERCIAL AT
-unichr(0x01):unichr(0x00A3), # POUND SIGN
-unichr(0x02):unichr(0x0024), # DOLLAR SIGN
-unichr(0x03):unichr(0x00A5), # YEN SIGN
-unichr(0x04):unichr(0x00E8), # LATIN SMALL LETTER E WITH GRAVE
-unichr(0x05):unichr(0x00E9), # LATIN SMALL LETTER E WITH ACUTE
-unichr(0x06):unichr(0x00F9), # LATIN SMALL LETTER U WITH GRAVE
-unichr(0x07):unichr(0x00EC), # LATIN SMALL LETTER I WITH GRAVE
-unichr(0x08):unichr(0x00F2), # LATIN SMALL LETTER O WITH GRAVE
-unichr(0x09):unichr(0x00E7), # LATIN SMALL LETTER C WITH CEDILLA
-unichr(0x0A):unichr(0x000A), # LINE FEED
-unichr(0x0B):unichr(0x00D8), # LATIN CAPITAL LETTER O WITH STROKE
-unichr(0x0C):unichr(0x00F8), # LATIN SMALL LETTER O WITH STROKE
-unichr(0x0D):unichr(0x000D), # CARRIAGE RETURN
-unichr(0x0E):unichr(0x00C5), # LATIN CAPITAL LETTER A WITH RING ABOVE
-unichr(0x0F):unichr(0x00E5), # LATIN SMALL LETTER A WITH RING ABOVE
-unichr(0x10):unichr(0x0394), # GREEK CAPITAL LETTER DELTA
-unichr(0x11):unichr(0x005F), # LOW LINE
-unichr(0x12):unichr(0x03A6), # GREEK CAPITAL LETTER PHI
-unichr(0x13):unichr(0x0393), # GREEK CAPITAL LETTER GAMMA
-unichr(0x14):unichr(0x039B), # GREEK CAPITAL LETTER LAMDA
-unichr(0x15):unichr(0x03A9), # GREEK CAPITAL LETTER OMEGA
-unichr(0x16):unichr(0x03A0), # GREEK CAPITAL LETTER PI
-unichr(0x17):unichr(0x03A8), # GREEK CAPITAL LETTER PSI
-unichr(0x18):unichr(0x03A3), # GREEK CAPITAL LETTER SIGMA
-unichr(0x19):unichr(0x0398), # GREEK CAPITAL LETTER THETA
-unichr(0x1A):unichr(0x039E), # GREEK CAPITAL LETTER XI
-unichr(0x1B):unichr(0x00A0), # ESCAPE TO EXTENSION TABLE (or displayed as NBSP, see note above)
-unichr(0x1B0A):unichr(0x000C), # FORM FEED
-unichr(0x1B14):unichr(0x005E), # CIRCUMFLEX ACCENT
-unichr(0x1B28):unichr(0x007B), # LEFT CURLY BRACKET
-unichr(0x1B29):unichr(0x007D), # RIGHT CURLY BRACKET
-unichr(0x1B2F):unichr(0x005C), # REVERSE SOLIDUS
-unichr(0x1B3C):unichr(0x005B), # LEFT SQUARE BRACKET
-unichr(0x1B3D):unichr(0x007E), # TILDE
-unichr(0x1B3E):unichr(0x005D), # RIGHT SQUARE BRACKET
-unichr(0x1B40):unichr(0x007C), # VERTICAL LINE
-unichr(0x1B65):unichr(0x20AC), # EURO SIGN
-unichr(0x1C):unichr(0x00C6), # LATIN CAPITAL LETTER AE
-unichr(0x1D):unichr(0x00E6), # LATIN SMALL LETTER AE
-unichr(0x1E):unichr(0x00DF), # LATIN SMALL LETTER SHARP S (German)
-unichr(0x1F):unichr(0x00C9), # LATIN CAPITAL LETTER E WITH ACUTE
-unichr(0x20):unichr(0x0020), # SPACE
-unichr(0x21):unichr(0x0021), # EXCLAMATION MARK
-unichr(0x22):unichr(0x0022), # QUOTATION MARK
-unichr(0x23):unichr(0x0023), # NUMBER SIGN
-unichr(0x24):unichr(0x00A4), # CURRENCY SIGN
-unichr(0x25):unichr(0x0025), # PERCENT SIGN
-unichr(0x26):unichr(0x0026), # AMPERSAND
-unichr(0x27):unichr(0x0027), # APOSTROPHE
-unichr(0x28):unichr(0x0028), # LEFT PARENTHESIS
-unichr(0x29):unichr(0x0029), # RIGHT PARENTHESIS
-unichr(0x2A):unichr(0x002A), # ASTERISK
-unichr(0x2B):unichr(0x002B), # PLUS SIGN
-unichr(0x2C):unichr(0x002C), # COMMA
-unichr(0x2D):unichr(0x002D), # HYPHEN-MINUS
-unichr(0x2E):unichr(0x002E), # FULL STOP
-unichr(0x2F):unichr(0x002F), # SOLIDUS
-unichr(0x30):unichr(0x0030), # DIGIT ZERO
-unichr(0x31):unichr(0x0031), # DIGIT ONE
-unichr(0x32):unichr(0x0032), # DIGIT TWO
-unichr(0x33):unichr(0x0033), # DIGIT THREE
-unichr(0x34):unichr(0x0034), # DIGIT FOUR
-unichr(0x35):unichr(0x0035), # DIGIT FIVE
-unichr(0x36):unichr(0x0036), # DIGIT SIX
-unichr(0x37):unichr(0x0037), # DIGIT SEVEN
-unichr(0x38):unichr(0x0038), # DIGIT EIGHT
-unichr(0x39):unichr(0x0039), # DIGIT NINE
-unichr(0x3A):unichr(0x003A), # COLON
-unichr(0x3B):unichr(0x003B), # SEMICOLON
-unichr(0x3C):unichr(0x003C), # LESS-THAN SIGN
-unichr(0x3D):unichr(0x003D), # EQUALS SIGN
-unichr(0x3E):unichr(0x003E), # GREATER-THAN SIGN
-unichr(0x3F):unichr(0x003F), # QUESTION MARK
-unichr(0x40):unichr(0x00A1), # INVERTED EXCLAMATION MARK
-unichr(0x41):unichr(0x0041), # LATIN CAPITAL LETTER A
-unichr(0x42):unichr(0x0042), # LATIN CAPITAL LETTER B
-unichr(0x43):unichr(0x0043), # LATIN CAPITAL LETTER C
-unichr(0x44):unichr(0x0044), # LATIN CAPITAL LETTER D
-unichr(0x45):unichr(0x0045), # LATIN CAPITAL LETTER E
-unichr(0x46):unichr(0x0046), # LATIN CAPITAL LETTER F
-unichr(0x47):unichr(0x0047), # LATIN CAPITAL LETTER G
-unichr(0x48):unichr(0x0048), # LATIN CAPITAL LETTER H
-unichr(0x49):unichr(0x0049), # LATIN CAPITAL LETTER I
-unichr(0x4A):unichr(0x004A), # LATIN CAPITAL LETTER J
-unichr(0x4B):unichr(0x004B), # LATIN CAPITAL LETTER K
-unichr(0x4C):unichr(0x004C), # LATIN CAPITAL LETTER L
-unichr(0x4D):unichr(0x004D), # LATIN CAPITAL LETTER M
-unichr(0x4E):unichr(0x004E), # LATIN CAPITAL LETTER N
-unichr(0x4F):unichr(0x004F), # LATIN CAPITAL LETTER O
-unichr(0x50):unichr(0x0050), # LATIN CAPITAL LETTER P
-unichr(0x51):unichr(0x0051), # LATIN CAPITAL LETTER Q
-unichr(0x52):unichr(0x0052), # LATIN CAPITAL LETTER R
-unichr(0x53):unichr(0x0053), # LATIN CAPITAL LETTER S
-unichr(0x54):unichr(0x0054), # LATIN CAPITAL LETTER T
-unichr(0x55):unichr(0x0055), # LATIN CAPITAL LETTER U
-unichr(0x56):unichr(0x0056), # LATIN CAPITAL LETTER V
-unichr(0x57):unichr(0x0057), # LATIN CAPITAL LETTER W
-unichr(0x58):unichr(0x0058), # LATIN CAPITAL LETTER X
-unichr(0x59):unichr(0x0059), # LATIN CAPITAL LETTER Y
-unichr(0x5A):unichr(0x005A), # LATIN CAPITAL LETTER Z
-unichr(0x5B):unichr(0x00C4), # LATIN CAPITAL LETTER A WITH DIAERESIS
-unichr(0x5C):unichr(0x00D6), # LATIN CAPITAL LETTER O WITH DIAERESIS
-unichr(0x5D):unichr(0x00D1), # LATIN CAPITAL LETTER N WITH TILDE
-unichr(0x5E):unichr(0x00DC), # LATIN CAPITAL LETTER U WITH DIAERESIS
-unichr(0x5F):unichr(0x00A7), # SECTION SIGN
-unichr(0x60):unichr(0x00BF), # INVERTED QUESTION MARK
-unichr(0x61):unichr(0x0061), # LATIN SMALL LETTER A
-unichr(0x62):unichr(0x0062), # LATIN SMALL LETTER B
-unichr(0x63):unichr(0x0063), # LATIN SMALL LETTER C
-unichr(0x64):unichr(0x0064), # LATIN SMALL LETTER D
-unichr(0x65):unichr(0x0065), # LATIN SMALL LETTER E
-unichr(0x66):unichr(0x0066), # LATIN SMALL LETTER F
-unichr(0x67):unichr(0x0067), # LATIN SMALL LETTER G
-unichr(0x68):unichr(0x0068), # LATIN SMALL LETTER H
-unichr(0x69):unichr(0x0069), # LATIN SMALL LETTER I
-unichr(0x6A):unichr(0x006A), # LATIN SMALL LETTER J
-unichr(0x6B):unichr(0x006B), # LATIN SMALL LETTER K
-unichr(0x6C):unichr(0x006C), # LATIN SMALL LETTER L
-unichr(0x6D):unichr(0x006D), # LATIN SMALL LETTER M
-unichr(0x6E):unichr(0x006E), # LATIN SMALL LETTER N
-unichr(0x6F):unichr(0x006F), # LATIN SMALL LETTER O
-unichr(0x70):unichr(0x0070), # LATIN SMALL LETTER P
-unichr(0x71):unichr(0x0071), # LATIN SMALL LETTER Q
-unichr(0x72):unichr(0x0072), # LATIN SMALL LETTER R
-unichr(0x73):unichr(0x0073), # LATIN SMALL LETTER S
-unichr(0x74):unichr(0x0074), # LATIN SMALL LETTER T
-unichr(0x75):unichr(0x0075), # LATIN SMALL LETTER U
-unichr(0x76):unichr(0x0076), # LATIN SMALL LETTER V
-unichr(0x77):unichr(0x0077), # LATIN SMALL LETTER W
-unichr(0x78):unichr(0x0078), # LATIN SMALL LETTER X
-unichr(0x79):unichr(0x0079), # LATIN SMALL LETTER Y
-unichr(0x7A):unichr(0x007A), # LATIN SMALL LETTER Z
-unichr(0x7B):unichr(0x00E4), # LATIN SMALL LETTER A WITH DIAERESIS
-unichr(0x7C):unichr(0x00F6), # LATIN SMALL LETTER O WITH DIAERESIS
-unichr(0x7D):unichr(0x00F1), # LATIN SMALL LETTER N WITH TILDE
-unichr(0x7E):unichr(0x00FC), # LATIN SMALL LETTER U WITH DIAERESIS
-unichr(0x7F):unichr(0x00E0), # LATIN SMALL LETTER A WITH GRAVE
-}
-
-encoding_map=dict([(v,k) for (k,v) in decoding_map.items()])
-
-class Codec(codecs.Codec):
-	def encode(self,input,errors='strict'):
-		ret=""
-		for i in input:
-		  ret+=encoding_map[i]
-		return (ret,len(ret))
-	def decode(self,input,errors='strict'):
-		ret=""
-		for i in input:
-		  ret+=decoding_map[i]
-		return (ret,len(ret))
-
-
-
-class StreamWriter(Codec,mbc.MultibyteStreamWriter,codecs.StreamWriter):
-    pass
-
-class StreamReader(Codec,mbc.MultibyteStreamReader,codecs.StreamReader):
-    pass
-
-### encodings module API
-
-def getregentry():
-	return (Codec().encode,Codec().decode,StreamReader,StreamWriter)
-
-
-def gsm_search(encoding):
-	if not encoding == ENCODING_NAME:
-		return
-	return getregentry()
-	
-# Register our codec when we load the module
-codecs.register(gsm_search)
-
-if __name__ == "__main__":
-	text = "€öäüß"
-	text2 = unicode(text,"utf-8").encode("gsm0338")
-	assert(text2==u"\u1B65\x7C\x7B\x7E\x1E")
-	text=""
-	text2 = unicode(text,"utf-8").encode("gsm0338")
-	assert(text==text2)
-
--- a/iro/anbieter/sipgate.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,157 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-
-from anbieter import anbieter
-from telnumber import telnumber, NotATelNumber
-import ConfigParser
-import xmlrpclib
-import logging
-logger=logging.getLogger("sipgate")
-
-class NoValidStatusCode(Exception):
-     def __init__(self, value):
-         self.value = value
-     
-     def __str__(self):
-         return repr(self.value)
-
-class sipgate(anbieter):
-    """
-        s. auch http://www.tuxad.com/sipgate.html
-        und http://lists.sipgate.net/pipermail/sipgate-developers/2007-September/000016.html
-    """
-    section="sipgate"
-    url="https://%s:%s@samurai.sipgate.net/RPC2"
-    def __init__(self,user="",password=""):
-        self.user=user
-        self.password=password
-
-    def read_basic_config(self,filenames):
-        """Read basic options from the config file"""
-        cp = ConfigParser.ConfigParser()
-        cp.read(filenames)
-        self.user=cp.get(self.section, 'user')
-        self.password=cp.get(self.section, 'password')
-
-    def sendSMS(self,sms,recipients):
-        """send SMS with $sms to $recipients"""
-        logger.debug('sipgate.sendSMS(%s,%s)'%(sms.getContent(),  str(recipients)))
-        args={
-                "TOS" : "text",
-                "Content" : sms.getContent()
-                }
-        self.__send(args,recipients)
-
-    def sendFAX(self,fax,recipients):
-        """send the PDF file $fax  to $recipients"""
-        logger.debug('sipgate.sendFAX(%s,%s)'%(fax,  str(recipients)))
-        args={
-                "TOS" : "fax",
-                "Content" : xmlrpclib.Binary(fax.getAttachment(0)),
-                }
-        self.__send(args,recipients)
-
-    def __connect(self):
-        """connect to sipgate XMLRPC Server"""
-        logger.debug("sipgate.__connect()-"+self.url%(self.user,"XXXXXXXX"))
-        
-        self.serv=xmlrpclib.ServerProxy(self.url%(self.user,self.password)) 
-        self.samurai=self.serv.samurai
-
-        args_identify = {
-                "ClientName"    : "anbieter.py",
-                "ClientVersion" : "V1.0",
-                "ClientVendor"  : "Sandro Knauss"
-                }
-        self.__send_method(self.samurai.ClientIdentify, args_identify)
-        return self.serv
-
-    def __send_method(self, func, args=None):
-        """execute $func and test weather if the func  ran successfully or not"""
-        logger.debug("sipgate.__send_method(func,%s)"%( args))
-
-        if args==None:
-            xmlrpc_result = func()
-        else:
-            xmlrpc_result = func(args)
-        if xmlrpc_result['StatusCode'] != 200:
-            raise NoValidStatusCode("There was an error during identification to the server! %d %s"% (xmlrpc_result['StatusCode'], xmlrpc_result['StatusString']))
-        logger.debug("sipgate.__send_method(..):ok"); 
-        return xmlrpc_result
-
-    def __send(self,args,recipients):
-        """main sending method - sending the args to $recipients"""
-        sended=[]
-
-        serv=self.__connect()
-        logger.debug('sipgate.__send(%s,%s)'%(args,  recipients))
-        for recipient in recipients:
-            try:
-                tel = telnumber(recipient)
-
-                if tel in sended:                                                                           #only send message once per recipient
-                    continue
-                sended.append(tel)
-				
-                args["RemoteUri"]="sip:%s%s@sipgate.net"%(tel.land,tel.number)
-                self.__send_method(serv.samurai.SessionInitiate, args)
-                self.updateStatus(arranged=recipient)
-            
-            except (NotATelNumber, NoValidStatusCode):
-                self.updateStatus(failed=recipient)
-
-        self.__disconnect()
-
-    def updateStatus(self, arranged=None, failed=None):
-        """is a function that is called, if a new SMS/FAX was send
-        -arranged is non None, if SMS/FAX was sended successfully
-        -failed is non None, if SMS/FAX sending failed
-        the content will be the recipent"""
-        pass
-
-    def BalanceGet(self):
-        """get the balance of sipgate"""
-        self.__connect()
-        ret = self.__send_method(self.samurai.BalanceGet )
-        self.__disconnect()
-        return ret['CurrentBalance']
-
-    def getNewMessages(self):
-        """get new messages from inbox"""
-        self.__connect()
-        tmp = self.__send_method(self.samurai.UmSummaryGet)
-        self.__disconnect()
-        tmp=tmp['UmSummary']
-        ret={}
-        for entry in tmp:
-            ret[entry['TOS']]={'read':entry["Read"],'unread':entry["Unread"]}
-        return ret
-
-    def getRecommendedInterval(self,methods):
-        """how often you can call one $methods"""
-        self.__connect()
-        args = {"MethodList" : methods }
-        tmp = self.__send_method(self.samurai.RecommendedIntervalGet, args)
-        self. __disconnect()
-        ret={}
-        for entry in tmp['IntervalList']:
-            ret[entry['MethodName']]=entry['RecommendedInterval']
-        return ret
-
-    def __disconnect(self):
-        """disconnect xmlrpc client"""
-        logger.debug('sipgate.__disconnect()')
-        self.samurai=None
-        self.serv=None
-        self.xmlrpc=None
--- a/iro/anbieter/smstrade.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Georg Bischoff
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-
-from anbieter import anbieter
-from sipgate import  NoValidStatusCode
-from telnumber import telnumber, NotATelNumber
-import ConfigParser
-import urllib, httplib
-from httplib import socket
-
-import logging
-logger=logging.getLogger("smstrade")
-
-class UnknownStatusCode(Exception):
-    def __init__(self,code):
-        self.code=code
-
-    def __str__(self):
-        return "StatusCode %i is unknown"%self.code
-
-
-class StatusCode:
-     statusCodes = {10 : "Empfaengernummer nicht korrekt",
-            20 : "Absenderkennung nicht korrekt",
-            30 : "Nachrichtentext nicht korrekt",
-            31 : "Messagetyp nicht korrekt",
-            40 : "SMS Route nicht korrekt",
-            50 : "Identifikation fehlgeschlagen",
-            60 : "nicht genuegend Guthaben",
-            70 : "Netz wird von Route nicht abgedeckt",
-            71 : "Feature nicht ueber diese Route moeglich",
-            80 : "Uebergabe an SMS-C fehlgeschlagen",
-            90 : "Versand nicht moeglich",
-            100 : "SMS wurde versendet",
-            999 : "SMS wird zeitversetzt verschickt"}
-
-     def __init__(self,code):
-        if code in self.statusCodes.keys():
-            self.code=code
-        else:
-            raise UnknownStatusCode(code)
-     
-     def __str__(self):
-        try:
-            return self.statusCodes[self.code]
-        except IndexError:
-            raise UnknownStatusCode(self.code)
-
-     def __int__(self):
-        if not self.code in self.statusCodes.keys():
-            raise UnknownStatusCode(self.code)
-        return self.code
-
-
-
-
-class smstrade(anbieter):
-    """
-    s. auch http://kundencenter.smstrade.de/sites/smstrade.de.kundencenter/__pdf/SMS-Gateway_HTTP_API_v2.pdf
-    """
-    section="smstrade"
-    url="https://gateway.smstrade.de"
-    def __init__(self):       
-        self.domain = "smstrade.de" # website of the sms service
-        self.gateway = "gateway.smstrade.de" # gateway where the request will be sent
-        self.gatewayPort = 80 # port of the gateway
-        self.script = "/"  # full path to the script that will handle the request
-        self.method = "POST" # method that will be used. Currently only POST is supported
-
-    def read_basic_config(self, filenames):
-        """Read basic options from the config file"""
-        cp = ConfigParser.ConfigParser()
-        cp.read(filenames)
-        self.key=cp.get(self.section, 'key')
-        self.route=cp.get(self.section, 'route')
-        self.from_=cp.get(self.section, 'from')
-        self.debug=cp.get(self.section, 'debug')
-
-    def sendSMS(self,sms,recipients):
-        """send SMS with $sms to $recipients"""
-        logger.debug('smstrade.sendSMS(%s,%s)'%(sms,  str(recipients)))
-        sended = []
-        route = unicode(self.route)
-        message = sms.content
-        timestamp = None
-        for recipient in recipients:
-            try:
-                tel = telnumber(recipient)                
-                if tel in sended:                                             #only send message once per recipient
-                    continue
-                sended.append(tel)	
-                to ='00'+tel.land+tel.number
-                if tel.land == '49':
-                    route=unicode("basic")
-                else:
-                    route=unicode("economy")
-                smsSendStatus = self.__send(route, to, message, timestamp)	
-                logger.info('smstrade._send(...)=%i(%s)'%(int(smsSendStatus),str(smsSendStatus)))
-                if int(smsSendStatus) in(100, 999):
-                    self.updateStatus(arranged=recipient)
-                else:
-                    self.updateStatus(failed=recipient)
-            except (NotATelNumber,NoValidStatusCode,InternetConnectionError):
-                self.updateStatus(failed=recipient)
-
-    def __send(self, route, to, message,  timestamp=None):
-        """ This function is the main part of the request to the sms service.    
-        The function has to return a unicode formated string that will represent the answer of the sms service
-        to the request."""
-        logger.debug('smstrade._send(%s,%s,%s,%s)'%( route, to, message, timestamp))
-        parameters= {"key": self.key,
-                "route": route,
-                "to": to,
-                "message": message,
-                "charset":"utf-8", 
-                "debug": self.debug,
-                }
-        
-        if self.from_ is not None:
-            parameters["from"] = self.from_
-        
-        if timestamp is not None:
-            parameters["senddate"] = unicode(timestamp)
-
-        parameters["concat_sms"] = "1" if len(message) > 160 else "0"
-        params = "&".join( ["%s=%s" % (urllib.quote(k),urllib.quote(v.encode("utf-8"))) for (k, v) in parameters.items()])
-        logger.debug('smstrade._send-parameters:%s\n\t->%s'%(str(parameters), str(params)) )
-        headers = {"Content-type": "application/x-www-form-urlencoded",
-            "Accept": "text/plain"}
-        conn = httplib.HTTPConnection("%s:%i" % (self.gateway, self.gatewayPort))
-        try:
-            conn.request(self.method, self.script, params, headers)
-            response = conn.getresponse()
-            data = response.read()
-        except socket.gaierror:
-            raise InternetConnectionError("%s:%i" % (self.gateway, self.gatewayPort))
-        finally:
-            conn.close()
-            
-        try:
-            return StatusCode(int(data))
-        except UnknownStatusCode:
-            # this happens if the sms will be send delayed
-            return StatusCode(999)
-
-    def updateStatus(self, arranged=None, failed=None):
-        """is a function that is called, if a new SMS/FAX was send
-        -arranged is non None, if SMS/FAX was sended successfully
-        -failed is non None, if SMS/FAX sending failed
-        the content will be the recipent"""
-        pass
-
-class InternetConnectionError(Exception):
-	def __init__(self, url):
-		self.url = url
-
-	def __str__(self):
-		return "InternetConnectionError: It is not possible to open 'http://%s'. Please check your connection to the Internet!" % self.url
--- a/iro/anbieter/smtp.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-class anbieter:
-    default_conf = ''    # override this
-import smtplib
-import ConfigParser
-import logging
-import copy
-
-logger=logging.getLogger("SMTP")
-
-class SMTP():
-    def __init__(self,config_filenames=None,section="smtp"):
-        self.config_filenames=config_filenames
-        self.section=section
-        self.bStart=False
-        self.bTLS=False
-        self.bSSL=False
-        self.max_recipients=1
-        
-
-    def read_basic_config(self,config_filenames=None):
-        """Read basic options from the config file"""
-        if not (config_filenames is None):
-            self.config_filenames=config_filenames
-        
-        cp = ConfigParser.ConfigParser()
-        cp.read(self.config_filenames)
-        self.config_parser = cp
-        self.send_from=cp.get(self.section, 'send_from')
-        self.host=cp.get(self.section, 'host')
-        self.port=cp.get(self.section, 'port')
-        self.user=cp.get(self.section, 'user')
-        self.pw=cp.get(self.section, 'password')
-        
-        try:
-            self.bTLS=cp.getboolean(self.section, 'TLS')
-        except ValueError:
-            self.bTLS=False
-        
-        try:
-            self.bSSL=cp.getboolean(self.section, 'SSL')
-        except ValueError:
-            self.bSSL=False
-
-    def prepareSMTP(self):
-        if self.bSSL:
-            self.smtp = smtplib.SMTP_SSL(self.host,self.port)
-        else:
-            self.smtp = smtplib.SMTP(self.host,self.port)
-        
-        if self.bTLS:
-            self.smtp.starttls()
-        
-        if not self.user == "":
-            self.smtp.login(self.user,self.pw)
-        
-        self.bStart=True
-
-    def sendMail(self,mail,recipients):   
-        logger.debug('SMTP.sendMail(%s,%s)'%(mail,  str(recipients)))
-        if not self.bStart:
-            self.prepareSMTP()
-       
-        frm=self.send_from
-        
-        if mail.getFrom():
-            frm = mail.getFrom()
-        
-        mail.content['From'] = frm 
-	
-
-        while len(recipients) > 0:
-            tmp_recipients=recipients[:self.max_recipients]
-	    tmpmail=copy.deepcopy(mail)
-            tmpmail.content['To']=", ".join(tmp_recipients)
-            logger.debug('self.smtp.sendmail(%s,%s,%s)'%(frm,  str(tmp_recipients), tmpmail.as_string()))
-            self.smtp.sendmail(frm,  tmp_recipients, tmpmail.as_string())
-            self.updateStatus( arranged=tmp_recipients)
-            recipients = recipients[self.max_recipients:]
-        
-        self.shutdownSMTP()
-
-
-    def updateStatus(self, arranged=None, failed=None):
-        """is a function that is called, if a new SMS/FAX was send
-        -arranged is non None, if SMS/FAX was sended successfully
-        -failed is non None, if SMS/FAX sending failed
-        the content will be the recipent"""
-        pass
-
-    def shutdownSMTP(self):
-        self.smtp.quit()
-        self.bStart=False
-    
-    
--- a/iro/anbieter/telnumber.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-class anbieter:
-    default_conf = ''    # override this
-import re
-
-class NotATelNumber(Exception):
-    def __init__(self, number):
-        self.number= number
-    
-    def __str__(self):
-        return ("This is not a telefonnumber:", selfnumber)
-
-class telnumber:
-    re_telnum=re.compile(r'^\s*(\+)?([0-9\s\-/\(\)])+\s*$')
-    re_land=re.compile(r'^\s*(\+|00)(?P<land>[1-9]{2})')
-    re_number=re.compile(r'[^0-9]')
-    std_land="49"
-
-    def __init__(self,number=None):
-        if not(number is  None):
-            self.createNumber(number)
-
-    def createNumber(self, number):
-
-        if not self.re_telnum.match(number):
-            raise NotATelNumber(number)
-        
-        
-        self.land=self.std_land
-        land=self.re_land.match(number)
-        
-        if not(land is None):
-            self.land=land.group("land")
-            number=number[land.end("land"):]
-            
-        number=self.re_number.sub('',number)
-        
-        if number[0]=="0":
-            number=number[1:]
-
-        self.number = number
-        
-    def __eq__(self, y):
-        return ((self.number == y.number) and ( self.land == y.land))
-    
-    def __ne__(self, y):
-        return not self.__eq__(y)
--- a/iro/anbieter/tests/testTelnumber.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-class anbieter:
-    default_conf = ''    # override this
-
-import unittest
-import iro.anbieter.telnumber as tn
-
-class TestTelnumber(unittest.TestCase):
-    def equalNumber(self, tel1, tel2):
-        self.assertEqual(tel1.number, tel2.number)
-        self.assertEqual(tel1.land, tel2.land)
-        
-    def testWrongNumber(self):
-        telnum=tn.telnumber()
-        self.assertRaises(tn.NotATelNumber, telnum.createNumber,  "hallo")
-        self.assertRaises(tn.NotATelNumber, telnum.createNumber,  "0?242")
-        
-   
-    def testNumber(self):
-        telnum=tn.telnumber("0551-28293640")
-        telnum2=tn.telnumber("+49551/28293640")
-        telnum3=tn.telnumber("00495512829364-0")
-        telnum4=tn.telnumber("+49(0)551-28293640")
-        
-        self.assertEqual(telnum.land, "49")
-        self.assertEqual(telnum.number, "55128293640")
-        
-        self.equalNumber(telnum, telnum2)
-        self.equalNumber(telnum, telnum3)
-        self.equalNumber(telnum, telnum4)
-
-    def testEqual(self):
-        telnum=tn.telnumber("0551-28293640")
-        telnum2=tn.telnumber("+49551/28293640")
-        li=[]
-        self.assertEqual(telnum == telnum2, True)
-        self.assertEqual(telnum <> telnum2, False)
-        self.assertEqual(telnum,  telnum2)
-        self.assertEqual(telnum in li,False)
-        li.append(telnum)
-        self.assertEqual(telnum in li,True)
-        self.assertEqual(telnum in li,True)
-
-if __name__ == "__main__":
-    unittest.main()  
-	
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/config.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,219 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.python import log
+
+from ConfigParser import ConfigParser
+import signal
+from functools import partial
+try:
+    from collections import OrderedDict
+except ImportError:
+    from ordereddict import OrderedDict
+
+from validate import vInteger
+from error import NeededOption 
+
+class MyConfigParser(ConfigParser):
+    """Configparser that also validate configfile.
+
+    It is possile to restiger function, that are called, when config file is reloaded
+    """
+    def __init__(self):
+        ConfigParser.__init__(self)
+        self.reloadList=[]
+
+    def read(self,files):
+        """reads an validate configuration file"""
+        from offer import getProvider
+        r = ConfigParser.read(self, files)
+        for s in self.sections():
+            if s == "main":
+                main.validate(self.items(s))
+            else:
+                getProvider("tmp", self.get(s,'typ'), self.items(s))
+        return r
+
+    def reload_(self):
+        """run all registered function."""
+        for f in self.reloadList:
+            f()
+
+    def registerReload(self, func):
+        """adds **func** to reloadList.
+        
+        func ist called with no arguments.
+        """
+        self.reloadList.append(func)
+
+class Option():
+    """One Option in the configuration file"""
+    def __init__(self, validate, long="", help="", must=False, default=None):
+        """
+        :param func validate: a validate function, it has to return the value, if valid and raise an error if not.
+        :param string long: long description
+        :param string help: the help text
+        :param boolean must: Is this option nessasary
+        :param default: default value
+        """
+        self.validate = validate
+        self.long=long
+        self.help = help
+        self.must = must
+        self.default = default
+
+class Config:
+    """Base class for all classes, that uses option from configfile.
+    
+    If one option is valid, the attribute is created with the value of the validate function.
+    """
+    def __init__(self, name, options=None):
+        """
+        :param string name: section name.
+        :param `collections.OrderedDict` options: Orderd Dict of the configuration options (see :attr:`options`) 
+        """
+        self.name = name
+        
+        self.options = options
+        """Options :class:`collections.OrderedDict` for Options used in configuration file (see :class:`iro.config.Option`). Ordering of configuration fields are done by :attr:`order`. 
+        
+        Sample::
+        
+            OrderedDict([
+                ("dburl",Option(lambda x,y:x,long="Connection URL to database",must=True)),
+                ("port",Option(partial(vInteger,minv=0),long="Port under that twisted is running",must=True)),
+            ])
+        
+        A child class typically use update to add more options.
+        """
+        
+        self._init = True
+        """indecates, if the config is loaded with config values."""
+
+
+    def _read(self, cfg, write=False):
+        """Test or set configuration options.
+        
+        :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``.
+        :param boolean write: test or set the option to actual object.
+
+        :raises: :exc:`iro.error.NeededOption`
+        """
+        c = dict(cfg)
+
+        for o in self.options:
+            option = self.options[o]
+            try:
+                value = option.validate(c[o],o)
+                if write:
+                    self._init = False
+                    setattr(self,o,value)
+            except KeyError:
+                if option.must:
+                    raise NeededOption(self.name, o)
+                elif write and option.default is not None:
+                    setattr(self, o, option.default)
+
+    def validate(self, cfg):
+        """Validate configuration.
+
+        :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``.
+        """
+        self._read(cfg, False)
+
+    def load(self, cfg):
+        """Loads configuration into object.
+
+        :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``.
+        """
+        self._read(cfg, True)
+
+    def same(self, other):
+        """returns ``True``, if the options of other object are the same"""
+        for o in self.options:
+            if getattr(self,o) != getattr(other,o):
+                return False
+        else:
+            return True
+
+    def sampleConf(self):
+        """returns a sample Configuration section.
+
+        This function also adds the long help text to the sample section.
+
+        :return: a list of lines
+        """
+        ret=[]
+        for o in self.options:
+            opt=self.options[o]
+            if opt.long:
+                ret.append("# "+opt.long)
+            if opt.must:
+                s= "%s = "%o
+                if opt.default is not None:
+                    s += str(opt.default)
+                ret.append(s)
+            else:
+                ret.append("# %s = %s"%(o,opt.default))
+            ret.append("")
+
+        return ["[%s]"%self.name,]+ret
+
+def readConfig():
+    """Read the configuration and update all registered object (see :meth:`MyConfigParser.reload_`)."""
+    log.msg("Reading configs.")
+    configParser.read(confFiles)
+    configParser.reload_()
+    if main._init:
+        main.load(configParser.items("main"))
+    else:
+        m = Config("main", main_options)
+        m.load(configParser.items("main"))
+        if not main.same(m):
+            raise Exception("Main options can't be reloaded, please restart your Application.")
+
+def init():
+    """Load the main options."""
+    configParser.read(confFiles)
+    main.load(configParser.items("main"))
+
+def registerSignal():
+    '''register readConfig to SIGUSR2'''
+    def rC(signal, frame):
+        readConfig()
+
+    signal.signal(signal.SIGUSR2,rC)
+
+configParser = MyConfigParser()
+"""configParser to get configuration."""
+
+confFiles=["~/iro.conf","/etc/iro/iro.conf"]
+"""Configfile list """
+
+main_options = OrderedDict([
+    ("dburl",Option(lambda x,y:x,long="Connection URL to database",must=True)),
+    ("port",Option(partial(vInteger,minv=0),long="Port under that twisted is running",must=True)),
+    ])
+
+"options for main section"
+
+main = Config("main", main_options)
+"""Main config options"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/controller/__init__.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,20 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/controller/pool.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,63 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.python.threadpool import ThreadPool
+from twisted.internet import threads
+
+class Pool:
+    """wrapper class to handles a twisted threadpool"""
+    def __init__(self,name,maxthreads):
+        """
+        :param string name: name of the threadpool
+        :param integer maxthreads: how many thread a allowed at maximum
+        """
+        self.maxthreads = maxthreads
+        self.pool =  ThreadPool(minthreads=1, maxthreads=maxthreads, name=name)
+        self.reactor = None
+
+    def start(self, reactor):
+        """stats the pool and adds the pool.stop function to the stop procedure.
+        
+        :param reactor: a valid twisted reactor
+        """
+        self.pool.start()
+        self.reactor = reactor
+        self.reactor.addSystemEventTrigger('before', 'shutdown', self.pool.stop)
+
+    def run(self,f,*args,**kwargs):
+        """run a function in Twisted's thread pool"""
+        return threads.deferToThreadPool(self.reactor, self.pool, f, *args, **kwargs)
+
+taskPool = Pool('task',5)
+"""taskpool to handle sending data"""
+
+dbPool = Pool('database',5)
+"""pool to handle database connection via sqlalchemy""" 
+
+pools=[taskPool,dbPool]
+"""all available pools"""
+
+def startPool(reactor):
+    '''run start function for all items in :attr:`pools`'''
+    for pool in pools:
+        pool.start(reactor)
+
+__all__=["Pool", "startPool", "dbPool", "taskPool", "pools"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/controller/task.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,108 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from functools import partial
+
+from twisted.python import log
+from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred, Deferred
+
+from ..error import NoRouteForTask, RejectRecipient
+
+from ..model.offer import offers
+from ..model.job import exJobs
+
+from .pool  import taskPool
+
+class Task:
+    '''A Task is one message to one recipient and is a part of one :class:`iro.model.job.ExJob`.
+    '''
+    def __init__(self, recipient, job):
+        """
+        :param recipient: a recipient
+        :param `iro.model.job.ExJob` job: connected job
+        """
+        self.recipient = recipient
+        self.job = job
+        self.status = None
+        self.error = False
+
+    def setStatus(self,status):
+        """callback, to set status of task"""
+        self.status = status
+        return status
+
+    def setError(self, error):
+        """errback to set error of task"""
+        self.status = error
+        self.error = True
+        return error
+
+    def start(self):
+        """Starting to send message to recipient.
+
+        :return: a defer, that is fired, when message is sended successfully over any offer.
+        """
+        self.d = Deferred()
+        self.d.addCallback(self.setStatus)
+        self.d.addCallback(partial(self.job.setStatus,self))
+        self.d.addErrback(self.setError)
+        self.d.addErrback(partial(self.job.setError,self))
+        taskPool.run(self._run)
+        return self.d
+
+    def _run(self):
+        """sends the message to recipient, tries all possible offers."""
+        os= iter(self.job.offers)
+        def n():
+            try:
+                offer = os.next()
+                d = maybeDeferred(offers[offer],self.recipient,self.job.message)
+                d.addCallback(self.d.callback)
+                d.addErrback(addErr,offer)
+                d.addErrback(self.d.errback)
+                return d
+            except StopIteration:
+                self.d.errback(NoRouteForTask())
+
+        def addErr(failure, offer):
+            if not isinstance(failure.value, RejectRecipient):  
+                log.err(_why="Job(%s): Send to '%s' failed via '%s'"%(self.job.dbjob, self.recipient, offer),_stuff=failure)
+            n()
+        
+        n()
+
+
+@inlineCallbacks
+def createJob(user,recipients, msg, offers, info=None):
+    """Creates a :class:`iro.model.job.ExJob` and start for all recipients one task.
+    
+    :param `iro.model.schema.User` user: the sender 
+    :param `iro.model.message.Message` msg: the message
+    :param list offers: a list of possible offer and provider names, to try to send the message over. The first entry will be tried first.
+    :param string info: a bill group name
+    :return: the new :class:`iro.model.job.ExJob` object.
+    """
+    job = yield exJobs.create(user, recipients, msg, offers, info)
+    for r in recipients:
+        task = Task(r,job)
+        job.addTask(task)
+        task.start()
+    returnValue(job)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/controller/viewinterface.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,223 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*-
+from ..model.decorators import vUser, vRoute, dbdefer, vTyp
+from ..model.message import SMS, Fax, Mail
+
+from ..validate import validate, vBool, vTel, vEmail, vInteger
+
+from .task import createJob
+
+class Interface(object):
+    '''Interface for views.'''
+    
+    @validate(kwd="detailed", func=vBool, need=False)
+    @validate(kwd="id", func=vInteger, minv=0, need=False, none_allowed=True)
+    @vUser
+    @dbdefer
+    def status(self, session, user, id=None, detailed=False):
+        '''Returns the status of one or more jobs.
+
+        :param string user: apikey of a user
+        :param integer id: one job id
+        :param `boolean` detailed: return more details about the status
+
+        :return dict: 
+           - `key` -- is the job id
+           - [`key`][**'status'**] -- status of the job
+
+        .. warning:: detailed is not used yet.
+        
+        >>> status(APIKEY)
+        {"1":  {"status":"sended"},
+         "2":  {"status":"error"},
+         "10": {"status":"sending"}}
+
+        >>> status(APIKEY,10)
+        {"10": {"status":"sending"}}
+        '''
+        user = session.merge(user)
+        ret={}
+        if not id:
+            for job in user.jobs:
+                ret[str(job.id)]={"status":job.status}
+        else:
+            ret[str(id)]={"status":user.job(id).status}
+        
+        return ret
+   
+    @validate(kwd="recipients",func=vTel)
+    @vUser
+    @validate(kwd="route", func=vRoute, typ="sms")
+    def sms(self, user, message, recipients, route="default", info=""):
+        '''Send a sms.
+
+        :param string user: apikey of a user
+        :param string message: message
+        :param list recipients:  a list of telefon numbers (use ITU-T E.123)
+        :param route: route to use to send, or a list of routes as fallback
+        :type route: string|list
+        :param string info: a name, to combine different jobs to one billing group
+
+        :return integer: the job id
+        '''
+        d = createJob(user, recipients, SMS(message), route, info)
+        def ret(job):
+            return job.dbjob
+        d.addCallback(ret)
+        return d
+   
+    @validate(kwd="recipients",func=vTel)
+    @vUser
+    @validate(kwd="route",func=vRoute, typ="fax")
+    def fax(self, user, subject, fax, recipients, route="default", info=""):
+        '''Send a fax.
+
+        :param string user: apikey of a user
+        :param string subject: subject
+        :param string fax: content (base64 encoded)
+        :param list recipients:  a list of telefon numbers (use ITU-T E.123)
+        :param route: route to use to send, or a list of routes as fallback
+        :type route: string|list
+        :param string info: a name, to combine different jobs to one billing group
+
+        :return integer: the job id
+        '''
+        d = createJob(user, recipients, Fax(subject, fax), route, info)
+        def ret(job):
+            return job.dbjob
+        d.addCallback(ret)
+        return d
+
+    @validate(kwd="recipients",func=vEmail, allowString=False)
+    @validate(kwd="frm",func=vEmail, need=False, allowList=False)
+    @vUser
+    @validate(kwd="route",func=vRoute, typ="mail")
+    def mail(self, user, subject, body, recipients, frm=None, route="default", info=""):
+        '''Send a mail.
+
+        :param string user: apikey of a user
+        :param string subject: subject
+        :param string body: mail body
+        :param list recipients:  a list of email addresses
+        :param route: route to use to send, or a list of routes as fallback
+        :type route: string|list
+        :param string info: a name, to combine different jobs to one billing group
+        :param string frm: sender mail address
+        :return integer: the job id
+        '''
+        d = createJob(user, recipients, Mail(subject, body, frm), route, info)
+        def ret(job):
+            return job.dbjob
+        d.addCallback(ret)
+        return d
+       
+    @validate(kwd="typ", func=vTyp)
+    @vUser
+    @dbdefer
+    def routes(self, session, user, typ):
+        '''Returns a list of all possible offernames.
+
+        :param string user: apikey of a user
+        :param string typ: a typ of message -- one of in this list ["sms","fax","mail"]
+
+        :return list: a list of all possible offer names for a typ
+        '''
+        user = session.merge(user)
+        offers = user.routes(typ)
+        return [u[0] for u in offers]
+        
+    @validate(kwd="typ", func=vTyp)
+    @vUser
+    @dbdefer
+    def defaultRoute(self, session, user, typ):
+        '''Returns all default offernames.
+ 
+        :param string user: apikey of a user
+        :param string typ: a typ of message -- one of in this list ["sms","fax","mail"]
+
+        :return list: a list of all possible offer names for a typ
+        '''
+        user = session.merge(user)
+        offers = user.routes(typ, default=True)
+        return [u[0] for u in offers]
+
+    @vUser
+    @dbdefer
+    def bill(self, session, user):
+        '''Returns the bill, of not paid messages.
+
+        :param string user: apikey of a user
+
+        :return dict:
+            - `route` -- one offer name ; **"total"** complete sum
+            - [`route`][`info`][**anz**] -- Number of sended messages in one billing group
+            - [`route`][`info`][**price**] -- Price for one billing group
+            - [`route` | **total**][**anz**] -- Number of sended messages for one offer
+            - [`route` | **total**][**price**] -- Price for one offer
+
+        >>> bill(APIKEY)
+        {"route1": {"info1":{"anz":1,"price":2.00},
+                    "info2":{"anz":2,"price":5.00},
+                    "anz":3,"price":7.00},
+        "route2":  {"info1":{"anz":3, "price":1.00},
+                    "info3":{"anz":4, "price":8.00},
+                    "anz":7, "price":9.00},
+        "total":   {"anz":10, "price":16.00}
+        }
+
+        '''
+        ret={'total':{'price':0, 'anz':0}}
+        user=session.merge(user)
+        for route in user.rights:
+            n=route.offer_name
+            ret[n]={'price':0, 'anz':0, 'info':{}}
+            for bill in route.bill:
+                ret[n]['info'][bill.info]={'price':float(bill.price),'anz':bill.anz}
+                ret[n]['price'] += bill.price
+                ret[n]['anz'] += bill.anz
+            ret['total']['price'] += ret[n]['price'] 
+            ret['total']['anz'] += ret[n]['anz'] 
+            ret[n]['price'] = float(ret[n]['price'])
+
+        ret['total']['price'] = float(ret['total']['price'])
+        return ret
+
+    @validate(kwd="recipients",func=vTel)
+    def telnumber(self,recipients):
+        '''Return True, if all telnumbers a vaild.
+        
+        :param list recipients:  a list of telnumbers (use ITU-T E.123)
+
+        :return boolean: True -- all numbers are valid
+        '''
+        return True
+
+    @validate(kwd="recipients",func=vEmail)
+    def email(self,recipients):
+        '''Return True, if all mailadresses a valid.
+        
+        :param list recipients:  a list of mailadresses
+
+        :return boolean: True -- all addresses are valid
+        '''
+        return True
--- a/iro/database.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import MySQLdb
-import sqlite3
-class Database(object):
-    def __init__(self,connection):
-        self.conn=None
-        self.cursor=None
-        self.connection=connection
-        self.testConnection()
-
-    def testConnection(self):
-        self.connect()
-        self.disconnect()
-
-    def getConn(self):
-        return self.conn
-
-    def getCursor(self):
-        return self.cursor
-
-    def connect(self):
-        if not self.getConn():
-            if (self.connection['type']=='mysql'):
-                self.conn=MySQLdb.connect(
-                    host   = self.connection["host"],
-                    db     = self.connection["db"],
-                    user   = self.connection["user"],
-                    passwd = self.connection["passwd"],
-                    )
-            if (self.connection['type']=='sqlite'):
-                self.conn=sqlite3.connect(self.connection['path'])
-
-        if not self.getCursor():
-            self.cursor = self.conn.cursor()
-
-    def disconnect(self):
-        if self.getCursor():
-            self.cursor.close()
-        self.cursor=None
-        if self.getConn():
-            self.conn.close()
-        self.conn=None
-
-
--- a/iro/dump_test_log.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-import time, os, signal
-LOG_FILE = 'test.log'
-
-log_file = open(LOG_FILE, 'a')
-
-def log(msg):
-    log_file.write(msg + '\n')
-    log_file.flush()
-
-def SigUSR1Handler(signum, frame):
-    print "Reacting on USR1 signal (signal 10)"
-    global log_file
-    log_file.close()
-    log_file = open(LOG_FILE, 'a')
-    return
-
-def init():
-    if os.path.isfile('/var/usr/dump_test_log.pid'):
-        print 'Have to stop server first'
-        os.exit(1)
-    else:
-        print 'Starting server...'
-        #write process id file
-        f = open('/var/run/dump_test_log.pid', 'w')
-        f.write(str(os.getpid()))
-        f.flush()
-        f.close()
-        print 'Process start with pid ', os.getpid()
-
-    signal.signal(signal.SIGUSR1, SigUSR1Handler)
-
-def main():
-    init()
-    count = 1
-    while True:
-      log('log line #%d, pid: %d' % (count, os.getpid()))
-    count = count + 1
-    time.sleep(1)
-
-if __name__ == '__main__':
-    main()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/error.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,146 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*-
+class InterfaceException(Exception):
+    """Exception, that should be reported to external client
+    
+    999 -- unknown error
+    """
+    def __init__(self, code=999, msg="Unknown error."):
+        """
+        :param integer code: the error code
+        :param string msg: the error message
+        """
+        self.code=code
+        self.msg=msg
+
+    def dict(self):
+        """dict representation of the error"""
+        return {"code":self.code,
+                "msg":self.msg,
+                }
+
+    def __str__(self):
+        return "%i: %s"%(self.code,self.msg)
+
+class UserNotFound(InterfaceException):
+    """ 901 -- apikey is unknown -- user not found""" 
+    def __init__(self):
+        InterfaceException.__init__(self, 901, "Apikey is unknown.")
+
+class JobNotFound(InterfaceException):
+    """902 -- jobid is unknown"""
+    def __init__(self):
+        InterfaceException.__init__(self, 902, "Jobid is unknown.")
+
+class ExternalException(InterfaceException):
+    """950 -- error in external api"""
+    def __init__(self):
+        InterfaceException.__init__(self, 950, "Error in external API.")
+
+class ValidateException(Exception):
+    """700 -- validation failed."""
+    def __init__(self, code=700, field=None, msg=None):
+        """
+        :param integer code: the error code
+        :param string field: the field, that is not valid 
+        :param string msg: the error message
+        """
+        self.code=code
+        self.field=field
+        self.msg = msg
+        if not msg:
+            self.msg='Validation failed.'
+        if field and not msg:
+            self.msg="Validation of '%s' failed."%field
+
+    def dict(self):
+        """dict representation of the error"""
+        return {"code":self.code,
+                "msg":self.msg,
+                "field":self.field,
+                }
+    def __str__(self):
+        return "%i: %s"%(self.code,self.msg)
+
+class InvalidTel(ValidateException):
+    """701 -- invalid telnumber"""
+    def __init__(self, number,field=None):
+        self.number = number
+        msg = "No valid telnumber: '%s'"%(number)
+        ValidateException.__init__(self, 701, field, msg)
+    
+class InvalidMail(ValidateException):
+    """702 -- invalid mailaddress"""
+    def __init__(self, number,field=None):
+        self.number = number
+        msg = "No valid email: '%s'"%(number)
+        ValidateException.__init__(self, 702, field, msg)
+
+class OfferException(Exception):
+    """an Exception in Offer handling"""
+    def __init__(self, name):
+        self.name = name
+
+class NoRoute(OfferException):
+    """no valid route found"""
+    def __str__(self):
+        return "Not a valid route: %s"%self.name
+
+class NoProvider(OfferException):
+    """no provider found"""
+    def __str__(self):
+        return "Not a valid provider: %s"%self.name
+
+class NoTyp(OfferException):
+    """no typ found."""
+    def __str__(self):
+        return "Not a valid Typ: %s"%self.name
+
+class RejectRecipient(Exception):
+    """can't handle the recipient in a route"""
+    def __init__(self,recipient, status=None):
+        self.recipient = recipient
+        self.status = status
+
+    def __str__(self):
+        return "Reject recipient(%s): %s"%(str(self.recipient),str(self.status))
+
+class ConfigException(Exception):
+    """Exception while loading configuration."""
+    def __init__(self,section, name):
+        self.section = section
+        self.name = name
+
+class UnknownOption(ConfigException):
+    """Option is unknown"""
+    def __str__(self):
+        return "Unknown option '%s' in section '%s'."%(self.name, self.section)
+
+class NeededOption(ConfigException):
+    """Option is missing, but needed."""
+    def __str__(self):
+        return "Option '%s' in section '%s' is missing."%(self.name, self.section)
+
+class NoRouteForTask(Exception):
+    """Can't send message to recipient with given offers"""
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/inspect_getcallargs.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,116 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from inspect import getargspec, ismethod
+import sys
+
+#code just copied form python 2.7.3 
+
+def getcallargs(func, *positional, **named):
+    """Get the mapping of arguments to values.
+
+    A dict is returned, with keys the function argument names (including the
+    names of the * and ** arguments, if any), and values the respective bound
+    values from 'positional' and 'named'."""
+    args, varargs, varkw, defaults = getargspec(func)
+    f_name = func.__name__
+    arg2value = {}
+
+    # The following closures are basically because of tuple parameter unpacking.
+    assigned_tuple_params = []
+    def assign(arg, value):
+        if isinstance(arg, str):
+            arg2value[arg] = value
+        else:
+            assigned_tuple_params.append(arg)
+            value = iter(value)
+            for i, subarg in enumerate(arg):
+                try:
+                    subvalue = next(value)
+                except StopIteration:
+                    raise ValueError('need more than %d %s to unpack' %
+                                     (i, 'values' if i > 1 else 'value'))
+                assign(subarg,subvalue)
+            try:
+                next(value)
+            except StopIteration:
+                pass
+            else:
+                raise ValueError('too many values to unpack')
+    def is_assigned(arg):
+        if isinstance(arg,str):
+            return arg in arg2value
+        return arg in assigned_tuple_params
+    if ismethod(func) and func.im_self is not None:
+        # implicit 'self' (or 'cls' for classmethods) argument
+        positional = (func.im_self,) + positional
+    num_pos = len(positional)
+    num_total = num_pos + len(named)
+    num_args = len(args)
+    num_defaults = len(defaults) if defaults else 0
+    for arg, value in zip(args, positional):
+        assign(arg, value)
+    if varargs:
+        if num_pos > num_args:
+            assign(varargs, positional[-(num_pos-num_args):])
+        else:
+            assign(varargs, ())
+    elif 0 < num_args < num_pos:
+        raise TypeError('%s() takes %s %d %s (%d given)' % (
+            f_name, 'at most' if defaults else 'exactly', num_args,
+            'arguments' if num_args > 1 else 'argument', num_total))
+    elif num_args == 0 and num_total:
+        if varkw:
+            if num_pos:
+                # XXX: We should use num_pos, but Python also uses num_total:
+                raise TypeError('%s() takes exactly 0 arguments '
+                                '(%d given)' % (f_name, num_total))
+        else:
+            raise TypeError('%s() takes no arguments (%d given)' %
+                            (f_name, num_total))
+    for arg in args:
+        if isinstance(arg, str) and arg in named:
+            if is_assigned(arg):
+                raise TypeError("%s() got multiple values for keyword "
+                                "argument '%s'" % (f_name, arg))
+            else:
+                assign(arg, named.pop(arg))
+    if defaults:    # fill in any missing values with the defaults
+        for arg, value in zip(args[-num_defaults:], defaults):
+            if not is_assigned(arg):
+                assign(arg, value)
+    if varkw:
+        assign(varkw, named)
+    elif named:
+        unexpected = next(iter(named))
+        if isinstance(unexpected, unicode):
+            unexpected = unexpected.encode(sys.getdefaultencoding(), 'replace')
+        raise TypeError("%s() got an unexpected keyword argument '%s'" %
+                        (f_name, unexpected))
+    unassigned = num_args - len([arg for arg in args if is_assigned(arg)])
+    if unassigned:
+        num_required = num_args - num_defaults
+        raise TypeError('%s() takes %s %d %s (%d given)' % (
+            f_name, 'at least' if defaults else 'exactly', num_required,
+            'arguments' if num_required > 1 else 'argument', num_total))
+    return arg2value
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/install.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,123 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- test-case-name: iro.tests.test_install -*-
+
+from twisted.python import log
+import logging
+from sqlalchemy import create_engine
+from sqlalchemy.exc import DatabaseError
+from sets import Set
+import os
+
+from .config import configParser, confFiles, main
+from .error import NeededOption, ValidateException
+from .offer.provider import providers, getProvider
+
+from .model.schema import Base, Offer
+from .model.utils import WithSession
+
+from . import config
+
+def checkConfig():
+    """check configuration file syntax.
+
+    :return: boolean value.
+    """
+            
+    try:
+        l = configParser.read(confFiles)
+        if len(l) > 0:
+            return True
+        return False
+    except (NeededOption,ValidateException) as e:
+        log.msg("Error while processing config file: %s"%e,logLevel=logging.ERROR)
+        return False
+
+def checkDatabase():
+    """Checks, if all tables are created.
+
+    :return: boolean value
+    """
+    engine = create_engine(config.main.dburl)
+    for t in Base.metadata.sorted_tables:
+        if not t.exists(engine):
+            return False
+    return True
+
+def checkDatabaseConnection():
+    """Checks, if database can be connected.
+
+    :return: boolean value
+    """
+    try:
+        engine = create_engine(config.main.dburl)
+        con = engine.connect()
+        con.close()
+        return True
+    except DatabaseError as e:
+        log.msg("Error while trying to connect to database\n%s"%e,logLevel=logging.ERROR)
+        return False
+
+def createDatabase():
+    """Create all database tables or only missing."""
+    engine = create_engine(config.main.dburl)
+    Base.metadata.create_all(engine)
+
+def createSampleConfig():
+    """create a sample configuration file 'iro.conf' with all possible provider sections."""
+    if not os.path.exists("iro.conf"):
+        with open("iro.conf",'w') as fp:
+            fp.write("\n".join(main.sampleConf()))
+            fp.write("\n")
+            k = providers.keys()
+            k.sort()
+            for p in k:
+                fp.write("\n".join(providers[p](p).sampleConf()))
+                fp.write("\n")
+    else:
+        log.msg("iro.conf exists and will not be overwritten.")
+
+def getAllRoutes(providers,write=False):
+    """Checks and update offer list.
+
+    :param boolean write: check or update list
+    :return dict:
+        - **"orphand"** (Set) -- a set of orphand offers
+        - **"added"** (Set) -- a set of new offers. The new name have a schema provider_typ_route
+                
+    """
+    engine = create_engine(config.main.dburl)
+    ret={"orphand":Set(),"added":Set()}
+    with WithSession(engine,write) as session:
+        ret["orphand"]=Set([i[0] for i in session.query(Offer.name).all()])
+        for provider in providers:
+            p=getProvider(provider,configParser.get(provider,"typ"),configParser.items(provider))
+            for t in p.typs:
+                for r in p.typs[t]:
+                    try:
+                        ret["orphand"].remove(Offer.get(session, provider, r, t).name)
+                    except:
+                        if write:
+                            session.add(Offer(provider=provider,route=r,typ=t,name='%s_%s_%s'%(provider,t,r)))
+                        ret["added"].add("%s_%s_%s"%(provider,t,r))
+    return ret
+
--- a/iro/iro.api	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-iro.anbieter.FAX_de.FAX_de.createMailaddress?4(number)
-iro.anbieter.FAX_de.FAX_de.default_conf?7
-iro.anbieter.FAX_de.FAX_de.max_recipients?7
-iro.anbieter.FAX_de.FAX_de.section?7
-iro.anbieter.FAX_de.FAX_de?1()
-iro.anbieter.anbieter.anbieter.default_conf?7
-iro.anbieter.anbieter.anbieter.section?7
-iro.anbieter.anbieter.anbieter.sendFAX?4(fax, recipients)
-iro.anbieter.anbieter.anbieter.sendMail?4(mail, recipients)
-iro.anbieter.anbieter.anbieter.sendSMS?4(sms, recipients)
-iro.anbieter.geonet.Mail?1(send_from, header, cont, attachments)
-iro.anbieter.geonet.geonet.createMailaddress?4(number)
-iro.anbieter.geonet.geonet.default_conf?7
-iro.anbieter.geonet.geonet.max_recipients?7
-iro.anbieter.geonet.geonet.section?7
-iro.anbieter.geonet.geonet.sendFAX?4(fax, recipients)
-iro.anbieter.geonet.geonet.sendSMS?4(sms, recipients)
-iro.anbieter.geonet.geonet?1()
-iro.anbieter.sipgate.sipgate.BalanceGet?4()
-iro.anbieter.sipgate.sipgate.__connect?6()
-iro.anbieter.sipgate.sipgate.__disconnect?6()
-iro.anbieter.sipgate.sipgate.__send?6(args, recipients)
-iro.anbieter.sipgate.sipgate.__send_method?6(func, args=None)
-iro.anbieter.sipgate.sipgate.getNewMessages?4()
-iro.anbieter.sipgate.sipgate.getRecommendedInterval?4(methods)
-iro.anbieter.sipgate.sipgate.read_basic_config?4(filename)
-iro.anbieter.sipgate.sipgate.section?7
-iro.anbieter.sipgate.sipgate.sendFAX?4(fax, recipients)
-iro.anbieter.sipgate.sipgate.sendSMS?4(sms, recipients)
-iro.anbieter.sipgate.sipgate.updateStatus?4(arranged=None, failed=None)
-iro.anbieter.sipgate.sipgate.url?7
-iro.anbieter.sipgate.sipgate?1(user="", password="")
-iro.anbieter.smstrade.InternetConnectionError.__str__?6()
-iro.anbieter.smstrade.InternetConnectionError?1(url)
-iro.anbieter.smstrade.__send?6(self, key, route, to, message, from_=None, timestamp=None)
-iro.anbieter.smstrade.sendSMS?4(self, sms, recipients)
-iro.anbieter.smstrade.smstrade.read_basic_config?4(filename)
-iro.anbieter.smstrade.smstrade.section?7
-iro.anbieter.smstrade.smstrade.url?7
-iro.anbieter.smstrade.smstrade?1()
-iro.anbieter.smstrade.updateStatus?4(self, arranged=None, failed=None)
-iro.anbieter.smtp.SMTP.prepareSMTP?4()
-iro.anbieter.smtp.SMTP.read_basic_config?4(config_filename=None)
-iro.anbieter.smtp.SMTP.sendMail?4(mail, recipients)
-iro.anbieter.smtp.SMTP.shutdownSMTP?4()
-iro.anbieter.smtp.SMTP.updateStatus?4(arranged=None, failed=None)
-iro.anbieter.smtp.SMTP?1(config_filename=None, section="smtp")
-iro.anbieter.smtp.anbieter.default_conf?7
-iro.anbieter.telnumber.NotATelNumber.__str__?6()
-iro.anbieter.telnumber.NotATelNumber?1(number)
-iro.anbieter.telnumber.anbieter.default_conf?7
-iro.anbieter.telnumber.telnumber.__eq__?6(y)
-iro.anbieter.telnumber.telnumber.__ne__?6(y)
-iro.anbieter.telnumber.telnumber.createNumber?4(number)
-iro.anbieter.telnumber.telnumber.re_land?7
-iro.anbieter.telnumber.telnumber.re_number?7
-iro.anbieter.telnumber.telnumber.re_telnum?7
-iro.anbieter.telnumber.telnumber.std_land?7
-iro.anbieter.telnumber.telnumber?1(number=None)
-iro.anbieter.test.TestTelnumber.equalNumber?4(tel1, tel2)
-iro.anbieter.test.TestTelnumber.testEqual?4()
-iro.anbieter.test.TestTelnumber.testNumber?4()
-iro.anbieter.test.TestTelnumber.testWrongNumber?4()
-iro.anbieter.test.anbieter.default_conf?7
-iro.content.FAX.sendto?4(anbieter, recipients)
-iro.content.FAX?1(header, cont, attachments)
-iro.content.Mail.as_string?4()
-iro.content.Mail.sendto?4(anbieter, recipients)
-iro.content.Mail?1(subject, body)
-iro.content.SMS.sendto?4(anbieter, recipients)
-iro.content.SMS?1(cont)
-iro.content.content.sendto?4(anbieter, recipients)
-iro.content.content?1(content)
-iro.iro.MyManager.certificate?7
-iro.iro.MyManager.userdb?7
-iro.iro.MySMTP.setJob?4(job)
-iro.iro.MySMTP.updateStatus?4(arranged=None, failed=None)
-iro.iro.MySMTP?1(config_filename=None, section="smtp")
-iro.iro.MySipgate.setJob?4(job)
-iro.iro.MySipgate.updateStatus?4(arranged=None, failed=None)
-iro.iro.MySipgate?1(user="", password="")
-iro.iro.MySmstrade.setJob?4(job)
-iro.iro.MySmstrade.updateStatus?4(arranged=None, failed=None)
-iro.iro.MySmstrade?1()
-iro.iro.MyUserDB.createUser?4(user)
-iro.iro.MyUserDB?1(userlist, jobqueue)
-iro.iro.start?4(userlist)
-iro.job.Job.addFailed?4(failed)
-iro.job.Job.addGood?4(good)
-iro.job.Job.getName?4()
-iro.job.Job.getProvider?4()
-iro.job.Job.getStatus?4(detailed)
-iro.job.Job.start?4()
-iro.job.Job.stop?4()
-iro.job.Job?1(provider, name)
-iro.job.MessageJob.getMessage?4()
-iro.job.MessageJob.getRecipients?4()
-iro.job.MessageJob.start?4()
-iro.job.MessageJob.stop?4()
-iro.job.MessageJob?1(provider, name, message, recipients)
-iro.joblist.Joblist.__getitem__?6(key)
-iro.joblist.Joblist.__registerJob__?6(job)
-iro.joblist.Joblist._createID?5()
-iro.joblist.Joblist.newFAX?4(subject, fax, recipients)
-iro.joblist.Joblist.newMail?4(subject, body, recipients)
-iro.joblist.Joblist.newSMS?4(message, recipients)
-iro.joblist.Joblist?1(manager, queue, providerlist)
-iro.providerlist.Providerlist.add?4(name, provider, typeslist)
-iro.providerlist.Providerlist.getDefault?4(stype)
-iro.providerlist.Providerlist.getProvider?4(stype, name="default")
-iro.providerlist.Providerlist.getProviderlist?4(stype)
-iro.providerlist.Providerlist.setDefault?4(stype, name)
-iro.providerlist.Providerlist?1()
-iro.test.MyManager.certificate?7
-iro.test.MyManager.userdb?7
-iro.test.StoppableXMLRPCServer.run?4()
-iro.test.StoppableXMLRPCServer.running?7
-iro.test.StoppableXMLRPCServer.stop?4()
-iro.test.StoppableXMLRPCServer?1(*args, **kwargs)
-iro.test.TestServer.setUp?4()
-iro.test.TestServer.tearDown?4()
-iro.test.TestServer.testGetDefault?4()
-iro.test.TestServer.testGetProvider?4()
-iro.test.TestServer.testLogin?4()
-iro.test.TestServer.testTwoUser?4()
-iro.test.TestServer.testsendSMS?4()
-iro.test.init_server?4()
-iro.user.Admin?1(jobqueue)
-iro.user.NotSupportedFeature.__str__?6()
-iro.user.NotSupportedFeature?1(name)
-iro.user.User.getDefaultProvider?4(name)
-iro.user.User.getProvider?4(name)
-iro.user.User.startFAX?4()
-iro.user.User.startMail?4(subject, body, recipients)
-iro.user.User.startSMS?4(message, recipients)
-iro.user.User.status?4(id=None, detailed=False)
-iro.user.User.stop?4(id)
-iro.user.User?1(jobqueue)
-iro.worker.Worker.run?4()
-iro.worker.Worker?1(queue)
-iro.xmlrpc.AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.do_POST?4()
-iro.xmlrpc.AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.report_401?4()
-iro.xmlrpc.AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.report_error?4(code)
-iro.xmlrpc.AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.testUser?4(username, password)
-iro.xmlrpc.AuthentificateXMLRPCServer.test?4()
-iro.xmlrpc.SecureAuthentificateXMLRPCServer.SecureAuthentificateXMLRPCRequestHandler.do_POST?4()
-iro.xmlrpc.SecureAuthentificateXMLRPCServer.test?4()
-iro.xmlrpc.SecureUserdbXMLRPCServer.SecureAuthentificateXMLRPCRequestHandler2.testUser?4(username, password)
-iro.xmlrpc.SecureUserdbXMLRPCServer.SecureUserDBXMLRPCServer.activateUser?4(username, password)
-iro.xmlrpc.SecureUserdbXMLRPCServer.SecureUserDBXMLRPCServer?1(addr, userdb, requestHandler=SecureAuthentificateXMLRPCRequestHandler2, certificate="server.cert", privatekey="server.pem", logRequests=1)
-iro.xmlrpc.SecureUserdbXMLRPCServer.UserDB.__getitem__?6(key)
-iro.xmlrpc.SecureUserdbXMLRPCServer.UserDB.createHash?4(user)
-iro.xmlrpc.SecureUserdbXMLRPCServer.UserDB.createUser?4(user)
-iro.xmlrpc.SecureUserdbXMLRPCServer.UserDB?1(userClass, userlist, jobqueue)
-iro.xmlrpc.SecureXMLRPCServer.SSLWrapper.__getattr__?6(name)
-iro.xmlrpc.SecureXMLRPCServer.SSLWrapper.__setattr__?6(name, value)
-iro.xmlrpc.SecureXMLRPCServer.SSLWrapper.accept?4()
-iro.xmlrpc.SecureXMLRPCServer.SSLWrapper.shutdown?4(how=1)
-iro.xmlrpc.SecureXMLRPCServer.SSLWrapper?1(conn)
-iro.xmlrpc.SecureXMLRPCServer.SecureTCPServer?1(server_address, RequestHandlerClass, certificate, privatekey)
-iro.xmlrpc.SecureXMLRPCServer.SecureXMLRPCRequestHandler.setup?4()
-iro.xmlrpc.SecureXMLRPCServer.SecureXMLRPCServer?1(addr, requestHandler=SecureXMLRPCRequestHandler, certificate="server.cert", privatekey="server.pem", logRequests=1)
-iro.xmlrpc.SecureXMLRPCServer.test?4()
\ No newline at end of file
--- a/iro/iro.e4p	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,225 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Project SYSTEM "Project-4.6.dtd">
-<!-- eric4 project file for project iro -->
-<!-- Saved: 2009-11-23, 00:15:34 -->
-<!-- Copyright (C) 2009 Sandro Knauß, bugs@sandroknauss.de -->
-<Project version="4.6">
-  <Language></Language>
-  <ProgLanguage mixed="0">Python</ProgLanguage>
-  <ProjectType>Console</ProjectType>
-  <Description>Ein Daemon zum Senden von Massensms, -faxen und emails</Description>
-  <Version>0.1</Version>
-  <Author>Sandro Knauß</Author>
-  <Email>bugs@sandroknauss.de</Email>
-  <Sources>
-    <Source>iro.py</Source>
-    <Source>__init__.py</Source>
-    <Source>xmlrpc/SecureXMLRPCServer.py</Source>
-    <Source>xmlrpc/__init__.py</Source>
-    <Source>xmlrpc/SecureAuthentificateXMLRPCServer.py</Source>
-    <Source>xmlrpc/AuthentificateXMLRPCServer.py</Source>
-    <Source>xmlrpc/SecureUserdbXMLRPCServer.py</Source>
-    <Source>worker.py</Source>
-    <Source>user.py</Source>
-    <Source>test.py</Source>
-    <Source>providerlist.py</Source>
-    <Source>joblist.py</Source>
-    <Source>job.py</Source>
-    <Source>anbieter/smtp.py</Source>
-    <Source>anbieter/geonet.py</Source>
-    <Source>anbieter/content.py</Source>
-    <Source>anbieter/__init__.py</Source>
-    <Source>anbieter/telnumber.py</Source>
-    <Source>anbieter/test.py</Source>
-    <Source>anbieter/FAX_de.py</Source>
-    <Source>anbieter/sipgate.py</Source>
-    <Source>anbieter/gsm0338.py</Source>
-    <Source>anbieter/anbieter.py</Source>
-    <Source>anbieter/smstrade.py</Source>
-  </Sources>
-  <Forms>
-  </Forms>
-  <Translations>
-  </Translations>
-  <Resources>
-  </Resources>
-  <Interfaces>
-  </Interfaces>
-  <Others>
-    <Other>iro.conf.inst</Other>
-    <Other>iro.conf</Other>
-  </Others>
-  <MainScript>iro.py</MainScript>
-  <Vcs>
-    <VcsType>None</VcsType>
-    <VcsOptions>
-      <dict>
-        <key>
-          <string>add</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>checkout</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>commit</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>diff</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>export</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>global</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>history</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>log</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>remove</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>status</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>tag</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-        <key>
-          <string>update</string>
-        </key>
-        <value>
-          <list>
-            <string></string>
-          </list>
-        </value>
-      </dict>
-    </VcsOptions>
-    <VcsOtherData>
-      <dict>
-        <key>
-          <string>standardLayout</string>
-        </key>
-        <value>
-          <bool>True</bool>
-        </value>
-      </dict>
-    </VcsOtherData>
-  </Vcs>
-  <FiletypeAssociations>
-    <FiletypeAssociation pattern="*.pyw" type="SOURCES" />
-    <FiletypeAssociation pattern="*.idl" type="INTERFACES" />
-    <FiletypeAssociation pattern="*.py" type="SOURCES" />
-    <FiletypeAssociation pattern="*.ptl" type="SOURCES" />
-  </FiletypeAssociations>
-  <Documentation>
-    <DocumentationParams>
-      <dict>
-        <key>
-          <string>ERIC4API</string>
-        </key>
-        <value>
-          <dict>
-            <key>
-              <string>basePackage</string>
-            </key>
-            <value>
-              <unicode>iro</unicode>
-            </value>
-            <key>
-              <string>ignoreFilePatterns</string>
-            </key>
-            <value>
-              <list>
-                <unicode></unicode>
-              </list>
-            </value>
-            <key>
-              <string>includePrivate</string>
-            </key>
-            <value>
-              <bool>True</bool>
-            </value>
-            <key>
-              <string>languages</string>
-            </key>
-            <value>
-              <list>
-                <unicode>Python</unicode>
-              </list>
-            </value>
-            <key>
-              <string>outputFile</string>
-            </key>
-            <value>
-              <unicode>iro.api</unicode>
-            </value>
-            <key>
-              <string>useRecursion</string>
-            </key>
-            <value>
-              <bool>True</bool>
-            </value>
-          </dict>
-        </value>
-      </dict>
-    </DocumentationParams>
-  </Documentation>
-</Project>
\ No newline at end of file
--- a/iro/iro.py	Wed Dec 21 22:07:48 2011 +0100
+++ b/iro/iro.py	Thu Sep 27 17:15:46 2012 +0200
@@ -1,177 +1,82 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-import multiprocessing, logging 
-#logging anfangen
-logger=logging.getLogger("iro")
-logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s')
-
-
-# Server code
-from xmlrpc import SecureUserDBXMLRPCServer,UserDB
-
-from user import User, Admin
-import anbieter
-import ConfigParser
-
-from job import SMSJob, FAXJob, MailJob
-from joblist import Joblist
-from providerlist import Providerlist
-from acounting import Acounting
-
-class MyUserDB(UserDB):
-    def __init__(self, userlist,jobqueue):
-        UserDB.__init__(self, None,userlist,jobqueue)
-        
-    def createUser(self, user):
-        self.userlist[self.createHash(user)]=user["class"](user["name"],self.jobqueue)
-
-
- 
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-class MySipgate(anbieter.sipgate):
-    
-    def __init__(self,user="",password="" ):
-        anbieter.sipgate.__init__(self, user, password)
-    
-    def setJob(self, job):
-        self.job=job
-    
-    def updateStatus(self, arranged=None, failed=None):
-        if arranged:
-            self.job.addGood(arranged)
-        
-        if failed:
-            self.job.addFailed(failed)
-            
-class MySMTP(anbieter.SMTP):
-    
-    def __init__(self,config_filename=None,section="smtp"):
-        anbieter.SMTP.__init__(self,config_filename,section)
-    
-    def setJob(self, job):
-        self.job=job
-    
-    def updateStatus(self, arranged=None, failed=None):
-        if arranged:
-            self.job.addGood(arranged)
-        
-        if failed:
-            self.job.addFailed(failed)
-    
-class MySmstrade(anbieter.smstrade):
-    
-    def __init__(self ):
-        anbieter.smstrade.__init__(self )
-    
-    def setJob(self, job):
-        self.job=job
-    
-    def updateStatus(self, arranged=None, failed=None):
-        if arranged:
-            self.job.addGood(arranged)
-        
-        if failed:
-            self.job.addFailed(failed)
+from twisted.application.service import Service, MultiService
+from twisted.application import internet
+from twisted.web import resource, server
+from twisted.internet import reactor
+from twisted.python import log
+
+from sqlalchemy import create_engine, pool
 
+import config, install
+from .view import xmlrpc, jsonrpc, jsonresource
+from .model import setEngine, setPool
+from .controller.pool import startPool, dbPool
+
+class IroService(Service):
+    def startService(self):
+        log.msg("Starting service...")
+        engine = create_engine(config.main.dburl,
+               poolclass = pool.SingletonThreadPool,  pool_size=dbPool.maxthreads, pool_recycle=3600)
+
+        setEngine(engine)
+        startPool(reactor)
+        setPool(dbPool)
+        reactor.callWhenRunning(config.readConfig)
+        Service.startService(self)
 
 
-def start(userlist):
-    from multiprocessing import Queue
-    from multiprocessing.managers import BaseManager
-
-    
-    class MyManager(BaseManager):
-        pass
-    
-    MyManager.register('SMSJob', SMSJob) 
-    MyManager.register('FaxJob', FAXJob) 
-    MyManager.register('MailJob',MailJob) 
-    MyManager.register('Providerlist',Providerlist) 
-    MyManager.register('Acounting',Acounting)
-    manager = MyManager()
-    manager.start()
-    
-    
-    conf=["iro.conf", "~/iro.conf","/etc/iro/iro.conf"]
+def makeService(cfg):
+    top_service = MultiService()
+    config.confFiles.insert(0, cfg["config"])
+    if not install.checkConfig():
+        log.err("You can create a sample configuration file by running iro-install")
+        raise Exception("Please update or create your configuration file %s." % cfg["config"])
+    config.init()
 
-    #anbieter erzeugen und konfigurieren
-
-    sip=MySipgate()
-    sip.read_basic_config(conf)
-    
-    localhost=MySMTP()
-    localhost.read_basic_config(conf)
-
-    smstrade=MySmstrade()
-    smstrade.read_basic_config(conf)
-
-    cp = ConfigParser.ConfigParser()
-    cp.read(conf)
-    dbconn={'type':cp.get('db', 'type'),
-            'host':cp.get('db', 'host'),
-            'db':cp.get('db', 'db'),
-            'user':cp.get('db', 'user'),
-            'passwd':cp.get('db', 'passwd'),
-            'table':cp.get('db', 'table'),
-    }
-    
+    if not install.checkDatabaseConnection():
+        raise Exception("Can't connect to database")
     
-    #Benutzerdatenbank erstellen
-    queue = Queue()
-    provider=manager.Providerlist()
-    provider.add("sipgate", sip, ["sms", "fax", ])
-    provider.add("smstrade", smstrade, ["sms", ])
-    #provider.add("geonet", None, ["sms", "fax", ])
-    #provider.add("fax.de", None, ["sms", "fax", ])
-    provider.add("localhost", localhost, ["mail", ])
-    provider.setDefault("sms","smstrade")
-    provider.setDefault("fax","sipgate")
-    provider.setDefault("mail","localhost")
-    jobqueue=Joblist(manager,  queue, provider,dbconn)
-   
-    userdb=MyUserDB(userlist,jobqueue)
+    if not install.checkDatabase():
+        raise Exception("Database not in right format. Please run iro-install --install")
 
-    #working thread erstellen
-    from worker import Worker
-    worker=Worker(queue)
-    worker.start()
+    routes = [ s for s in config.configParser.sections() if not s in ["main",]]
+    ao =  install.getAllRoutes(routes, False)
+    for o in  ao["orphand"]:
+        log.msg("Offer(%s) is orphand (no route using this offer)."%o)
+    if ao["added"]:
+        raise Exception("offerlist is not up-to-date.\nPlease run iro-install --update")
 
-    #Server starten
-    cp = ConfigParser.ConfigParser()
-    cp.read(conf)
-    cert=cp.get('server', 'cert')
-    key=cp.get('server', 'key')
-    server = SecureUserDBXMLRPCServer(addr=("localhost", 8000), 
-                                      userdb=userdb,
-                                      certificate=cert,privatekey=key)
-    server.relam="xmlrpc"
+    root = resource.Resource()
+    xmlrpc.appendResource(root)
+    jsonrpc.appendResource(root)
+    jsonresource.appendResource(root)
     
-    logger.info('Server gestartet...')
-    try:
-        server.serve_forever()
-    except KeyboardInterrupt:
-        pass
-    except:
-        logger.exception('Äh, ein Fehler ist aufgetreten')
-    finally:    
-        logger.info('Server wird beendet...')
-        queue.close()
-        worker.terminate()
+    v2 = resource.Resource()
+    xmlrpc.appendResource(v2)
+    jsonrpc.appendResource(v2)
+    jsonresource.appendResource(v2)
+    root.putChild('1.0a', v2)
 
-if __name__ == '__main__':
-    userlist=[{"name":"test","password":"test",  "class":User},
-	      {"name":"test2","password":"test2", "class": Admin}]
-    start(userlist)
-
-
+    internet.TCPServer(config.main.port, server.Site(root)).setServiceParent(top_service)
+    IroService().setServiceParent(top_service)
+    return top_service
--- a/iro/job.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-class Job(object):
-    '''
-    Basic class for all jobs
-    '''   
-    def __init__(self,providerlist,provider, name):
-        self.providerlist=providerlist
-        self.provider=provider
-        self.name=name
-        self.status = "init"
-        self.dStatus={"good":[], "failed":[]}
-        self.id=None
-        self.acounting=None
-   
-    def setAcounting(self,ac):
-        self.acounting=ac
-
-    def setId(self, id):
-        self.id=id
-        if self.acounting:
-            self.acounting.setId(id)
-
-    def start(self,indifier=None):
-        self.indifier=indifier
-        self.status = "started"
-        
-    def stop(self):
-        self.status = "stopped"
-
-    def setLog(self,log):
-        self.log=log
-        
-    def getStatus(self,detailed=False):
-        if detailed and self.status == "started" or self.status == "sended":
-            return self.status, self.dStatus
-        return self.status,{}
-    
-    def setStatus(self,status):
-        self.status=status
-
-    def getName(self):
-        return self.name
-        
-    def getProvider(self):
-        return None
-        
-    def addGood(self, good):
-        if self.acounting:
-            self.acounting.addGood(good)
-        if type(good) == list:
-            self.dStatus['good']=self.dStatus['good']+good
-        else:
-            self.dStatus['good'].append(good)
-    
-    def addFailed(self, failed):
-        if self.acounting:
-            self.acounting.addFailed(failed)
-        if type(failed) == list:
-            self.dStatus['failed']=self.dStatus['failed']+failed
-        else:
-            self.dStatus['failed'].append(failed)
-
-class MessageJob(Job):
-    '''
-    A specialized class for smsjobs
-    '''
-    def __init__(self,providerlist,provider, name, message,recipients):
-        self.message=message
-        self.recipients=recipients
-        Job.__init__(self,providerlist,provider,  name)
-        
-    def stop(self):
-        pass
-    
-    def start(self, id=None):
-        Job.start(self,id)
-        self.getProvider().setJob(self)
-        self.message.sendto(self.getProvider(), self.recipients)
-        self.status="sended"
-
-    def getMessage(self):
-        return self.message
-        
-    def getRecipients(self):
-        return self.recipients
-
-
-class SMSJob(MessageJob):
-    def __init__(self,providerlist,provider, name, message,recipients):
-        MessageJob.__init__(self,providerlist,provider, name, message,recipients)
-        
-    def getProvider(self):
-        return self.providerlist.getProvider("sms", self.provider)
-
-class FAXJob(MessageJob):
-    def __init__(self,providerlist,provider, name, message,recipients):
-        MessageJob.__init__(self,providerlist,provider, name, message,recipients)
-        
-    def getProvider(self):
-        return self.providerlist.getProvider("fax", self.provider)
-
-class MailJob(MessageJob):
-    def __init__(self,providerlist,provider, name, message,recipients):
-        MessageJob.__init__(self,providerlist,provider, name, message,recipients)
-        
-    def getProvider(self):
-        return self.providerlist.getProvider("mail", self.provider)
-
-
--- a/iro/joblist.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-from anbieter import content
-import hashlib, os,  time
-import logging
-logger=logging.getLogger("iro.joblist");
-
-class Joblist:
-    '''
-    Providing an list of jobs; each new job gets a hash id
-    '''
-    def __init__(self,manager, queue,providerlist,dbconn=None):
-        self.jobs={}
-        self.manager=manager
-        self.queue=queue
-        self.providerlist=providerlist
-        self.dbconn=dbconn
-        
-
-    def __getitem__(self,key):
-        return self.jobs[key]
-
-    def __registerJob__(self, job, user):
-        id = self._createID()
-        if self.dbconn:
-            job.setAcounting(self.manager.Acounting(id,self.dbconn))
-        job.setId(id, user)
-        self.jobs[id]=job
-        self.queue.put(job)
-        return id
-
-    def newSMS(self, message, recipients, provider="default", user=None):
-        '''
-        creates a new SMS
-        '''
-        job=self.manager.SMSJob(self.providerlist, provider,message, content.SMS(message),recipients)
-        return self.__registerJob__(job,user)
-
-    def newFAX(self,subject, fax,recipients,provider="default",user=None):
-        '''
-        creates a new Fax
-        '''
-        job=self.manager.FaxJob(self.providerlist, provider,subject, content.FAX(subject,'' ,fax),recipients)
-        return self.__registerJob__(job,user)
-
-    def newMail(self, subject, body, recipients, frm, provider="default",user=None):
-        '''
-        creates a new Mail
-        '''
-        job=self.manager.MailJob(self.providerlist, provider,subject, content.Mail(subject, body, frm),recipients)
-        return self.__registerJob__(job,user) 
-
-    def _createID(self):
-        '''
-        creats a random hash id
-        '''
-        while True:
-            m = hashlib.sha1()
-            m.update(str(time.time()))
-            m.update(os.urandom(10))
-            if not self.jobs.has_key(m.hexdigest):
-                if not self.dbconn:                    
-                    self.jobs[m.hexdigest()]=None
-                    break
-                if not self.manager.Acounting(m.hexdigest(),self.dbconn).getStatus():
-                    self.jobs[m.hexdigest()]=None
-                    break
-        return m.hexdigest()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/main.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,69 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.web import resource, server
+from twisted.internet import reactor
+from twisted.python import log
+
+from sqlalchemy import create_engine, pool
+
+from .model import setEngine, setPool
+from .controller.pool import startPool, dbPool
+from .view import xmlrpc, jsonrpc, jsonresource
+from . import config
+
+def runReactor(reactor, engine, port, root):
+    """start reactor.
+    
+    :param reactor: twisted reactor
+    :param engine: sqlalchemy engine
+    :param integer port: port to listen to
+    :param `twisted.web.resource.Resource` root: resource to share
+    """
+    setEngine(engine)
+    startPool(reactor)
+    setPool(dbPool)
+    
+    reactor.listenTCP(port, server.Site(root))
+    log.msg("Server is running now...")
+    reactor.run()
+
+
+if __name__ == '__main__':
+   
+    config.readConfig()
+
+    engine = create_engine(config.main.dburl,
+           poolclass = pool.SingletonThreadPool,  pool_size=dbPool.maxthreads, pool_recycle=3600)
+
+
+    root = resource.Resource()
+    xmlrpc.appendResource(root)
+    jsonrpc.appendResource(root)
+    jsonresource.appendResource(root)
+    
+    v2 = resource.Resource()
+    xmlrpc.appendResource(v2)
+    jsonrpc.pappendResource(v2)
+    jsonresource.pappendResource(v2)
+    root.putChild('1.0a', v2)
+
+    runReactor(reactor, engine, config.main.port, root)
--- a/iro/merlin	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# merlin commando
-#
-# eine überprüfung auf korrekten aufruf findet nicht statt
-#
-# beispiel:
-#
-#       ./merlin ./arthur
-#
-#               startet programm arthur und wenn er stirbt, wird er sofort
-#               wiederbelebt.
-#               harmlose magie halt... :-)
-#
-LOG=/home/sandy/var/log/merlin_Iro.log
-while : ; do
-	echo -n "$(date +'%F %T %Z') " >> $LOG
-	$1 status >> $LOG
-	if [ $? -eq 1 ]; then
-		echo $(date +'%F %T %Z') $1 neustarten >> $LOG
-		$1 start >> $LOG
-	fi
-	sleep 60
-done
-
--- a/iro/merlin_daemon	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-#! /bin/sh
-NAME="merlin"
-DEAMON=/home/sandy/svn/iro/$NAME
-DEAMON_OPTS="/home/sandy/svn/iro/MyIro_daemon"
-PID=/home/sandy/var/run/$NAME.pid
-
-test -x $DEAMON || exit 0
-
-. /lib/lsb/init-functions
-
-case "$1" in
-  start)
-	log_daemon_msg "Starting $NAME server" $NAME
-	if start-stop-daemon --start --quiet --oknodo --pidfile $PID --make-pidfile --background --startas $DEAMON -- $DEAMON_OPTS; then
-	    log_end_msg 0
-	else
-	    log_end_msg 1
-	fi
-	;;
-  stop)
-	log_daemon_msg "Stopping $NAME server" $NAME
-	if start-stop-daemon --stop --quiet --oknodo --pidfile $PID; then
-	    log_end_msg 0
-	else
-	    log_end_msg 1
-	fi
-	;;
-
-  restart)
-	$0 stop
-	sleep 1
-	$0 start
-	;;
-
-  status)
-	status_of_proc -p $PID $DEAMON $NAME && exit 0 || exit $?
-	;;
-
-  *)
-	log_action_msg "Usage: $0 {start|stop|restart|status}"
-	exit 1
-esac
-
-exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/__init__.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,34 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""The model pakcage represents all data, that is stored either in database or in objects.
+
+before using the model, you have to use, you have to set the Engine with :func:`dbdefer.setEngine` and set the Threadpool with :func:`dbdefer.setPool` (see :func:`iro.main.runReactor` for initalizing this module).
+"""
+
+import schema
+import user
+import utils
+import job
+import offer
+
+from dbdefer import setEngine
+from pool import setPool
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/dbdefer.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,92 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from decorator import FunctionMaker
+import sqlalchemy
+
+from .pool import runInDBPool
+from .utils import WithSession
+
+import inspect
+
+class DBDefer(object):
+    '''a twisted sqlalchemy connector.
+    
+    This is used as a Decorator class.
+    It adds a session parameter to the function with a valid session.
+    If the session parmaeter is used in calling this function only calls a commit after running the function, instead of createing a new session.'''
+    def __init__(self, engine, autocommit=False):
+        """
+        :param `sqlalchemy.engine.base.Engine` engine: a valid sqlalchemy engine object (normally created via :func:`sqlalchemy.create_engine`).
+        :param boolean autocommit: autocommit after running the function.
+        """
+        self.autocommit=autocommit
+        self.engine = engine
+
+    def __call__(self, func):
+        @runInDBPool
+        def wrapper(func,*a, **kw):
+            i = argspec.args.index("session")
+            ab = a[:i]
+            ae = a[i:-1]
+            if isinstance(a[-1],sqlalchemy.orm.session.Session):
+                al = ab + (a[-1],) + ae
+                ret = func(*al, **kw)
+                if self.autocommit:
+                    a[-1].commit()
+                return ret
+            else:
+                with WithSession(self.engine, self.autocommit) as session:
+                    al = ab + (session,) + ae
+                    return func(*al, **kw)
+
+        caller=func
+        argspec = inspect.getargspec(caller)
+        args =[i for i in argspec.args if i != "session" ]
+        sargs=", ".join(args)       
+        if sargs:
+            sargs+=", session"
+        else:
+            sargs="session"
+        defaults = argspec.defaults
+        if not defaults:
+            defaults = (None,)
+        else:
+            defaults += (None,)
+        evaldict = caller.func_globals.copy()
+        evaldict['_call_'] = func
+        evaldict['decorator'] = wrapper
+        wrap = FunctionMaker.create(
+              '%s(%s)' % (caller.__name__, sargs), 
+              'return decorator(_call_, %s)' % sargs,
+              evaldict, defaults=defaults, undecorated=caller, __wrapped__=caller,
+              doc=caller.__doc__, module=caller.__module__, addsource=True)
+        return wrap
+
+dbdefer=DBDefer(None)
+"""the decorator to use. Use :func:`setEngine` to set a valid engine after program has started."""
+
+def setEngine(engine,autocommit=False): 
+    """set the engine and autocommit for the decorator (see :class:`DBDefer`)."""
+    dbdefer.engine = engine
+    dbdefer.autocommit = autocommit
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/decorators.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,89 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""All decorators, that are created by this package.
+
+Imports:
+
+- :func:`.user.vUser` -- a validator for apikeys.
+- :func:`.pool.runInDBPool` -- runs a actual function in dbpool.
+"""
+import types
+
+from .user import vUser
+from .schema import Offer
+from .dbdefer import  dbdefer
+from .pool import runInDBPool
+
+from ..error import ValidateException
+
+@dbdefer
+def vRoute(session, value, field, typ, allowString=True, allowList=True):
+    """ a validator to test a valid route. use with :func:`iro.validate.validate`. 
+
+    :param session: a valid session object (is created by decorator :func:`iro.model.dbdefer.dbdefer`)
+    :param value: the value to test
+    :param string field: the field that is tested (only used to get a propper error message).
+    :param string typ: a typ to test the route in
+    :param boolean allowString: a single route is allowd.
+    :param boolean allowList: a list of routes is allowed.
+    :return: *value*, if value is a valid route for a given typ.
+    :raises: :exc:`iro.error.ValidateException`
+    """
+    str_ = False
+    ret = []
+
+    if type(value) is types.StringType:
+        if not allowString:
+            raise ValidateException(field=field,msg='%s must be a list of routes.'%field)
+        str_=True
+        value=[value]
+    elif not allowList:
+        raise ValidateException(field=field,msg='%s must be a route - No list of routes.'%field)
+
+    routes = [o.name for o in Offer.routes(session,typ)]
+    providers =  [o.provider for o in Offer.providers(session,typ)]
+    for v in value:
+        if v not in routes and v not in providers and v != "default":
+            raise ValidateException(field=field,msg='Route %s is not valid.'%v)
+        if v not in ret:
+            ret.append(v)
+    if str_:
+        return ret[0]
+    return ret
+
+@dbdefer
+def vTyp(value,field, session):
+    """ a validator to test a valid typ. use with :func:`iro.validate.validate`. 
+
+    :param session: a valid session object (is created by decorator :func:`iro.model.dbdefer.dbdefer`)
+    :param value: the value to test
+    :param string field: the field that is tested (only used to get a propper error message).
+    :return: *value*, if value is a valid typ.
+    :raises: :exc:`iro.error.ValidateException`
+    """
+
+    for typ in Offer.typs(session):
+        if value == typ[0]:
+            break
+    else:
+        raise ValidateException(field=field,msg='Typ %s is not valid.'%value)
+    return value
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/job.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,146 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.python import log, threadable
+
+from datetime import datetime
+from collections import MutableMapping
+
+import schema
+import offer
+from .dbdefer import dbdefer
+
+class ExJob:
+    ''' A ExJob object represents a message to multiple recipients over multiple offers to send.  
+
+    One single message to one recipient is handeld in :class:`iro.controller.task.Task`.
+    This class holds connections to all tasks.
+    This class is responsiple to update the status in database of one job and updating the bill.
+    '''
+    
+    synchronized = ["incStatus", "_status"]
+
+    def __init__(self, dbjob, recipients, message, offers):
+        """Constructor of ExJob.
+        
+        :param dbjob: primary key of the job element in database
+        :param list recipients: list of all recipients
+        :param `iro.model.message.Message` message: message to send
+        :param list offers: list of all possible offers to send message over
+        """
+
+        self.dbjob = dbjob       #Connection to database job element (id)
+        self.message = message
+        self.recipients = recipients
+        self.offers = offers
+        self.tasks={}
+        self.c = 0
+        self.status = "started"
+        log.msg("Job(%s) created."%(self.dbjob))
+
+    def addTask(self,task):
+        """adding a task to tasks dict - key is the recipient.
+        
+        :param `iro.controller.task.Task` task: a task
+        """
+        self.tasks[task.recipient] = task
+
+    def incStatus(self):
+        """increments the processed messages (function is threadsafe)."""
+        self.c += 1
+        return self.c
+
+    def _status(self, session, status):
+        """updates the status of the database object (function is threadsafe).
+
+        :param session: a valid database session
+        :param string status: new status
+        """
+        job = schema.Job.get(session, self.dbjob)
+        if self.status == "error":
+            return
+        elif self.status == "sended" and status != "error":
+            return
+        job.status = status
+        self.status = status
+        log.msg("Job(%s) status changed to: %s."%(self.dbjob, status))
+        session.commit()
+
+    @dbdefer
+    def setStatus(self, session, task, status):
+        """callback of one task.
+        
+        This function  updates the database object and the bill.
+        """
+        c = self.incStatus()
+        job = schema.Job.get(session, self.dbjob)
+        
+        if c == len(self.recipients):
+            self._status(session,"sended") 
+        elif job.status in ["started","init"]:
+            self._status(session,"sending")
+
+        if status.costs > 0:
+            o = schema.Offer.get(session, status.provider.name, status.route, self.message.typ)
+            job.messages.append(schema.Message(price=status.costs, isBilled=False, recipient=str(task.recipient), count=status.count, exID=status.exID, date=datetime.today(), offer=o))
+        session.commit()
+        
+        log.msg("Job(%s) to '%s' ended sucecessfully via %s:%s."%(self.dbjob, task.recipient, status.provider.name,status.route))
+
+    @dbdefer
+    def setError(self, session, task, err):
+        """errback for one task.
+
+        This function updates the database object.
+        """
+        self.incStatus()
+        if self.status != "error":
+            self._status(session,"error")
+        log.err(_why="Error: Job(%s) to '%s' failed."%(self.dbjob, task.recipient),_stuff=err)
+
+threadable.synchronize(ExJob)
+
+class ExJobs(dict, MutableMapping):
+    """ a dict to handle all jobs.
+    """
+    @dbdefer
+    def create(self, session, user, recipients, message, offers, info=None):
+        """creates on new Job.
+        
+        :param session: a valid session ( created by decorator :func:`iro.model.dbdefer.dbdefer`)
+        :param `iro.model.schema.User` user: a user object
+        :param list recipients: list of all recipients
+        :param `iro.model.message.Message` message: message to send
+        :param list offers: a list of offers ( list will be reduced to the allowed offers for the **user** -- using :func:`iro.model.offer.extendProvider`)
+        :param string info: a bill group name
+        :returns: the new job
+        """
+        user = session.merge(user)
+        job = schema.Job(info=info, status="started")
+        user.jobs.append(job)
+        session.commit()
+        
+        o = offer.extendProvider(user, message.typ, offers, session=session)
+        self[job.id] = ExJob(job.id, recipients, message, o)
+        return self[job.id]
+
+exJobs = ExJobs()
+"""the dict of all available jobs."""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/message.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,147 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*-
+"""All available message typs to send send.
+"""
+from email.mime.text import MIMEText
+from email.header import Header
+from email.Utils import formatdate
+
+class Message:
+    """ Baseclass for all different message typs."""
+    def __init__(self,content, typ="Message"):
+        """Constructor of Message class.
+        
+        :param content: content of the message
+        :param string typ: typ of the message
+
+        .. automethod:: __eq__
+        .. automethod:: __neq__
+        """
+        self.content=content
+        self.typ = typ
+
+
+    def getContent(self):
+        """returns the content of the message"""
+        return self.content
+
+    def __eq__(self,other):
+        """return ``True`` if **other** has the same content."""
+        return self.content == other.content
+
+    def __neq__(self,other):
+        """return ``False`` if **other** has the same content."""
+        return not self.__eq__(other)
+
+class SMS(Message):
+    """ A representation of one SMS"""
+    def __init__(self, cont, from_ = None):
+        """Constructor of SMS class.
+
+        :param string cont: SMS content
+        :param string from_: the telnumber from the SMS should be sended.
+        """
+        Message.__init__(self, cont.encode("utf-8"), typ="sms")
+        self.from_ = from_
+
+class Fax(Message):
+    """A representation of one fax."""
+    def __init__(self,header,cont,attachments=[]):
+        """Constructor of one fax.
+        
+        :param string header: Headline of fax
+        :param string cont: fax content
+        :param list attachments: attachments of fax
+        """
+        Message.__init__(self,cont.encode("utf-8"),typ="fax")
+        self.header=header
+        self.attachments=attachments
+
+    def getAttachment(self,i):
+        """returns a attachment
+        :param integer i:  the i-th attachment
+        """
+        return self.attachments[i]
+
+    def __eq__(self,other):
+        if not  Message.__eq__(self,other):
+            return False
+
+        if self.header != other.header:
+            return False
+
+        if len(self.attachments) != len(other.attachments):
+            return False
+
+        for a in self.attachments:
+            if a not in other.attachments:
+                return False
+        return True
+
+
+
+class Mail(Message):
+    """A representation of one Mail"""
+    def __init__(self, subject, body, frm):
+        """Constructor of one mail.
+
+        :param string subject: subject of the mail
+        :param string body: body of the mail
+        :param string frm: mailaddress to send mail from
+
+        .. automethod:: __repr__
+        """
+        con = MIMEText(body.encode("utf-8"), _charset='utf-8')
+        sub = Header(subject.encode('utf-8'), 'utf-8')
+        con['Subject'] = sub
+        con['Date'] = formatdate(localtime=True)
+        self.subject = subject
+        self.body = body
+        self.frm=frm
+        Message.__init__(self, con, typ='mail')
+
+    def as_string(self):
+        """returns created mail"""
+        return self.content.as_string()
+
+    def getFrom(self):
+        """returns the from mailaddress"""
+        return self.frm
+    
+    def __eq__(self,other):
+        if self.as_string() != other.as_string():
+            return False
+
+        if self.frm != other.frm:
+            return False
+        
+        return True
+
+    def __repr__(self):
+        """string representation of the class.
+
+        :returns: ``<Mail(subject, body, frm)>``
+        """
+        return "<Mail(%s, %s, %s)>"%(self.subject,self.body,self.frm)
+
+__all__=["Message", "SMS", "Fax", "Mail"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/offer.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,79 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from .dbdefer import dbdefer
+
+import schema
+from ..config import configParser
+from ..offer import getProvider, Offer
+
+@dbdefer
+def extendProvider(session, user, typ, ps):
+    """extend and reduce the offer list to allowed routes for **user**.
+
+    - extend the "default" to the default offerlist of the **user**.
+    - extend a Provider name to all routes of that provider.
+
+    :param session: a valid session ( created by decorator :func:`iro.model.dbdefer.dbdefer`)
+    :param `iro.model.schema.User` user: a user object
+    :param string typ: typ of the message
+    :param ps: a list of strings or a string, each one offer name or provider name
+    :return: a extended an reduced offer list
+    """
+    user = session.merge(user)
+    ret = []
+    if ps == "default" or ps == ["default"]:
+        ps = (q[0] for q in  user.routes(typ,default=True))
+    for p in ps:
+        if p not in ret and user.has_right(typ, offer_name = p): 
+                ret.append(p)
+        elif user.providers(typ).filter(schema.Offer.provider==p).first():
+            for r in providers[p].typs[typ]:
+                n = user.has_right(typ, provider=p, route=r)
+                if n and n not in ret:
+                    ret.append(n)
+    return ret
+
+@dbdefer
+def loadOffers(session):
+    """loading Offers from database and configuration file and store them in :attr:`~iro.model.offer.offers` and :attr:`~iro.model.offer.providers`.
+
+    :param session: a valid session ( created by decorator :func:`iro.model.dbdefer.dbdefer`)
+    """
+    offers.clear()
+    providers.clear()
+    for provider in ( s for s in configParser.sections() if not s in ["main",]):
+        p=getProvider(provider,configParser.get(provider,"typ"),configParser.items(provider))
+        for t in p.typs:
+            for r in p.typs[t]:
+                n = schema.Offer.get(session, provider, r, t).name
+                offers[n]=Offer(provider=p,route=r,typ=t,name=n)
+        providers[provider]=p
+
+offers={}
+"""A dict of all available offers -- key is the offer name"""
+
+providers={}
+"""A dict of all available providers -- key is the provider name"""
+
+
+configParser.registerReload(loadOffers)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/pool.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,47 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from decorator import decorator
+import sqlalchemy
+class Data:
+    """A very simple class to save the thread pool for database requests"""
+    def __init__(self):
+        self.pool =  None
+
+data = Data()
+"""holds connection to the actual thread pool for the database requests"""
+
+def setPool(pool): 
+    """setting the thread pool"""
+    data.pool = pool
+
+@decorator
+def runInDBPool(f,*args,**kwargs):
+    """Decorator to run DB queries in Twisted's thread pool.
+    
+    If last argument is a session object, the function is called directly.
+    """
+    if isinstance(args[-1],sqlalchemy.orm.session.Session):
+        return f(*args,**kwargs)
+    else:
+        return data.pool.run(f, *args, **kwargs)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/schema.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,302 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*- 
+
+from sqlalchemy import Column, Integer, String, Sequence, Boolean, DateTime, Numeric, Enum
+from sqlalchemy.ext.declarative import declarative_base
+
+#relationship
+from sqlalchemy import ForeignKey
+from sqlalchemy.orm import relationship, backref, object_session
+
+from sqlalchemy import and_
+from sqlalchemy.orm.exc import DetachedInstanceError 
+import sqlalchemy.sql.functions as func
+
+import job
+from ..error import JobNotFound
+
+Base = declarative_base()
+
+class Userright(Base):
+    """Allowed offers for one user. Default routes are sorted by **default** value."""
+    __tablename__ = 'userright'
+    user_name = Column('user', String(100), ForeignKey('apiuser.name'), primary_key=True)
+    """username"""
+    offer_name = Column('offer', String(100), ForeignKey('offer.name'), primary_key=True)
+    """offername"""
+    default = Column(Integer)
+    """sorting defaults routes with this value"""
+    offer = relationship("Offer")
+    """connected :class:`Offer` object"""
+    user = relationship("User")
+    """connected :class:`User` object"""
+
+    def __init__(self, offer, default=None):
+        """Constructor of Userright class.
+
+        :param `Offer` offer: a offer object
+        :param integer default: default value
+        """
+
+        self.offer = offer
+        self.default = default
+
+    @property
+    def bill(self):
+        """returns a list of unbilled messages grouped by Job.info"""
+        query = [ func.count(Message.id).label('anz'),      # anz of messages
+                  func.sum(Message.price).label("price"),   # price of the messages
+                  Job.info.label('info'),                   # info tag
+                  ]
+
+        filters = [ Message.isBilled==False,                # only unbilled messages
+                   Job.user==self.user,                     # only jobs connected to user
+                   Message.offer==self.offer,               # only messages in the right offer
+                   Message.job_id==Job.id,                  # join Message and Job
+                ]
+        return object_session(self).query(*query).filter(and_(*filters)).group_by(Job.info)
+
+class Offer(Base):
+    """All possible Offers over a Message can be sended. **provider**, **typ** and **route** are used to get the data form configuration file."""
+    __tablename__ = "offer"
+    name = Column(String(100), primary_key=True)
+    """name of the offer"""
+    provider = Column(String(100))
+    """provider name"""
+    route = Column(String(100))
+    """route of the provider"""
+    typ = Column(String(100))
+    """typ of message"""
+    
+    def __init__(self, **kwargs):
+        Base.__init__(self,**kwargs)
+  
+    @classmethod
+    def get(cls, session, provider, route, typ):
+        """returns a Offer object."""
+        return session.query(cls).filter(and_(cls.provider==provider, cls.route==route, cls.typ==typ)).first()
+
+    @classmethod
+    def routes(cls, session, typ):
+        """returns a query object of all possible offers.
+        
+        :param string typ: get all offers that support this typ
+        """
+        return session.query(cls).filter_by(typ=typ)
+
+    @classmethod
+    def typs(cls, session):
+        """returns a list of all possible types.
+        """
+        return session.query(cls.typ).distinct()
+
+    @classmethod
+    def providers(cls, session, typ):
+        """returns a list of all possible providers.
+        
+        :param string typ: get all providers that support this typ
+        """
+        return session.query(cls.provider).filter_by(typ=typ).distinct()
+
+class Message(Base):
+    """A message that has created costs.
+    
+    **isBilled** is False since the bill is paid.
+    """
+    __tablename__ = "message"
+    id = Column(Integer, Sequence('message_id_seq'), primary_key=True)
+    """primary key of the message"""
+
+    recipient = Column(String(100))
+    """string representation of the recipient"""
+    
+    isBilled = Column(Boolean)
+    """is bill paid?"""
+
+    date = Column(DateTime)
+    """date of sending the message"""
+
+    price = Column(Numeric(8,4))
+    """price of sending the message"""
+
+    count = Column(Integer)
+    """Count of sended messages"""
+    exID = Column(String(100))
+    """external API id """
+
+    job_id = Column("job", String(40), ForeignKey('job.id'))
+    """id of the connected job"""
+    job = relationship("Job", backref=backref('messages'))
+    """connected :class:`Job` object"""
+    
+    offer_id = Column("offer",String(100), ForeignKey('offer.name'))
+    """sended message over this offer woth ithe offer.name"""
+
+    offer = relationship("Offer", backref=backref('messages'))
+    """connected :class:`Offer` object"""
+    
+    def __init__(self, **kwargs):
+        Base.__init__(self,**kwargs)
+
+
+class Job(Base):
+    """A complete Job.
+
+    - **status** show the status of the job (``init``, ``started``, ``sending``, ``sended`` or ``error``). 
+    - **info** is used to make it possible to create different billing groups for user.
+    """
+    __tablename__ = "job"
+    id = Column(Integer, Sequence('job_id_seq'), primary_key=True)
+    """job id"""
+    info = Column(String(100))
+    """job info, for billing porpuse"""
+    status = Column(Enum("init","started","sending","sended","error"))
+    """status of a job"""
+    user_id = Column("user", String(100), ForeignKey('apiuser.name'))
+    """connected user id"""
+
+    user = relationship("User", backref=backref('jobs'))
+    """connected :class:`User` object"""
+    
+    def __init__(self, **kwargs):
+        """
+        .. automethod:: __repr__
+        """
+        Base.__init__(self,**kwargs)
+
+    @property
+    def extend(self):
+        """returns the connected :class:`iro.model.job.ExJob`"""
+        return job.exJobs[self.id]
+
+
+    def __repr__(self):
+        """string representation of the Job class.
+
+        :return: ``<Job('id' ,'info', 'status', 'user_id')>``
+        """
+        try:
+            return "<Job('%s' ,'%s', '%s', '%s')>"%(self.id,self.info, self.status, self.user_id)
+        except DetachedInstanceError:
+            return Base.__repr__(self)
+
+    @classmethod
+    def get(cls, session, id):
+        """returns a job object from a given id"""
+        return session.query(cls).filter_by(id=id).first()
+
+class User(Base):
+    """An user in iro."""
+    __tablename__ = "apiuser"
+    
+    name = Column(String(100), primary_key=True)
+    """Username"""
+    
+    ng_kunde = Column(Integer)
+    """Connection to the netzguerilla userdatabase"""
+
+    apikey = Column(String(50),unique=True)
+    """apikey only [0-9a-f]"""
+    
+    rights = relationship('Userright')
+    """all allowed offers to send with."""
+
+    def __init__(self, name, apikey):
+        """Constructor of User class.
+
+        :param string name: username
+        :param string apikey: apikey for the user
+
+        .. automethod:: __repr__
+        """
+        self.name=name
+        self.apikey=apikey
+
+    def __repr__(self):
+        """string representation of the user class.
+
+        :return: ``<User('name', 'apikey')>``
+        """
+        try:
+            return "<User('%s','%s')>"%(self.name,self.apikey)
+        except DetachedInstanceError:
+            return Base.__repr__(self)
+    
+    def routes(self, typ, default = False):
+        """returns a query object to get all possible routes for a given typ
+
+        :param string typ: the typ
+        :param boolean default: use only default routes
+        """
+        filters=[User.name == self.name,
+                Offer.typ == typ, 
+                ]
+        if default:
+            filters.append(Userright.default != None)
+        return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).order_by(Userright.default)
+   
+    def providers(self, typ, default = False):
+        """return a query object for all possible providers for a given typ
+
+        :param string typ: the typ
+        :param boolean default: use only default routes
+        """
+        filters=[User.name == self.name,
+                Offer.typ == typ, 
+                ]
+        if default:
+            filters.append(Userright.default != None)
+        return object_session(self).query(Offer.provider).join(Userright,User).filter(and_(*filters))
+
+    def has_right(self, typ, offer_name = None, provider = None, route = None):
+        """if a user has the right to use a offer, provider e. al. (arguments are and connected).
+        
+        :param string typ: the typ
+        :param string offer_name: offer name
+        :param string provider: provider name
+        :param string route: a route name
+        :return: offer_name or  None (not allwoed)
+        :raises: :class:`sqlalchemy.orm.exc.MultipleResultsFound` if not a single offer match"""
+        filters=[User.name == self.name,
+                Offer.typ == typ, 
+                ]
+        if offer_name:
+            filters.append(Userright.offer_name==offer_name)
+        if provider:
+            filters.append(Offer.provider==provider)
+        if route:
+            filters.append(Offer.route==route)
+
+        return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).scalar()
+
+    def job(self, id):
+        """returns a job object.
+
+        :param integer id: id of a Job
+        :return: :class:`Job`
+        :raises: :exc:`iro.error.JobNotFound`
+        """
+        job = object_session(self).query(Job).join(User).filter(and_(User.name == self.name, Job.id==id)).first()
+        if job is None:
+            raise JobNotFound()
+        return job
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/status.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,37 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+class Status:
+    '''status object -- the resulat of one :class:`iro.controller.task.Task`.'''
+    def __init__(self, provider, route, costs=0.0, count=0, exID=None):
+        """
+        :param `iro.offer.provider.Provider` provider: a provider object
+        :param string route: a route of the provider
+        :param `decimal.Decimal` costs: costs for sending this message
+        :param integer count: count of sended messages
+        :param string exID: ID of external API
+        """
+        self.provider = provider
+        self.route = route
+
+        self.costs = costs
+        self.count = count
+        self.exID = exID
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/user.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,74 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.internet import defer
+try:
+    from inspect import getcallargs
+except ImportError:
+    from ..inspect_getcallargs import getcallargs
+
+from decorator import decorator
+
+from .schema import User
+from .dbdefer import dbdefer
+
+from ..validate import validate, vHash
+from ..error import UserNotFound, InterfaceException
+
+
+@validate(kwd="apikey", func=vHash, minlength=15, maxlength=15)
+@dbdefer
+def getuser(session, apikey):
+    """Returns a valid user object.
+
+    :param session: a valid session object (is created by decorator :func:`iro.model.dbdefer.dbdefer`)
+    :param string apikey: a vaild apikey (only [0-9a-f] is allowed)
+    :return: a :class:`iro.model.schema.User` object
+    :raises: :exc:`iro.error.UserNotFound`
+    """
+    user = session.query(User).filter_by(apikey=apikey).first()
+    if user is None:
+        raise UserNotFound()
+    else:
+        return user
+
+@decorator
+def vUser(f,*args,**kargs):
+    """A Decorator to verify the apikey and execute the function with a valid :class:`iro.model.schema.User` instead of the apikey.
+    
+    The decorator expect the apikey in the **user** parameter.
+
+    :return: a defer
+    """ 
+    kp=getcallargs(f,*args,**kargs)
+    try:
+        apikey = kp["user"]
+    except KeyError:
+        raise InterfaceException() 
+    
+    def _gotResult(_user):
+        kp["user"]=_user
+        e =  defer.maybeDeferred(f,**kp)
+        return e
+    d = defer.maybeDeferred(getuser, apikey=apikey)
+    return d.addCallback(_gotResult)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/utils.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,51 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from sqlalchemy.orm import sessionmaker
+
+class WithSession(object):
+    '''a with statement for a database session connection.'''
+    def __init__(self, engine, autocommit=False):
+        """
+        :param `sqlalchemy.engine.base.Engine` engine: a valid sqlalchemy engine object (normally created via :func:`sqlalchemy.create_engine`).
+        :param boolean autocommit: autocommit after running the function.
+
+        .. automethod:: __enter__
+        .. automethod:: __exit__
+        """
+        self.engine = engine
+        self.autocommit=autocommit
+    
+    def __enter__(self):
+        """returns a vaild session object."""
+        self.session = sessionmaker(bind=self.engine)()
+        return self.session
+    
+    def __exit__(self,exc_type, exc_value, traceback):
+        if exc_type is None:
+            if self.autocommit:
+                self.session.commit()
+        else:
+            self.session.rollback()
+        self.session.close()
+
+
+
--- a/iro/newuser.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-class User: 
-    '''
-    class for a xmlrpc user
-    '''
-    
-    def status(self, apikey, id=None, detailed=False):
-        u'''Gibt den aktuellen Status eines Auftrages zurück.
-
-        Keywords:
-        apikey[string]: Der API Key
-        id[hash]: Eine Auftragsnummer
-        detailed[boolean]: Details ausgeben
-
-        Return:
-        jobs[list]: Eine Liste der Aufträge.
-        job.name[string]: Angebener Name
-        job.status[string]: Status des Auftrages
-
-
-        '''
-        pass
-
-    def stop(self, apikey,id):
-        u'''Stoppt den angegeben Auftrag.
-
-        Keywords:
-        apikey[string]: Der API Key
-        id[hash]: Eine Auftragsnummer
-
-        Return:
-
-        '''
-        pass
-    
-    def sms(self, apikey, message, recipients, route="default"):
-        u'''Versendet eine SMS.
-
-        Keywords:
-        apikey[string]: Der API Key
-        message[string]: Nachricht
-        recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
-        route[string|list]: Route über den geschickt werden soll, 
-                            oder eine Liste von Routen, um Fallbacks anzugeben
-
-        Return:
-        id[hash]: Die ID des Auftrages
-
-        '''
-        pass
-   
-    
-    def fax(self, apikey, subject, fax, recipients, route="default"):
-        u'''Versendet ein FAX.
-
-        Keywords:
-        apikey[string]: Der API Key
-        subject[string]: Der Betreff
-        fax[string]: Das PDF base64 kodiert
-        recipients[list]: Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
-        route[string|list]: Route über den geschickt werden soll, 
-                            oder eine Liste von Routen, um Fallbacks anzugeben
-
-        Return:
-        id[hash]: Die ID des Auftrages
-
-        '''
-        pass
-
-    def mail(self, apikey, subject,  body, recipients, frm, route="default"):
-        u'''Versendet eine Email.
-
-        Keywords:
-        apikey[string]: Der API Key
-        subject[string]: Der Betreff
-        body[string]: Der Email Body
-        recipients[list]: Eine Liste von Emailadressen
-        frm[string]: Die Absender Emailadresse
-        route[string|list]: Route über den geschickt werden soll, 
-                            oder eine Liste von Routen, um Fallbacks anzugeben
-
-        Return:
-        id[hash]: Die ID des Auftrages
-
-        '''
-        pass
-       
-    def routes(self, apikey, typ):
-        u'''Gibt eine Liste aller verfügbaren Provider zurück.
-
-        Keywords:
-        apikey[string]: Der API Key
-        typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
-                     Einer der Liste ["sms","fax","mail"]
-
-        Return:
-        providerlist[list]: Eine Liste aller möglichen Provider
-
-        '''
-        pass
-        
-    def defaultRoute(self, apikey, typ):
-        u'''Gibt den Standardprovider zurück.
- 
-        Keywords:
-        apikey[string]: Der API Key
-        typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
-                     Einer der Liste ["sms","fax","mail"]
-
-        Return:
-        provider[string]: Der Standardprovider für den angeben Typ
-
-
-        '''
-        pass
-
-    def statistic(self,apikey):
-        u'''Gibt eine Statik zurück über die versendendeten Nachrichten und des Preises.
-
-        Keywords:
-        apikey[string]: Der API Key
-
-        Return:
-        statistic[list]: Eine Liste nach Nachrichtentypen
-        '''
-        pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/offer/__init__.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,36 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Package all about providers. This packages handles the code for sending a message through a provider.
+
+- :attr:`iro.offer.provider.providers` -- a dict of all available providers
+- :func:`iro.offer.provider.getProvider` -- returns a object, from a provider name
+- :class:`iro.offer.provider.Provider` -- Baseclass for all providers
+- :class:`iro.offer.smtp.SMTP` -- a provider to send emails over smtp
+- :class:`iro.offer.smstrade.Smstrade` -- a provider to send sms via http://smstrade.de
+- :class:`iro.offer.sipgate.Sipgate` -- a provider to send sms and fax via http://sipgate.de
+- :class:`iro.offer.offer.Offer` -- a represantation of an offer, this is used to send (see :mod:`iro.model.offer`).
+"""
+from .offer import Offer
+from .provider import Provider, providers, getProvider
+from .smtp import SMTP
+from .smstrade import Smstrade
+from .sipgate import Sipgate
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/offer/offer.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,67 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+class Offer():
+    """One Offer for sending.
+    This class is used to send a message via a provider.
+    """
+    def __init__(self, name, provider, route, typ):
+        """ Constructor for Offer class.
+
+        :param string name: name is the name in database for the offer
+        :param `iro.offer.provider.Provider` provider: A valid provider object.
+        :param sting route: used to the the right send function via :meth:`iro.offer.provider.Provider.getSendFunc`.
+        :param sting typ: used to the the right send function via :meth:`iro.offer.provider.Provider.getSendFunc`.
+
+
+        .. automethod:: __call__
+        .. automethod:: __eq__
+        .. automethod:: __neq__
+        .. automethod:: __repr__
+        """
+        self.name = name
+        self.route = route
+        self.provider = provider
+        self.typ = typ
+        self.sendfunc = provider.getSendFunc(typ, route)
+
+    def __call__(self, recipient, message):
+        """send a message to a recipient. This method uses :meth:`iro.offer.provider.Provider.send`
+
+        :param recipient: one recipient
+        :param `iro.model.message.Message` message: message to send
+        """
+        return self.sendfunc(recipient, message)
+
+    def __eq__(self,o):
+        """return ``True``, if o is equal."""
+        return (self.name == o.name) and (self.route == o.route) and (self.provider == o.provider) and (self.typ == o.typ)
+
+    def __neq__(self,o):
+        """return ``True``, if ``o`` is not equal (see :meth:`__eq__`)."""
+        return not self.__eq__(o)
+
+    def __repr__(self):
+        """string representation of this class for debugging purpose.
+
+        :return: ``<Offer(name, provider, route, typ)>`` """
+        return "<Offer(%s, %s, %s, %s)>"%(self.name,self.provider,self.route,self.typ)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/offer/provider.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,113 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from functools import partial
+try:
+    from collections import OrderedDict
+except ImportError:
+    from ordereddict import OrderedDict
+
+from ..error import NoRoute, NoTyp, ValidateException, NoProvider
+from ..config import Option, Config
+
+class Provider(Config):
+    """Base class for Providerbackends."""
+
+    testmode = False
+    """- **True** -- no message to external provider should be send.
+    - **False** (default) -- message are send to external provider."""
+    def __init__(self, name, typs={}, options=[]):
+        """Constructor for Provider class.
+        
+        :param string name: Name of the Provider.
+        :param dict typs: A Dictonary with typs and routes.
+        :param items options: [("name",Option(...)),...]
+
+        >>> p = Provider("myProvider",{"sms":["route1","route2"]})
+        """
+
+        Config.__init__(self, name, OrderedDict([
+                    ("typ",Option(vProvider, long="One available provider typ.", must=True, default=name))
+                    ]+options)
+                )
+        self.typs=typs
+        self.testmode = False
+
+    def send(self, typ, route, recipient, message):
+        """Main send function, that is called, for every single message.
+
+        .. note::
+            This function is not used directly. Normally :func:`~iro.offer.provider.Provider.getSendFunc` return this function with typ and route bound."""
+        pass
+
+    def getSendFunc(self, typ, route):
+        """Returns the actually send function for a given typ and route.
+        
+        Normally it returns the send function with typ and route bound.
+
+        :raises: :exc:`~iro.error.NoRoute`, :exc:`~iro.error.NoTyp`
+        """
+
+        try:
+            if route not in self.typs[typ]:
+                raise NoRoute(route)
+        except KeyError:
+            raise NoTyp(route)
+        return partial(self.send, typ, route)
+
+def getProvider(name, typ, config):
+    '''creates a provider object and init this with config.
+   
+    :param dict config: The Configuration dict. Normally you use ``configParser.items("section")``.
+    :param string typ: A valid typ
+    :raises: :exc:`~iro.error.NoProvider`
+    '''
+    try:
+        p = providers[typ](name)
+        p.load(config)
+        return p
+    except KeyError:
+        raise NoProvider(typ)
+
+def vProvider(typ, field):
+    '''validator to test the existence of the typ.
+
+    :param string typ: A typ
+    :param string field: A field name used for the Exception.
+    :return:
+        - valid -- returns typ
+        - invalid -- raises :class:`~iro.error.ValidateException`
+
+    :raises: :exc:`~iro.error.ValidateException`
+    '''
+    if typ not in providers.keys():
+        raise ValidateException(field=field)
+    return typ
+
+providers={}
+"""Avalable Providers.
+    - **key** -- typ of provider (see configuration typ field).
+    - **value** -- class to handle specific Providertyp.
+
+To extend provider typs, just add this new typ to this dict.
+see :doc:`provider`
+   
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/offer/sipgate.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,139 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from functools import partial
+from twisted.web.xmlrpc import Proxy
+import xmlrpclib
+from decimal import Decimal
+
+from .provider import providers, Provider
+from ..config import Option
+from ..validate import vFloat
+from ..model.status import Status
+from ..error import NoTyp
+from .. import __version__
+
+
+class Sipgate(Provider):
+    """
+        s. auch http.tuxad.com/sipgate.html
+        und http://lists.sipgate.net/pipermail/sipgate-developers/2007-September/000016.html
+
+        http://www.sipgate.de/beta/public/static/downloads/basic/api/sipgate_api_documentation.pdf
+
+        See :doc:`provider` for a tutorial, how to create a new provider backend. This turorial implements this provider.
+    """
+
+    url="https://%s:%s@samurai.sipgate.net/RPC2"
+    """XML-RPC url for sipgate"""
+
+    def __init__(self,name):
+        options =[("username", Option(lambda x,y: x,long="Loginname for sipgate", must=True)),
+                  ("password", Option(lambda x,y: x,long="Password for sipgate", must=True)),
+                  ("price_sms", Option(vFloat, long="price for one sms", default="0.079")),
+                  ("price_fax", Option(vFloat, long="price for one fax", default="0.03")),
+                  ]
+        Provider.__init__(self, name, {"sms" : [None], "fax":[None]}, options)
+
+    def proxy(self):
+        """returns a XML-RPC proxy object to sipgate API."""
+        return Proxy(self.url%(self.username, self.password))
+
+    def load(self,cfg):
+        """Loads configuration into object.
+
+        :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``.
+        """
+
+        Provider.load(self,cfg)
+        #return self.clientIdentify()
+
+    def clientIdentify(self):
+        """identificaton of client to sipgate server."""
+        args_identify = {
+                "ClientName"    : "Iro",
+                "ClientVersion" : __version__,
+                "ClientVendor"  : "netzguerilla.net"
+                }
+
+        return self.proxy().callRemote("samurai.ClientIdentify", args_identify)
+
+    def sms(self, recipient, sms):
+        """send one SMS to recimpient.
+
+        :param `iro.telnumber.Telnumber` recipient: mobilenumber of recipient
+        :param `iro.model.message.SMS` sms: the sms to send
+        :return: a deferrer
+        """
+        args={
+                "TOS" : "text",
+                "Content" : sms.getContent(),
+                "RemoteUri" : "sip:%s%s@sipgate.net"%(recipient.land, recipient.number),
+                }
+        return self.proxy().callRemote("samurai.SessionInitiate",args)
+
+    def fax(self, recipient, fax):
+        """send one fax to recimpient.
+
+        :param `iro.telnumber.Telnumber` recipient: faxnumber of recipient
+        :param `iro.model.message.Fax` fax: the fax to send
+        :return: a deferrer.
+        """
+
+        args={
+            "TOS" : "fax",
+            "Content" : xmlrpclib.Binary(fax.getAttachment(0)),
+            "RemoteUri" : "sip:%s%s@sipgate.net"%(recipient.land, recipient.number),
+            }
+        return self.proxy().callRemote("samurai.SessionInitiate",args)
+
+    def _status(self,value,typ):
+        """returns a :class:`~iro.model.status.Status` object.
+        :raises: :exc:`iro.error.NoTyp`
+        """
+        if typ not  in self.typs.keys():
+            raise NoTyp(typ)
+        return Status(self, None, Decimal(getattr(self,"price_"+typ)), 1, value["SessionID"])
+
+    def send(self, typ, recipient, msg):
+        """send msg to recipient.
+
+        :param string typ: Typ of message.
+        :param `iro.telnumber.Telnumber` recipient: telnumber of recipient
+        :param msg: the msg to send
+        :type msg: :class:`iro.model.message.Fax` or :class:`iro.model.message.SMS`
+        :return: a deferrer, that returns a :class:`~iro.model.status.Status` object
+        :raises: :exc:`iro.error.NoTyp`
+        """
+        if typ not  in self.typs.keys():
+            raise NoTyp(typ)
+        d = getattr(self,typ)(recipient, msg)
+        d.addCallback(self._status, typ)
+        return d
+
+    def getSendFunc(self, typ, route):
+        """returns :meth:`send` method with bound typ, if typ and route is valid."""
+
+        Provider.getSendFunc(self, typ, route)
+        return partial(self.send, typ)
+
+providers["sipgate"] = Sipgate
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/offer/smstrade.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+import urllib
+from functools import partial
+from decimal import Decimal
+#import copy
+
+from ..config import Option
+from ..model.status import Status
+from .provider import Provider, providers
+from ..error import RejectRecipient, ExternalException
+
+#import logging
+#logger=logging.getLogger("smstrade")
+
+statusCodes = {10 : "Empfaengernummer nicht korrekt.",
+    20 : "Absenderkennung nicht korrekt.",
+    30 : "Nachrichtentext nicht korrekt.",
+    31 : "Messagetyp nicht korrekt.",
+    40 : "SMS Route nicht korrekt.",
+    50 : "Identifikation fehlgeschlagen.",
+    60 : "nicht genuegend Guthaben.",
+    70 : "Netz wird von Route nicht abgedeckt.",
+    71 : "Feature nicht ueber diese Route moeglich.",
+    80 : "Uebergabe an SMS-C fehlgeschlagen.",
+    90 : "Versand nicht moeglich.",
+    100 : "SMS wurde versendet.",
+    }
+"""statuscodes of external smstrade API"""
+
+
+class SmstradeException(ExternalException):
+    """An excetion that connects the status code with the excetion string (see :attr:`statusCodes`)"""
+    def __init__(self,status):
+        ExternalException.__init__(self)
+        self.status = status
+        self.str_=str(status)
+    
+    def __str__(self):
+        return "%s\n%s"%(ExternalException.__str__(self),self.str_)
+
+
+class StatusCode:
+    """Class that represents the output of one smstrade request."""
+    def __init__(self,code, exID=None, costs=Decimal("0.0"), count=0):
+        self.code = code
+        
+        self.exID = exID
+        self.costs = Decimal(costs)
+        self.count = int(count)
+     
+    def __str__(self):
+        if self.code in statusCodes.keys():
+            return "%i: %s"%(self.code, statusCodes[self.code])
+        
+        return "%i: unknown statuscode."%self.code
+
+    def __int__(self):
+        return self.code
+
+class Smstrade(Provider):
+    """A Provider to send SMS to recipients using smstrade.
+    Smstrade only supports to send SMS  and four diffrent routes: ``["basic","economy","gold","direct"]``.
+
+    It needs a smstrade Gateway Key https://login.smstrade.de/index.php?gateway in configuration file.
+
+    smstrade API documentation:  http://kundencenter.smstrade.de/sites/smstrade.de.kundencenter/__pdf/SMS-Gateway_HTTP_API_v2.pdf
+
+    The smstrade API supports a debug mode, that can be set  with :attr:`~iro.offer.provider.Provider.testmode`.
+    """
+    _params= {"debug":("boolean",False),
+            "concat_sms":('boolean',False),
+            "message_id":('boolean',False),
+            "count":('boolean',False),
+            "cost":('boolean',False),
+           }
+    '''dict for standrd values of the smstrade api, it is used to get the right values to the API.'''
+
+    def __init__(self, name):       
+        self.url = "https://gateway.smstrade.de"
+        options =[("key", Option(lambda x,y:x,long="smstrade Gateway Key https://login.smstrade.de/index.php?gateway", must=True)),]
+        Provider.__init__(self, name, {"sms":["basic","economy","gold","direct"]},options)
+
+    def send(self, route, recipient, sms):
+        """send one SMS to recipient via route
+        
+        :param string route: A valid route ``["basic", "economy", "gold", "direct"]``
+        :param `iro.telnumber.Telnumber` recipient: Mobilenumber of recipient
+        :param `iro.model.message.SMS` sms: the sms to send
+        :return:
+            - All went ok -- :class:`iro.model.status.Status` object
+            - otherwise -- an exception
+        """
+        #logger.debug('smstrade.sendSMS(%s,%s)'%(sms,  recipient))
+
+        route = unicode(route)
+
+        if recipient.land != '49' and route == "basic":
+            raise RejectRecipient(recipient)
+        
+        to ='00'+recipient.land+recipient.number
+        
+        s = self.__send(route, to, sms)	
+        if int(s) in (100,):
+            return Status(self,route, exID=s.exID, costs=s.costs, count=s.count)
+        elif int(s) in (70,71,):
+            raise RejectRecipient(recipient, status=s)
+        else:
+            raise SmstradeException(s)
+
+    def __send(self, route, to, sms):
+        """ This is the main function to request to the sms service.    
+
+        :param string route: A valid route ``["basic", "economy", "gold", "direct"]
+        :param string recipient: Mobilenumber of recipient
+        :param `iro.model.message.sms` sms: the sms to send
+        :return: a :class:`.StatusCode` object
+        """
+        
+        #logger.debug('smstrade._send(%s,%s,%s)'%( route, to, sms))
+        parameters= {"key": self.key,
+                "route": route,
+                "to": to,
+                "message": sms.content,
+                "charset":"utf-8", 
+                "debug": self.testmode,
+                "message_id":True,
+                "count":True,
+                "cost":True,
+                }
+        
+        doubleChar="€[]{}|\\^~"    #these charactar need two GSM Chars
+
+        if sms.from_ is not None:
+            parameters["from"] = sms.from_
+       
+        length=len(sms.content)
+        for s in doubleChar:
+            length += sms.content.count(s)
+        parameters["concat_sms"] = True if length > 160 else False
+
+        ps={}
+        for p in parameters:
+            if p in self._params.keys():
+                if self._params[p][0] == "boolean":
+                    if parameters[p] != self._params[p][1]:
+                        ps[p]=int(bool(parameters[p]))
+            else:
+                ps[p] = parameters[p]
+
+        params = urllib.urlencode(ps)
+        #dp=copy.deepcopy(ps)
+        #dp["key"]="<KEY>"
+        #print 'smstrade._send-parameters:%s\n\t->%s'%(str(dp), urllib.urlencode(dp))
+        
+        response = urllib.urlopen(self.url, params)
+        data = response.readlines()
+        if len(data) == 1:
+            return StatusCode(int(data[0]))
+        return StatusCode(int(data[0]),exID=data[1],costs=data[2],count=data[3])
+
+    def getSendFunc(self, typ, route):
+        """returns  a partial :meth:`send` methed with bounded route, if typ and route is valid."""
+
+        Provider.getSendFunc(self, typ, route)
+        return partial(self.send,route)
+
+providers["smstrade"]=Smstrade
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/offer/smtp.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,94 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*-
+
+import smtplib
+import copy
+from functools import partial
+
+from ..validate import vInteger, vEmail,vBool
+from ..model.status import Status
+from ..config import Option
+from .provider import Provider, providers
+
+class SMTP(Provider):
+    """A SMTP Provider to send emails.
+    This Provider has only one typ ``"mail"`` and one route ``None``. 
+    
+    If :attr:`~iro.offer.provider.Provider.testmode` is **True** no mail will be send, only a connection is created to server.
+
+    """
+    def __init__(self, name):
+        options = [
+                ("host", Option(lambda x,y: x, long="Hostname of MTA", must=True)),
+                ("port", Option(partial(vInteger,minv=0),long="Port of the MTA", default=25)),
+                ("user", Option(lambda x,y: x, long="username to login into MTA.",default="")),
+                ("password", Option(lambda x,y: x, long="password to login into MTA.",default="")),
+                ("SSL", Option(vBool,long="use SSL for connection to MTA", default=False)),
+                ("TLS", Option(vBool,long="use TLS for connection to MTA", default=False)),
+                ("send_from", Option(vEmail,long="Emailaddress from which mail will be sended.",must=True)),
+                ]
+        Provider.__init__(self,name,{"mail":[None]},options)
+
+    def send(self, recipient, mail):   
+        """sends a mail to recipient
+        
+        :param string recipient: A valid email address.
+        :param `iro.model.message.Mail` mail: Mail to send.
+        :return: 
+            - All went ok -- :class:`iro.model.status.Status` object
+            - otherwise -- an exception
+        """
+        if not self.testmode:
+            if self.SSL:
+                smtp = smtplib.SMTP_SSL(self.host,self.port)
+            else:
+                smtp = smtplib.SMTP(self.host,self.port)
+            
+            if self.TLS:
+                smtp.starttls()
+           
+            if not self.user == "":
+                smtp.login(self.user,self.password)
+        try:
+            frm=self.send_from
+            
+            if mail.getFrom():
+                frm = mail.getFrom()
+            
+            tmpmail=copy.deepcopy(mail)
+            tmpmail.content['From'] = frm 
+            tmpmail.content['To'] = recipient
+            if not self.testmode:
+                smtp.sendmail(frm,  recipient, tmpmail.as_string())
+            return Status(self, None)
+        finally:
+            smtp.quit()
+
+    def getSendFunc(self, typ, route):
+        """returns :meth:`send` method, if typ and route is valid."""
+
+        Provider.getSendFunc(self, typ, route)
+        return self.send
+
+providers["smtp"]=SMTP
+
--- a/iro/providerlist.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-
-class Providerlist:
-    def __init__(self):
-        self.provider={}
-        self.types={}
-        self.defaults={}
-        
-    def add(self, name, provider, typeslist):
-        self.provider[name]={"name":name, "class":provider, "types":typeslist}
-        for stype in typeslist:
-            try:
-                self.types[stype].append(self.provider[name])
-            except KeyError:
-                self.types[stype]=[self.provider[name]]
-            
-    
-    def setDefault(self, stype, name):
-        self.defaults[stype]=self.provider[name]
-    
-    def getDefault(self, stype):
-        return self.defaults[stype]
-    
-    def getProviderlist(self, stype):
-        llist=[ provider["name"] for provider in self.types[stype] ]
-        llist.sort()
-        return llist
-
-    def status(self):
-        ret="provider:%s"%self.provider
-        ret +="\ntypes:%s"%self.types
-        return ret+"\ndefaults:%s"%self.defaults
-
-    def getProvider(self, stype,  name="default"):
-        if name=="default":
-            return self.getDefault(stype)["class"]
-
-        if not stype in self.provider[name] ["types"]:
-            raise Exception("argh")
-
-        return self.provider[name]["class"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/telnumber.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,114 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*-
+import re
+
+from .error import InvalidTel
+
+class Telnumber:
+    """A telefonnumer, with splitted country part"""
+    re_telnum=re.compile(r'^\s*(\+)?([0-9\s\-/\(\)]){5,}\s*$')
+    """Regex for a complete telefon number"""
+
+    re_land=re.compile(r'^\s*(\+|00)(?P<land>[1-9]{2})')
+    """Regex for country part"""
+
+    re_number=re.compile(r'[^0-9]')
+    """Regex for numbers"""
+
+    std_land="49"
+    """Standard country part"""
+
+    def __init__(self,number=None):
+        """
+        :param string number: a telefonnumber
+        
+        .. automethod:: __eq__
+        .. automethod:: __neq__
+        .. automethod:: __hash__
+        .. automethod:: __str__
+        .. automethod:: __repr__
+
+        """
+        self.land = None
+        """Country part of a telefonnumber"""
+        
+        self.number = None
+        """Localpart of the telefonnumber"""
+        
+        if not(number is  None):
+            self.createNumber(number)
+
+    def createNumber(self, number):
+        """Split string into two parts: one country part and the rest.
+        
+        For a local number :attr:`std_land` will be used for country part.
+
+        :param string number: a telefonnumber
+        """
+        if not self.re_telnum.match(number):
+            raise InvalidTel(number)
+        
+        
+        self.land=self.std_land
+
+        land=self.re_land.match(number)
+        
+        if land:
+            self.land=land.group("land")
+            number=number[land.end("land"):]
+            
+        number=self.re_number.sub('',number)
+        
+        if number[0]=="0":
+            number=number[1:]
+
+        self.number = number
+        
+    def __eq__(self, y):
+        """Return ``True``, if y hase the same telefonnumber"""
+        return ((self.number == y.number) and ( self.land == y.land))
+    
+    def __neq__(self, y):
+        """Return ``True``, if y is not equal (see :meth:`__eq__`)"""
+        return not self.__eq__(y)
+
+    def __hash__(self):
+        """Return the Hash for telefonnumbers.
+
+        Yust use the hash of the string representation of the Number
+        """
+        return str(self).__hash__()
+
+    def __str__(self):
+        """String representation of the telefonnumber.
+
+        :return: the international telefonnumber with leading zeros
+        """
+        return "00%s%s"%(self.land,self.number)
+
+    def __repr__(self):
+        """debug representation of the class.
+        
+        :return: `<Telnumber 0012345667>`
+        """
+        return "<Telnumber %s>"%str(self)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/test_helpers/__init__.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,20 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/test_helpers/dbtestcase.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,104 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.trial import unittest
+
+from sqlalchemy import create_engine, pool
+from tempfile import mkdtemp
+
+from shutil import rmtree
+import atexit 
+from ngdatabase.mysql import Server, createConfig, Database
+
+from iro.model import setEngine, setPool
+from iro.model.utils import WithSession
+from iro.model.schema import Base
+
+from iro.controller.pool import dbPool
+
+from .utils import DummyObserver
+
+class DBTestCase(unittest.TestCase):
+    '''a TestCase with DB connection
+    you have to set self.session manually in setUp'''
+    def __init__(self,*args,**kwargs):
+        unittest.TestCase.__init__(self,*args,**kwargs)
+        self.engine = md.engine
+
+    def setUp(self):
+        md.setUp()
+        self.log = DummyObserver()
+        self.log.start()
+
+    def tearDown(self):
+        self.log.stop()
+        self.__cleanDB()
+    
+    def session(self,autocommit=True):
+        '''returns a WithSession'''
+        return WithSession(self.engine,autocommit)
+    
+    def __cleanDB(self):
+        '''cleaning database'''
+        with self.session() as session:
+            for table in reversed(Base.metadata.sorted_tables):
+                session.execute(table.delete())
+
+
+class SampleDatabase(Database):
+    def createPassword(self):
+        self.password="test"
+        return self.password
+
+class ModuleData(object):
+    def __init__(self):
+        self.create()
+
+    def close(self):
+        if self.valid:
+            self.server.stop()
+            rmtree(self.tdir)
+            self.valid= False
+
+    def create(self):
+        self.tdir = mkdtemp(prefix='iro-mysql-')
+        self.server = Server('%s/my.cnf'%self.tdir)
+        self.db = SampleDatabase("iro","test",'%s/my.cnf'%self.tdir)
+        self.dburl = 'mysql://test:test@localhost/iro?unix_socket=%s/socket'%self.tdir
+        self.engine = create_engine(self.dburl, 
+                poolclass = pool.SingletonThreadPool,  pool_size=dbPool.maxthreads, )#echo=True)
+        self.valid = False
+
+    def setUp(self):
+        if not self.valid:
+            with open('%s/my.cnf'%self.tdir,'w') as cnf:
+                cnf.write(createConfig(self.tdir))
+            self.server.create()
+            self.server.start()
+            self.db.create()
+            Base.metadata.create_all(self.engine)
+            setEngine(self.engine)
+            setPool(dbPool)
+            self.valid = True
+    
+
+md=ModuleData()
+atexit.register(md.close)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/test_helpers/smtp_helper.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,49 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+A helper class which allows test code to intercept and store sent email.
+(from: http://lakin.weckers.net/thoughts/twisted/part1/threaded/)
+"""
+import smtpd
+import asyncore
+
+class TestSMTPServer(smtpd.SMTPServer):
+    """
+    An SMTP Server used to allow integration tests which ensure email is sent.
+    """
+
+    def __init__(self, localaddr):
+        self.rcvd = []
+        smtpd.SMTPServer.__init__(self, localaddr, None)
+
+    def start(self):
+        import threading
+        self.poller = threading.Thread(target=asyncore.loop,
+                kwargs={'timeout':0.1, 'use_poll':True})
+        self.poller.start()
+
+    def process_message(self, peer, mailfrom, rcpttos, data):
+        self.rcvd.append((mailfrom, rcpttos, data))
+
+    def close(self):
+        smtpd.SMTPServer.close(self)
+        self.poller.join()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/test_helpers/utils.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,39 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.python import log
+
+class DummyObserver(object):
+    def __init__(self):
+        self.e=[]    
+
+    def start(self):
+        log.addObserver(self.emit)
+
+    def stop(self):
+        log.removeObserver(self.emit)
+    
+    def emit(self, eventDict):
+        self.e.append(eventDict)
+
+class DummyPool():
+    def run(self, f,*a,**k):
+        return f(*a,**k)
--- a/iro/tests/__init__.py	Wed Dec 21 22:07:48 2011 +0100
+++ b/iro/tests/__init__.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,32 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import db
+import config
+import email_validate, telnumber, validate, model_validate
+import offer, job, task
+import smtp, smstrade, offer_integrated
+
+import reload, xmlrpc
+import install
+import viewinterface
+
+__all__=[db]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/config.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,230 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from mock import patch, Mock
+from twisted.trial import unittest
+import signal
+import io
+import ConfigParser
+from functools import partial
+
+from iro import config, error, validate 
+from iro.config import OrderedDict
+from iro.offer.provider import Provider, providers
+
+class TestModuleConfig(unittest.TestCase):
+    '''test config class'''
+ 
+    def setUp(self):
+        self._reloadList=config.configParser.reloadList
+        config.configParser.reloadList=[]
+
+    def tearDown(self):
+        config.configParser.reloadList = self._reloadList
+
+    @patch('iro.config.main')
+    @patch('iro.config.configParser')
+    def testReadConfig(self,pConfig,pMain):
+        pMain._init = True
+        config.readConfig()
+        self.assertEqual([i[0] for i in pConfig.method_calls],["read","reload_","items"])
+        pConfig.read.assert_called_once_with(config.confFiles)
+        pConfig.items.assert_called_once_with("main")
+        pConfig.reload_.assert_called_once_with()
+        self.assertEqual(pMain.load.called,1)
+
+    @patch('iro.config.main')
+    @patch('iro.config.configParser')
+    def testReadConfigInit(self,pConfig,pMain):
+        pConfig.items.return_value = [('dburl',''),("port",1000)]
+        pMain._init = False
+        config.readConfig()
+        self.assertEqual([i[0] for i in pConfig.method_calls],["read","reload_","items"])
+        self.assertEqual([i[0] for i in pMain.method_calls],["same"])
+        sa = pMain.same.call_args_list[0][0][0]
+        self.assertEqual(sa.port,1000)
+        self.assertEqual(sa.dburl,'')
+        pMain.same.return_value = False
+        self.assertRaises(Exception,config.readConfig,)
+
+    @patch('iro.config.main')
+    @patch('iro.config.configParser')
+    def testInit(self,pConfig,pMain):
+        pMain._init = False
+        config.init()
+        self.assertEqual([i[0] for i in pConfig.method_calls],["read", "items"])
+        pConfig.read.assert_called_once_with(config.confFiles)
+        pConfig.items.assert_called_once_with("main")
+        self.assertEqual(pMain.load.called,1)
+
+
+    @patch('signal.signal')
+    @patch('iro.config.readConfig')
+    def testRegisterSignal(self, pReadConfig, pSignal):
+        config.registerSignal()
+        self.assertEqual(pSignal.call_args[0][0],signal.SIGUSR2)
+        self.assertEqual(pReadConfig.called,0)
+        pSignal.call_args[0][1](None, None)
+        pReadConfig.assert_called_once_with()
+
+    def testRegisterReload(self):
+        def x():
+            pass
+        config.configParser.registerReload(x)
+        self.assertEqual(config.configParser.reloadList,[x])
+
+    def testReload(self):
+        x = Mock()
+        config.configParser.reloadList = [x] 
+        config.configParser.reload_()
+        x.assert_called_once_with()
+
+
+class TestRead(unittest.TestCase):
+
+
+
+    def tearDown(self):
+        for s in config.configParser.sections():
+            config.configParser.remove_section(s)
+
+    @patch('iro.config.ConfigParser.read')
+    def testMain(self,pRead):
+        sample_config = """[main]
+port = 8000
+dburl = sdfdsafgsfdg
+"""
+        config.configParser.readfp(io.BytesIO(sample_config))
+        config.configParser.read([])
+        pRead.assert_called_once_with(config.configParser,[])
+
+    @patch('iro.config.ConfigParser.read')
+    def testMainBadPort(self,pRead):
+        sample_config = """[main]
+port = -8000
+dburl = sadfaserasg
+"""
+        config.configParser.readfp(io.BytesIO(sample_config))
+        self.assertRaises(error.ValidateException, config.configParser.read, [])
+
+    @patch('iro.config.ConfigParser.read')
+    def testMainNoMust(self,pRead):
+        sample_config = """[main]
+port = 8000
+"""
+        config.configParser.readfp(io.BytesIO(sample_config))
+        self.assertRaises(config.NeededOption, config.configParser.read, [])
+
+    @patch('iro.config.ConfigParser.read')
+    def testMust(self,pRead):
+        v=Mock()
+        config.main.options["test"] = config.Option(v,default="jehei")
+        try:
+            sample_config = """[main]
+dburl = sdfawersdf
+port = 8000
+"""
+            config.configParser.readfp(io.BytesIO(sample_config))
+            config.configParser.read([])
+            self.assertEqual(v.called,0)
+            config.main.load(config.configParser.items("main"))
+            self.assertEqual(config.main.test,'jehei')
+            sample_config = """[main]
+dburl = adfgsdftsfg
+port = 8000
+test = foohu
+    """
+            config.configParser.readfp(io.BytesIO(sample_config))
+            config.configParser.read([])
+            v.assert_called_once_with("foohu","test")
+        finally:
+            del(config.main.options["test"])
+
+    @patch('iro.config.ConfigParser.read')
+    def testProviders(self, pRead):
+        v=Mock()
+        class TestProvider(Provider):
+            def __init__(self,name):
+                o = [("test",config.Option(v))]
+                Provider.__init__(self, name, options=o)
+        providers["test"]=TestProvider
+        try:
+            sample_config = """[p]
+"""
+            config.configParser.readfp(io.BytesIO(sample_config))
+            self.assertRaises(ConfigParser.NoOptionError, config.configParser.read, [])  
+            self.assertEqual(v.called,0)
+            sample_config = """[p]
+typ= test
+test= foo
+"""
+            config.configParser.readfp(io.BytesIO(sample_config))
+            config.configParser.read([])
+            v.assert_called_once_with("foo","test")
+        finally:
+            del(providers["test"])
+
+
+class TestConfig(unittest.TestCase):
+
+    def config(self,name):
+        return config.Config(name, OrderedDict([
+                ("dburl",config.Option(lambda x,y:x,long="Connection URL to database",must=True)),
+                ("port",config.Option(partial(validate.vInteger,minv=0),long="Port under that twisted is running",must=True)),
+                ])
+            )
+
+    def testSame(self):
+        c1 = self.config("1")
+        c1.port = 1
+        c1.dburl = "dburl1"
+        c2 = self.config("2")
+        c2.port = 1
+        c2.dburl = "dburl1"
+        self.assertTrue(c1.same(c2))
+        self.assertTrue(c1.same(c1))
+        self.assertTrue(c2.same(c1))
+        c2.port = 2
+        self.assertFalse(c2.same(c1))
+        self.assertFalse(c1.same(c2))
+        c2.port = 1
+        c2.dburl = "dburl2"
+        self.assertFalse(c2.same(c1))
+        self.assertFalse(c1.same(c2))
+
+    def testSampleConf(self):
+        c1 = self.config("1")
+        self.assertEqual(c1.sampleConf(),["[1]",
+            "# Connection URL to database",
+            "dburl = ","",
+            "# Port under that twisted is running",
+            "port = ",""])
+
+    def testSampleConfDefault(self):
+        c1 = self.config("1")
+        c1.options["port"].default = 12345
+        c1.options["port"].must = False
+        c1.options["dburl"].default = True
+        self.assertEqual(c1.sampleConf(),["[1]",
+            "# Connection URL to database",
+            "dburl = True","",
+            "# Port under that twisted is running",
+            "# port = 12345",""])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/db.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,307 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import unittest
+from sqlalchemy.orm.exc import MultipleResultsFound
+from datetime import datetime
+
+from iro.model.schema import User, Offer, Userright, Job, Message
+from decimal import Decimal
+
+from ..test_helpers.dbtestcase import DBTestCase
+
+class DBTests(DBTestCase):
+    """tests for the db model"""
+    
+    def testRoutes(self):
+        '''test routes'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            session.add(u)
+        
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.routes('sms').all(),[('sipgate_basic',),])
+
+    def testRoutesNoDefault(self):
+        '''test default routes'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            session.add(u)
+        
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.routes('sms',default=True).all(),[])
+
+    def testRoutesDefault(self):
+        '''test default routes'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o,default=1)) 
+            session.add(u)
+
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.routes('sms',default=True).all(),[('sipgate_basic',),])
+
+    def testRoutesDefaultOrdering(self):
+        '''test default routes ordering'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="s1", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o,default=3)) 
+            o=Offer(name="s2", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o,default=None)) 
+            o=Offer(name="s3", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o,default=1)) 
+            session.add(u)
+
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.routes('sms',default=True).all(),[('s3',),('s1',)])
+
+    def testProviders(self):
+        '''test providers'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            session.add(u)
+        
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.providers('sms').all(),[('sipgate',),])
+
+    def testTyps(self):
+        with self.session() as session:
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            session.add(o)
+        
+        with self.session() as session:
+            self.assertEqual(Offer.typs(session).all(),[('sms',),])
+
+        with self.session() as session:
+            o=Offer(name="s2", provider="sipgate", route="basic", typ="sms")
+            session.add(o)
+            o=Offer(name="s3", provider="sipgate", route="basic", typ="sms2")
+            session.add(o)
+        
+        with self.session() as session:
+            self.assertEqual(Offer.typs(session).order_by(Offer.typ).all(),[('sms',),('sms2',)])
+
+    def testOfferRoutes(self):
+        with self.session() as session:
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            session.add(o)
+        
+        with self.session() as session:
+            self.assertEqual([o.name for o in Offer.routes(session, "sms").order_by(Offer.name)],["sipgate_basic"])
+
+        with self.session() as session:
+            o=Offer(name="s2", provider="sipgate", route="basic", typ="sms")
+            session.add(o)
+            o=Offer(name="s3", provider="sipgate", route="basic", typ="sms2")
+            session.add(o)
+        
+        with self.session() as session:
+            self.assertEqual([o.name for o in Offer.routes(session, "sms2").order_by(Offer.name)],["s3"])
+            self.assertEqual([o.name for o in Offer.routes(session, "sms").order_by(Offer.name)],["s2","sipgate_basic"])
+
+    def testOfferProviders(self):
+        with self.session() as session:
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            session.add(o)
+        
+        with self.session() as session:
+            self.assertEqual([o.provider for o in Offer.providers(session, "sms").order_by(Offer.provider)],["sipgate"])
+
+        with self.session() as session:
+            o=Offer(name="s2", provider="sipgate2", route="basic", typ="sms")
+            session.add(o)
+            o=Offer(name="s3", provider="sipgate", route="basic", typ="sms2")
+            session.add(o)
+        
+        with self.session() as session:
+            self.assertEqual([o.provider for o in Offer.providers(session, "sms2").order_by(Offer.provider)],["sipgate"])
+            self.assertEqual([o.provider for o in Offer.providers(session, "sms").order_by(Offer.provider)],["sipgate","sipgate2"])
+
+class Has_RightTests(DBTestCase):
+    '''test User.has_right'''
+    def testSimple(self):
+        '''test a very simple case'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            session.add(u)
+        
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.has_right("sms"),"sipgate_basic")
+            self.assertEqual(u.has_right("sms", offer_name="sipgate_basic"),"sipgate_basic")
+            self.assertEqual(u.has_right("sms", route="basic"),"sipgate_basic")
+            self.assertEqual(u.has_right("sms", provider="sipgate"),"sipgate_basic")
+
+    def testDouble(self):
+        '''now we have different routes'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="b", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            o=Offer(name="c", provider="sipgate", route="c", typ="sms")
+            u.rights.append(Userright(o)) 
+            session.add(u)
+        
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.has_right("sms", offer_name="b"),"b")
+            self.assertEqual(u.has_right("sms", route="basic"),"b")
+            self.assertEqual(u.has_right("sms", offer_name="c"),"c")
+            self.assertEqual(u.has_right("sms", route="c"),"c")
+            self.assertRaises(MultipleResultsFound, u.has_right,"sms")
+            self.assertRaises(MultipleResultsFound, u.has_right,"sms", provider="sipgate")
+    
+    def testUnknown(self):
+        ''' a unknown typ'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="b", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            o=Offer(name="c", provider="sipgate", route="c", typ="sms")
+            u.rights.append(Userright(o)) 
+            session.add(u)
+        
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.has_right("fax", offer_name="b"), None)
+            self.assertEqual(u.has_right("fax"), None)
+
+
+
+class BillTest(DBTestCase):
+    """test the bill function"""
+    
+    def testBill(self):
+        '''test bill function'''
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            j = Job(info='i',status='sended')
+            m = Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.30, offer=o, job=j)
+            u.rights.append(Userright(o))
+            u.jobs.append(j)
+            session.add(m)
+
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.rights[0].bill.all(),[(1L,Decimal('0.3000'),'i')])
+
+    def testBillAgreget(self):
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            j = Job(info='i',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.30, offer=o))
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o))
+            u.rights.append(Userright(o))
+            u.jobs.append(j)
+ 
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.rights[0].bill.all(),[(2L,Decimal('0.7000'),'i')])
+        
+    def testBillIsBilled(self):
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            j = Job(info='i',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=True, date=datetime.now() , price=0.30, offer=o))
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o))
+            u.rights.append(Userright(o))
+            u.jobs.append(j)
+
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.rights[0].bill.all(),[(1L,Decimal('0.4000'),'i')])
+
+    def testBillMultipleJobs(self):
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            u.rights.append(Userright(o))
+            
+            j = Job(info='i',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o))
+            u.jobs.append(j)
+
+            j = Job(info='a',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o))
+            u.jobs.append(j) 
+        
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.rights[0].bill.order_by(Job.info).all(),
+                    [(1L,Decimal('0.4000'),'a'),
+                    (1L,Decimal('0.4000'),'i')
+                    ])
+
+    def testBillMultipleOffers(self):
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            u.rights.append(Userright(o))
+            
+            j = Job(info='a',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o))
+            u.jobs.append(j) 
+ 
+            o = Offer(name='sipgate_gold',provider="sipgate",route="gold",typ="sms")
+            u.rights.append(Userright(offer=o))
+            
+            j = Job(info='a',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.5, offer=o))
+            u.jobs.append(j) 
+       
+        with self.session() as session:
+            u=session.merge(u)
+            self.assertEqual(u.rights[0].bill.all(),
+                    [(1L,Decimal('0.4000'),'a')])
+        
+            self.assertEqual(u.rights[1].bill.all(),[(1L,Decimal('0.5000'),'a')])
+
+
+if __name__ == '__main__':
+        unittest.main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/email_validate.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,90 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+try:
+    import unittest
+    unittest.case
+except AttributeError:
+    import unittest2 as unittest
+
+from iro.validate import vEmail
+from iro.error import InvalidMail, ValidateException
+
+
+class testEmail(unittest.TestCase):
+    """tests for email adresses"""
+
+    def testVaildEmail(self):
+        '''test vaild email adresses (got from wikipedia)'''
+        validmails=["niceandsimple@example.com",
+                "simplewith+symbol@example.com",
+                'a.little.unusual@example.com',
+                'a.little.more.unusual@dept.example.com',
+                '@[10.10.10.10]',
+                '@[1.1.1.1]',
+                '@[200.100.100.100]',
+                'user@[2001:db8:1ff::a0b:dbd0]',
+                '"much.more\ unusual"@example.com',
+                't."test".t@example.com',
+                '"very.unusual.@.unusual.com"@example.com',
+                '"very.(),:;<>[]\\".VERY.\\"very@\\\ \\"very\\".unusual"@strange.example.com',
+                "!#$%&'*+-/=?^_`{}|~@example.org",
+                '"()<>[]:;@,\\\"!#$%&\'*+-/=?^_`{}| ~  ? ^_`{}|~."@example.org',
+                '""@example.org']
+        
+        for num in validmails:
+            self.failUnlessEqual(vEmail([num],None),[num])
+    
+    def testInvaildEmail(self):
+        '''test invaild email adresses (got from wikipedia)'''
+        invalid=["Abc.example.com",  # (an @ character must separate the local and domain parts)
+                "Abc.@example.com",          # (character dot(.) is last in local part)
+                "Abc..123@example.com",      # (character dot(.) is double)
+                "A@b@c@example.com",         # (only one @ is allowed outside quotation marks)
+                'a"b(c)d,e:f;g<h>i[j\k]l@example.com',  # (none of the special characters in this local part is allowed outside quotation marks)
+                'just"not"right@example.com',           # (quoted strings must be dot separated, or the only element making up the local-part)
+                'thisis."notallowed@example.com',      # (spaces, quotes, and backslashes may only exist when within quoted strings and preceded by a slash)
+                'this\\ still\\"not\\allowed@example.com',   # (even if escaped (preceded by a backslash), spaces, quotes, and backslashes must still be contained by quotes)
+                't."test".t"test".t@example.com',
+                't."test"t."test".t@example.com',
+                ]
+
+        for number in invalid:
+            with self.assertRaises(InvalidMail) as e:
+                vEmail([number],None)
+            self.failUnlessEqual(e.exception.number, number)
+
+    def testInvalidDomain(self):
+        '''invalid Domainname'''
+        with self.assertRaises(InvalidMail):
+            vEmail(['x@&.de'],None)
+
+    def testDoubles(self):
+        self.assertEqual(vEmail(['x@test.de','x@test.de'],None),["x@test.de"])
+
+    def testString(self):
+        self.assertEqual(vEmail('x@test.de', None),"x@test.de")
+
+    def testAllowString(self):
+        self.assertRaises(ValidateException,vEmail, 'x@test.de', None, allowString=False)
+
+    def testAllowList(self):
+        self.assertRaises(ValidateException,vEmail, ['x@test.de'], None, allowList=False)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/install.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,184 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#from mock import patch, Mock
+from twisted.trial import unittest
+from sqlalchemy import create_engine
+from sets import Set
+import io
+import os
+
+from iro import install
+from iro import config
+from iro.model.schema import Base, Offer
+from iro.model.utils import WithSession
+from iro.offer.provider import providers, Provider
+from ..test_helpers.dbtestcase import md, SampleDatabase
+from ..test_helpers.utils import DummyObserver
+
+class TestInstallation(unittest.TestCase):
+    '''test install script'''
+   
+    def setUp(self):
+        md.setUp()
+        if not hasattr(md,"db2"):
+            md.db2=SampleDatabase("test2","test2",'%s/my.cnf'%md.tdir)
+            md.dburl2='mysql://test2:test@localhost/test2?unix_socket=%s/socket'%md.tdir
+            md.db2.create()
+        self.log = DummyObserver()
+        self.log.start()
+        self.engine = None
+
+    def tearDown(self):
+        self.log.stop()
+        if self.engine:
+            Base.metadata.drop_all(self.engine)
+            self.engine = None
+        try:
+            os.remove("iro.conf")
+        except OSError as e:
+            if e.errno != 2:
+                raise
+
+        
+
+    def testCheckConfig(self):
+        self.assertEqual(install.checkConfig(),False)
+        with open("iro.conf",'w') as fp:
+            fp.write("""[main]
+dburl=foo""")
+        self.assertEqual(install.checkConfig(),False)
+        self.assertEqual(len(self.log.e),1)
+        self.assertEqual(self.log.e[0]['message'], ("Error while processing config file: Option 'port' in section 'main' is missing.",))
+        with open("iro.conf",'w') as fp:
+            fp.write("""[main]
+dburl=foo
+port=123456
+""")
+        self.assertEqual(install.checkConfig(),True)
+
+    def testCheckDatabase(self):
+        config.main.dburl=md.dburl2
+        self.assertTrue(install.checkDatabaseConnection())
+        self.assertFalse(install.checkDatabase())
+        self.engine = create_engine(md.dburl2)
+        Base.metadata.create_all(self.engine)
+        self.assertTrue(install.checkDatabase())
+
+    def testCheckDatabaseConnection(self):
+        config.main.dburl="mysql://t@localhost/test"
+        self.assertFalse(install.checkDatabaseConnection())
+        self.assertEqual(len(self.log.e),1)
+        self.assertTrue(self.log.e[0]['message'][0].startswith("Error while trying to connect to database\n"))
+        config.main.dburl="sqlite://"
+        self.assertTrue(install.checkDatabaseConnection())
+
+
+    def testCreateDatabase(self):
+        config.main.dburl=md.dburl2
+        self.assertTrue(install.checkDatabaseConnection())
+        self.assertFalse(install.checkDatabase())
+        install.createDatabase()
+        self.assertTrue(install.checkDatabase())
+   
+    def testCreateSampleConfig(self):
+        install.createSampleConfig()
+        with open("iro.conf",'r') as fp:
+            c = fp.read()
+        with open(os.path.abspath("../iro.conf.inst"),"r") as fp:
+            self.assertEqual(c,fp.read())
+
+    def testNoOverwrite(self):
+        with open("iro.conf","w") as fp:
+            fp.write("muhaha")
+        install.createSampleConfig()
+        with open("iro.conf",'r') as fp:
+            self.assertEqual(fp.read(),"muhaha")
+        self.assertEqual(len(self.log.e),1)
+        self.assertEqual(self.log.e[0]['message'], ("iro.conf exists and will not be overwritten.",))
+
+    def testGetAllRoutesNone(self):
+        config.main.dburl=md.dburl2
+        install.createDatabase()
+        self.assertEqual(install.getAllRoutes([]),{"orphand":Set(),"added":Set()})
+        
+    def testGetAllRoutesOrphand(self):
+        config.main.dburl=md.dburl2
+        install.createDatabase()
+        self.engine = create_engine(md.dburl2)
+        with WithSession(self.engine,True) as session:
+            session.add(Offer(provider="p",route="r",typ="t",name="test"))
+            session.add(Offer(provider="p",route="r2",typ="t",name="test2"))
+        self.assertEqual(install.getAllRoutes([]),{"orphand":Set(["test","test2"]),"added":Set()})
+
+    def testGetAllRoutesAdded(self):
+        config.main.dburl=md.dburl2
+        install.createDatabase()
+        self.engine = create_engine(md.dburl2)
+        class TestProvider(Provider):
+            def __init__(self, name):       
+                Provider.__init__(self, name, {"r":["1","2","3"]})
+        providers["test"]=TestProvider
+        sample_config = """[p]
+typ= test
+test= foo
+"""
+        config.configParser.readfp(io.BytesIO(sample_config))
+        self.assertEqual(install.getAllRoutes(["p"]),{"orphand":Set(),"added":Set(["p_r_1","p_r_2","p_r_3"])}) 
+        
+        #test writing
+        self.assertEqual(install.getAllRoutes(["p"],True),{"orphand":Set(),"added":Set(["p_r_1","p_r_2","p_r_3"])})
+        with WithSession(self.engine,True) as session:
+            o = Set([i[0] for i in session.query(Offer.name).all()])
+            self.assertEqual(o,Set(["p_r_1","p_r_2","p_r_3"]))
+
+        #nothing to do anymore
+        self.assertEqual(install.getAllRoutes(["p"]),{"orphand":Set(),"added":Set()}) 
+
+    def testGetAllRoutesOaA(self):
+        config.main.dburl=md.dburl2
+        install.createDatabase()
+        self.engine = create_engine(md.dburl2)
+        class TestProvider(Provider):
+            def __init__(self, name):       
+                Provider.__init__(self, name, {"r":["1","2","3"]})
+        providers["test"]=TestProvider
+        sample_config = """[p]
+typ= test
+test= foo
+"""
+        config.configParser.readfp(io.BytesIO(sample_config))
+
+        with WithSession(self.engine,True) as session:
+            session.add(Offer(provider="q",route="r",typ="t",name="test"))
+            session.add(Offer(provider="q",route="r2",typ="t",name="test2"))
+            session.add(Offer(provider="p",route="1",typ="r",name="foo"))
+        self.assertEqual(install.getAllRoutes(["p"]),{"orphand":Set(["test","test2"]),"added":Set(["p_r_2","p_r_3"])}) 
+        
+        #test writing
+        self.assertEqual(install.getAllRoutes(["p"],True),{"orphand":Set(["test","test2"]),"added":Set(["p_r_2","p_r_3"])})
+        with WithSession(self.engine,True) as session:
+            o = Set([i[0] for i in session.query(Offer.name).all()])
+            self.assertEqual(o,Set(["test","test2","foo","p_r_2","p_r_3"]))
+
+        self.assertEqual(install.getAllRoutes(["p"]),{"orphand":Set(["test","test2"]),"added":Set()}) 
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/job.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,197 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from datetime import datetime
+from decimal import Decimal
+from mock import patch
+
+from iro.model.job import exJobs, ExJob
+from iro.model.pool import data
+from iro.model.message import SMS
+from iro.model.status import Status
+from iro.model.schema import Job, User, Offer as DBOffer, Userright
+
+from iro.controller.task import Task
+
+from iro.offer.provider import Provider
+
+from iro.telnumber import Telnumber
+from iro.validate import vInteger
+
+from ..test_helpers.dbtestcase import DBTestCase
+from ..test_helpers.utils import DummyPool
+
+class JobTestCase(DBTestCase):
+    def setUp(self):
+        DBTestCase.setUp(self)
+        self.pool = data.pool
+        data.pool = DummyPool()
+
+    def tearDown(self):
+        exJobs.clear()
+        data.pool = self.pool
+        self.pool = None
+        DBTestCase.tearDown(self)
+
+
+class exJobsTest(JobTestCase):
+    '''tests for exJobs'''
+
+    def testCreate(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+
+        job = exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test'])
+        self.assertIsInstance(job, ExJob)
+        self.assertTrue(vInteger(job.dbjob, None, minv=0 ))
+        self.assertEqual(job.message, SMS('test'))
+        self.assertEqual(job.recipients,  [Telnumber('123456789')])
+        self.assertEqual(job.offers,[])
+        self.assertEqual(job.tasks,{})
+
+        with self.session() as session:
+            j = session.query(Job.id).all()
+            self.assertEqual(j,[(job.dbjob,)])
+
+        self.assertEqual(exJobs[job.dbjob],job)
+        self.assertEqual(self.log.e[0]['message'], ("Job(%s) created."%job.dbjob,))
+
+    def testCreate2(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="test", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+
+        job = exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test'])
+        self.assertEqual(job.offers,['test'])
+
+    def testGet(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+
+        job = ExJob(None, [Telnumber('123456789')], SMS('test'), ['test'])
+        exJobs[1] = job
+
+        self.assertEqual(len(exJobs), 1)
+        self.assertEqual(job, exJobs[1])
+
+    def testGetFromDB(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            job = Job( info="info", status="started")
+            u.jobs.append(job)
+            session.add(u)
+
+        with self.session() as session:
+            job = session.merge(job)
+            u = session.merge(u)
+            ejob= ExJob(job.id, [Telnumber('123456789')], SMS('test'), ['test'])
+            exJobs[job.id]=ejob
+            self.assertEqual(job.extend, ejob)
+            self.assertEqual(u.jobs[0].extend,ejob)
+
+    def testUnknownExJob(self):
+        self.assertRaises(KeyError,exJobs.__getitem__,'a1234567890')
+
+
+class StatiTest(JobTestCase):
+    def setUp(self):
+        JobTestCase.setUp(self)
+
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="test", provider="bla", route="a", typ="sms")
+            u.rights.append(Userright(o)) 
+
+        self.user = u
+        self.offer = o
+        
+        self.provider=Provider("bla", {"sms":["a","b","c"]})
+        self.job = exJobs.create(u, [Telnumber('123456789')], SMS('test'), [])
+
+    def tearDown(self):
+        JobTestCase.tearDown(self)
+
+
+    def testSetError(self):
+        self.job.setError(Task(Telnumber('123456789'),self),Exception("muhaha"))
+        errors = self.flushLoggedErrors(Exception)
+        self.assertEqual(len(errors), 1)
+        self.assertEqual(self.log.e[1]['message'], ("Job(%s) status changed to: error."%self.job.dbjob,))
+        self.assertEqual(self.log.e[2]['why'], "Error: Job(%s) to '0049123456789' failed."%self.job.dbjob)
+        
+        with self.session() as session:
+            u = session.merge(self.user)
+            job = u.job(self.job.dbjob)
+            self.assertEqual(job.status,"error")
+
+    def testSetStatus(self):
+        task = Task(Telnumber('123456789'),self.job)
+        status = Status(self.provider,"a")
+        self.job.setStatus(task, status)
+        
+        self.assertEqual(self.log.e[1]['message'], ("Job(%s) status changed to: sended."%self.job.dbjob,))
+        self.assertEqual(self.log.e[2]['message'], ("Job(%s) to '0049123456789' ended sucecessfully via bla:a."%self.job.dbjob,))
+        
+        with self.session() as session:
+            u = session.merge(self.user)
+            job = u.job(self.job.dbjob)
+            self.assertEqual(job.status,"sended")
+            self.assertEqual(len(job.messages),0)
+
+    def testMultipleRecipients(self):
+        self.job.recipients.append(Telnumber("01234567890"))
+        task = Task(Telnumber('123456789'),self.job)
+        status = Status(self.provider,"a")
+        self.job.setStatus(task, status)
+        
+        with self.session() as session:
+            u = session.merge(self.user)
+            job = u.job(self.job.dbjob)
+            self.assertEqual(job.status,"sending")
+
+    @patch("iro.model.job.datetime")
+    def testCosts(self,p_dt):
+        p_dt.today.return_value = datetime(2000, 1, 2, 3, 4, 5)
+        task = Task(Telnumber('123456789'),self.job)
+        status = Status(self.provider,"a",costs=0.055,exID="12345678",count=1)
+        
+        self.job.setStatus(task, status)
+        
+        with self.session() as session:
+            u = session.merge(self.user)
+            o = session.merge(self.offer)
+            job = u.job(self.job.dbjob)
+            self.assertEqual(job.status,"sended")
+            self.assertEqual(len(job.messages),1)
+            
+            msg = job.messages[0]
+            self.assertEqual(msg.price,Decimal('0.0550'))
+            self.assertEqual(msg.isBilled,False)
+            self.assertEqual(msg.recipient,str(Telnumber('123456789')))
+            self.assertEqual(msg.date,datetime(2000, 1, 2, 3, 4, 5))
+            self.assertEqual(msg.offer,o)
+            self.assertEqual(msg.exID,"12345678")
+            self.assertEqual(msg.count,1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/jsonresource.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,354 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+#
+# This file is part of Iro.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+from twisted.trial import unittest
+
+from twisted.web.client import Agent, ResponseDone
+from twisted.internet import reactor
+from twisted.web.http_headers import Headers
+from twisted.python.failure import Failure
+from twisted.internet.protocol import Protocol
+from twisted.internet import defer
+
+from zope.interface import implements
+
+from twisted.internet.defer import succeed
+from twisted.web.iweb import IBodyProducer
+
+import json
+
+from twisted.web import server
+from txjsonrpc.web.jsonrpc import Fault
+
+from datetime import datetime
+
+from iro.model.schema import User, Offer, Userright, Job, Message
+
+from iro.view import jsonresource
+import iro.error as IroError
+
+from iro.test_helpers.dbtestcase import DBTestCase
+
+from iro.model import setEngine, setPool
+from iro.controller.pool import startPool, dbPool
+
+
+class StringProducer(object):
+    implements(IBodyProducer)
+
+    def __init__(self, body):
+        self.body = json.dumps(body)
+        self.length = len(self.body)
+
+    def startProducing(self, consumer):
+        consumer.write(self.body)
+        return succeed(None)
+
+    def pauseProducing(self):
+        pass
+
+    def stopProducing(self):
+        pass
+
+class SimpleReceiver(Protocol):
+    def __init__(self, d):
+        self.d = d
+        self.data = ""
+
+    def dataReceived(self, data):
+        self.data += data
+
+    def connectionLost(self, reason):
+        try:
+            if reason.check(ResponseDone):
+                self.d.callback(json.loads(self.data))
+            else:
+                self.d.errback(reason)
+        except:
+            self.d.errback(Failure())
+
+class JSONRPCTest(DBTestCase):
+    """tests for the jsonrpc interface"""
+    ContentType = 'application/json'
+    StringProducer = StringProducer
+    def setUp(self):
+        DBTestCase.setUp(self)
+
+        setEngine(self.engine)
+        startPool(reactor)
+        setPool(dbPool)
+        self.p = reactor.listenTCP(0, server.Site(jsonresource.JSONFactory()),
+                interface="127.0.0.1")
+        self.port = self.p.getHost().port
+        self.agent = Agent(reactor)
+
+    def tearDown(self):
+        DBTestCase.tearDown(self)
+        return self.p.stopListening()
+
+    def proxy(self, method, data=None, **kargs):
+        d = self.agent.request(
+                'GET',
+                'http://localhost:%d/%s'%(self.port, method),
+                Headers({'Content-Type':[self.ContentType]}),
+                self.StringProducer(data) if data else None,
+                )
+
+        def cbResponse(response):
+            if kargs.has_key('code'):
+                self.failUnlessEqual(response.code, kargs["code"])
+            if response.code not in [200,400,500]:
+                raise Fault(response.code,'')
+            self.failUnlessEqual(response.headers.getRawHeaders('Content-Type'), ['application/json'])
+            d = defer.Deferred()
+            response.deliverBody(SimpleReceiver(d))
+            def _(data):
+                if data["status"]:
+                    return data["result"]
+                else:
+                    raise Fault(data["error"]["code"],data["error"]["msg"])
+
+            d.addCallback(_)
+            return d
+        d.addCallback(cbResponse)
+        return d
+
+
+    def testListMethods(self):
+        '''list of all offical Methods, that can be executed'''
+
+        def cbMethods(ret):
+            ret.sort()
+            self.failUnlessEqual(ret, ['bill', 'defaultRoute', 'email', 'fax', 'listMethods', 'mail', 'routes', 'sms', 'status', 'telnumber'])
+
+        d=self.proxy("listMethods", code=200)
+        d.addCallback(cbMethods)
+        return d
+
+    def testApikey(self):
+        ''' test apikey'''
+        with self.session() as session:
+            session.add(User(name='test',apikey='abcdef123456789'))
+
+        d = self.proxy('status', ['abcdef123456789'], code=200)
+        d.addCallback(lambda ret: self.failUnlessEqual(ret,{}))
+
+        return d
+
+    def testStatus(self):
+        ''' test the status function'''
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            j = Job(info='info', status="started")
+            j.user=u
+            session.add(j)
+            session.commit()
+            jid=j.id
+        status = {str(jid):{"status":"started"}}
+        args=[('abcdef123456789',jid),
+                ('abcdef123456789',),
+                ('abcdef123456789', '', 'false'),
+                ('abcdef123456789', '', 0),
+                ('abcdef123456789', '', 0),
+                {"id":jid, "user":'abcdef123456789'},
+                ]
+        dl=[]
+        for a in args:
+            d = self.proxy('status', a, code=200)
+            d.addCallback(lambda ret: self.failUnlessEqual(ret,status))
+            dl.append(d)
+
+        def f(exc):
+            jnf = IroError.JobNotFound()
+            self.failUnlessEqual(exc.faultCode, jnf.code)
+            self.failUnlessEqual(exc.faultString, jnf.msg)
+        d = self.proxy('status', ['abcdef123456789', jid+1], code=500)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        dl.append(d)
+
+        return defer.DeferredList(dl, fireOnOneErrback=True)
+
+    def testNoSuchUser(self):
+        '''a unknown user should raise a UserNotNound Exception
+        bewcause xmlrpc only has a Fault exception this Exception has to be deliverd through a xmlrpclib.Fault Exception'''
+        def f(exc):
+            unf = IroError.UserNotFound()
+            self.failUnlessEqual(exc.faultCode, unf.code)
+            self.failUnlessEqual(exc.faultString, unf.msg)
+        d = self.proxy('status', ['abcdef123456789'], code=500)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        return d
+
+    def testNoSuchMethod(self):
+        '''a unknown mothod should raise a Exception '''
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, 404)
+            self.failUnlessEqual(exc.faultString, '')
+        d = self.proxy('nosuchmethod', code=404)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        return d
+
+    def testValidationFault(self):
+        '''a validate Exception should be translated to a xmlrpclib.Fault.'''
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, 700)
+            self.failUnlessEqual(exc.faultString, "Validation of 'apikey' failed.")
+        d = self.proxy('status',{'user':'xxx'}, code=400)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        return d
+
+    @defer.inlineCallbacks
+    def testRoutes(self):
+        '''test the route function'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o))
+            session.add(u)
+
+        x = yield self.proxy('routes',['abcdef123456789','sms'], code=200)
+        self.failUnlessEqual(x,['sipgate_basic'])
+
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, 700)
+            self.failUnlessEqual(exc.faultString, "Typ fax is not valid.")
+        d = self.proxy('routes',['abcdef123456789','fax'], code=400)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        yield d
+
+        with self.session() as session:
+            o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms")
+            u = session.query(User).filter_by(name="test").first()
+            u.rights.append(Userright(o))
+            o=Offer(name="faxde", provider="faxde", route="", typ="fax")
+            session.add(o)
+            session.commit()
+
+        x = yield self.proxy('routes',['abcdef123456789','sms'], code=200)
+        self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus'])
+        x = yield self.proxy('routes', ['abcdef123456789','fax'], code=200)
+        self.failUnlessEqual(x, [])
+
+        with self.session() as session:
+            u = session.query(User).filter_by(name="test").first()
+            u.rights.append(Userright(o))
+
+        x = yield self.proxy('routes',['abcdef123456789','sms'], code=200)
+        self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus'])
+        x = yield self.proxy('routes', ['abcdef123456789','fax'], code=200)
+        self.failUnlessEqual(x, ['faxde'])
+
+    def testDefaultRoutes(self):
+        '''test the defaultRoute function'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o,True))
+            o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms")
+            u.rights.append(Userright(o))
+            session.add(u)
+        d = self.proxy('defaultRoute', ['abcdef123456789','sms'], code=200)
+        d.addCallback(lambda x: self.failUnlessEqual(x,['sipgate_basic']))
+
+        return d
+
+    def testTelnumbers(self):
+        '''test the telefon validator'''
+        dl = []
+        d = self.proxy('telnumber',[["0123/456(78)","+4912346785433","00123435456-658"]], code=200)
+        d.addCallback(lambda x: self.failUnlessEqual(x,True))
+        dl.append(d)
+
+        invalid=['xa','+1','1-23',';:+0','0123']
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, 701)
+            self.failUnlessEqual(exc.faultString, "No valid telnumber: '%s'" % invalid[0])
+        d = self.proxy('telnumber',[['01234']+invalid], code=400)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        dl.append(d)
+
+        return defer.DeferredList(dl, fireOnOneErrback=True)
+
+    def testVaildEmail(self):
+        '''test vaild email adresses (got from wikipedia)'''
+        validmails=["niceandsimple@example.com"]
+        d = self.proxy('email', {"recipients":validmails}, code=200)
+        d.addCallback(lambda x: self.failUnlessEqual(x,True))
+        return d
+
+    def testInvaildEmail(self):
+        '''test invaild email adresses (got from wikipedia)'''
+        invalid=["Abc.example.com",'foo@t.de']
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, 702)
+            self.failUnlessEqual(exc.faultString, "No valid email: '%s'" % invalid[0])
+        d = self.proxy('email', [invalid], code=400)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        return d
+
+    def testBill(self):
+        '''test bill function'''
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+        d = self.proxy('bill', {"user":apikey}, code=200)
+        d.addCallback(lambda x: self.failUnlessEqual(x,{'total':{'price':0.0,'anz':0}}))
+
+        return d
+
+    def testBillWithPrice(self):
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            u.rights.append(Userright(o))
+            j = Job(info='i',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o))
+            u.jobs.append(j)
+
+            j = Job(info='a',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o))
+            u.jobs.append(j)
+
+        def f(ret):
+            self.failUnlessEqual(ret['total'],{'price':0.8,'anz':2})
+            self.failUnlessEqual(ret['sipgate_basic'],
+                {'price':0.8,'anz':2,
+                    'info':{'i':{'price':0.4,'anz':1},
+                        'a':{'price':0.4,'anz':1},
+                    }
+                })
+
+        d = self.proxy('bill', [apikey], code=200)
+        d.addCallback(f)
+        return d
+
+if __name__ == '__main__':
+    unittest.main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/jsonrpc.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,279 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+#
+# This file is part of Iro.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import unittest
+from twisted.internet import reactor, defer
+from twisted.web import server
+from txjsonrpc.web import jsonrpc as txjsonrpc
+from txjsonrpc.web.jsonrpc import Fault
+from  txjsonrpc import jsonrpclib
+
+from datetime import datetime
+
+from iro.model.schema import User, Offer, Userright, Job, Message
+
+from iro.view import jsonrpc
+import iro.error as IroError
+
+from iro.test_helpers.dbtestcase import DBTestCase
+
+from iro.model import setEngine, setPool
+from iro.controller.pool import startPool, dbPool
+
+
+class JSONRPCTest(DBTestCase):
+    """tests for the jsonrpc interface"""
+    def setUp(self):
+        DBTestCase.setUp(self)
+
+        setEngine(self.engine)
+        startPool(reactor)
+        setPool(dbPool)
+        self.p = reactor.listenTCP(0, server.Site(jsonrpc.JSONRPCInterface()),
+                interface="127.0.0.1")
+        self.port = self.p.getHost().port
+
+    def tearDown(self):
+        DBTestCase.tearDown(self)
+        return self.p.stopListening()
+
+    def proxy(self):
+        return txjsonrpc.Proxy("http://127.0.0.1:%d/" % self.port,version=jsonrpclib.VERSION_2)
+
+    def testListMethods(self):
+        '''list of all offical Methods, that can be executed'''
+
+        def cbMethods(ret):
+            ret.sort()
+            self.failUnlessEqual(ret, ['bill', 'defaultRoute', 'email', 'fax', 'listMethods', 'mail', 'routes', 'sms', 'status', 'telnumber'])
+
+        d=self.proxy().callRemote("listMethods")
+        d.addCallback(cbMethods)
+        return d
+
+    def testApikey(self):
+        ''' test apikey'''
+        with self.session() as session:
+            session.add(User(name='test',apikey='abcdef123456789'))
+
+        d = self.proxy().callRemote('status', 'abcdef123456789')
+        d.addCallback(lambda ret: self.failUnlessEqual(ret,{}))
+
+        return d
+
+    def testStatus(self):
+        ''' test the status function'''
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            j = Job(info='info', status="started")
+            j.user=u
+            session.add(j)
+            session.commit()
+            jid=j.id
+        status = {str(jid):{"status":"started"}}
+        args=[('abcdef123456789',jid),
+                ('abcdef123456789',),
+                ('abcdef123456789', '', 'false'),
+                ('abcdef123456789', '', 0),
+                ]
+        dl=[]
+        for a in args:
+            d = self.proxy().callRemote('status',*a)
+            d.addCallback(lambda ret: self.failUnlessEqual(ret,status))
+            dl.append(d)
+
+        def f(exc):
+            jnf = IroError.JobNotFound()
+            self.failUnlessEqual(exc.faultCode, jnf.code)
+            self.failUnlessEqual(exc.faultString, jnf.msg)
+        d = self.proxy().callRemote('status', 'abcdef123456789', jid+1)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        dl.append(d)
+
+        return defer.DeferredList(dl, fireOnOneErrback=True)
+
+    def testNoSuchUser(self):
+        '''a unknown user should raise a UserNotNound Exception
+        bewcause xmlrpc only has a Fault exception this Exception has to be deliverd through a xmlrpclib.Fault Exception'''
+        def f(exc):
+            unf = IroError.UserNotFound()
+            self.failUnlessEqual(exc.faultCode, unf.code)
+            self.failUnlessEqual(exc.faultString, unf.msg)
+        d = self.proxy().callRemote('status', 'abcdef123456789')
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        return d
+
+    def testNoSuchMethod(self):
+        '''a unknown mothod should raise a Exception '''
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, -32601)
+            self.failUnlessEqual(exc.faultString, 'procedure nosuchmethod not found')
+        d = self.proxy().callRemote('nosuchmethod')
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        return d
+
+    def testValidationFault(self):
+        '''a validate Exception should be translated to a xmlrpclib.Fault.'''
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, 700)
+            self.failUnlessEqual(exc.faultString, "Validation of 'apikey' failed.")
+        d = self.proxy().callRemote('status','xxx')
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        return d
+
+    @defer.inlineCallbacks
+    def testRoutes(self):
+        '''test the route function'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o))
+            session.add(u)
+
+        x = yield self.proxy().callRemote('routes','abcdef123456789','sms')
+        self.failUnlessEqual(x,['sipgate_basic'])
+
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, 700)
+            self.failUnlessEqual(exc.faultString, "Typ fax is not valid.")
+        d = self.proxy().callRemote('routes','abcdef123456789','fax')
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        yield d
+
+        with self.session() as session:
+            o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms")
+            u = session.query(User).filter_by(name="test").first()
+            u.rights.append(Userright(o))
+            o=Offer(name="faxde", provider="faxde", route="", typ="fax")
+            session.add(o)
+            session.commit()
+
+        x = yield self.proxy().callRemote('routes','abcdef123456789','sms')
+        self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus'])
+        x = yield self.proxy().callRemote('routes','abcdef123456789','fax')
+        self.failUnlessEqual(x, [])
+
+        with self.session() as session:
+            u = session.query(User).filter_by(name="test").first()
+            u.rights.append(Userright(o))
+
+        x = yield self.proxy().callRemote('routes','abcdef123456789','sms')
+        self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus'])
+        x = yield self.proxy().callRemote('routes','abcdef123456789','fax')
+        self.failUnlessEqual(x, ['faxde'])
+
+    def testDefaultRoutes(self):
+        '''test the defaultRoute function'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o,True))
+            o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms")
+            u.rights.append(Userright(o))
+            session.add(u)
+        d = self.proxy().callRemote('defaultRoute','abcdef123456789','sms')
+        d.addCallback(lambda x: self.failUnlessEqual(x,['sipgate_basic']))
+
+        return d
+
+    def testTelnumbers(self):
+        '''test the telefon validator'''
+        dl = []
+        d = self.proxy().callRemote('telnumber',["0123/456(78)","+4912346785433","00123435456-658"])
+        d.addCallback(lambda x: self.failUnlessEqual(x,True))
+        dl.append(d)
+
+        invalid=['xa','+1','1-23',';:+0','0123']
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, 701)
+            self.failUnlessEqual(exc.faultString, "No valid telnumber: '%s'" % invalid[0])
+        d = self.proxy().callRemote('telnumber', ['01234']+invalid)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        dl.append(d)
+
+        return defer.DeferredList(dl, fireOnOneErrback=True)
+
+    def testVaildEmail(self):
+        '''test vaild email adresses (got from wikipedia)'''
+        validmails=["niceandsimple@example.com"]
+        d = self.proxy().callRemote('email', validmails)
+        d.addCallback(lambda x: self.failUnlessEqual(x,True))
+        return d
+
+    def testInvaildEmail(self):
+        '''test invaild email adresses (got from wikipedia)'''
+        invalid=["Abc.example.com",'foo@t.de']
+        def f(exc):
+            self.failUnlessEqual(exc.faultCode, 702)
+            self.failUnlessEqual(exc.faultString, "No valid email: '%s'" % invalid[0])
+        d = self.proxy().callRemote('email', invalid)
+        d = self.assertFailure(d, Fault)
+        d.addCallback(f)
+        return d
+
+    def testBill(self):
+        '''test bill function'''
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+        d = self.proxy().callRemote('bill', apikey)
+        d.addCallback(lambda x: self.failUnlessEqual(x,{'total':{'price':0.0,'anz':0}}))
+
+        return d
+
+    def testBillWithPrice(self):
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            u.rights.append(Userright(o))
+            j = Job(info='i',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o))
+            u.jobs.append(j)
+
+            j = Job(info='a',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o))
+            u.jobs.append(j)
+
+        def f(ret):
+            self.failUnlessEqual(ret['total'],{'price':0.8,'anz':2})
+            self.failUnlessEqual(ret['sipgate_basic'],
+                    {'price':0.8,'anz':2,
+                        'info':{'i':{'price':0.4,'anz':1},
+                            'a':{'price':0.4,'anz':1},
+                            }
+                        })
+
+        d = self.proxy().callRemote('bill', apikey)
+        d.addCallback(f)
+        return d
+
+if __name__ == '__main__':
+    unittest.main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/model_validate.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,75 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from iro.model.schema import Offer
+from iro.model.decorators import vRoute, vTyp
+from iro.model.pool import data
+
+from iro.error import ValidateException
+
+from ..test_helpers.dbtestcase import DBTestCase
+from ..test_helpers.utils import DummyPool
+
+class ModelVaidatorTest(DBTestCase):
+    """tests for the model vaidators"""
+    def setUp(self):
+        DBTestCase.setUp(self)
+        self.pool = data.pool
+        data.pool = DummyPool()
+
+    def tearDown(self):
+        data.pool = self.pool
+        self.pool = None
+        DBTestCase.tearDown(self)
+
+    def testTyp(self):
+        with self.session() as session:
+            session.add(Offer(name="t",provider="p",typ="type"))
+
+        with self.session() as session:
+            self.assertEqual(vTyp("type",None),"type")
+            e = self.assertRaises(ValidateException,vTyp, "sss", None)
+            self.assertEqual(str(e),'700: Typ sss is not valid.')
+
+    def testRoute(self):
+        with self.session() as session:
+            session.add(Offer(name="t",provider="p",typ="type"))
+        self.assertEqual(vRoute("t",None,typ="type"),"t")
+        self.assertEqual(vRoute(["t","t"],None,typ="type"),["t"])
+        e = self.assertRaises(ValidateException,vRoute, "s", None, typ="type")
+        self.assertEqual(str(e),'700: Route s is not valid.')
+
+    def testRouteAllow(self):
+        with self.session() as session:
+            session.add(Offer(name="t",provider="p",typ="type"))
+        e = self.assertRaises(ValidateException,vRoute, "t", "foo", typ="type", allowString=False)
+        self.assertEqual(str(e),'700: foo must be a list of routes.')
+        e = self.assertRaises(ValidateException,vRoute, ["t"], "foo", typ="type", allowList=False)
+        self.assertEqual(str(e),'700: foo must be a route - No list of routes.')
+
+    def testRouteProvider(self):
+        with self.session() as session:
+            session.add(Offer(name="t",provider="p",typ="type"))
+        self.assertEqual(vRoute("p",None,typ="type"),"p")
+
+    def testRouteDefault(self):
+        self.assertEqual(vRoute("default",None,typ="type"),"default")
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/offer.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,177 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
+
+import io
+
+from iro.model import offer
+from iro.config import configParser
+from iro.offer import Offer, providers as OfferProviders, Provider
+from iro.model.schema import User, Offer as DBOffer, Userright
+from iro.controller.pool  import dbPool
+from iro.error import NoProvider
+from ..test_helpers.dbtestcase import DBTestCase
+
+class TestOffers(DBTestCase):
+    '''test config class'''
+    def setUp(self):
+        DBTestCase.setUp(self)
+        dbPool.start(reactor)
+
+    def tearDown(self):
+        for s in configParser.sections():
+            configParser.remove_section(s)
+        
+        dbPool.pool.stop()
+        offer.offers.clear()
+        
+        try:
+            del(OfferProviders["test"])
+        except KeyError:
+            pass
+        
+        offer.providers.clear()
+        DBTestCase.tearDown(self)
+
+    def testReloadList(self):
+        '''test if loadOffers will be fired by reloading Config'''
+        self.assertTrue(offer.loadOffers in configParser.reloadList)
+
+    @inlineCallbacks
+    def testExtendProviderUnknown(self):
+        '''test the extendProvider Function (route unknown)'''
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+
+        ret = yield offer.extendProvider(u, "sms", ["blub"])
+        self.assertEqual(ret, [])
+
+    @inlineCallbacks
+    def testExtendProviderKnown(self):
+        '''test the extendProvider Function (route known)'''
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="blub", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+
+        ret = yield offer.extendProvider(u, "sms", ["blub"])
+        self.assertEqual(ret, ["blub"])
+
+        ret = yield offer.extendProvider(u, "sms", ["blub", "blub"])
+        self.assertEqual(ret, ["blub"])
+
+    @inlineCallbacks
+    def testExtendProviderProvider(self):
+        '''test the extendProvider Function (provider known)'''
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="oh", provider="bla", route="a", typ="sms")
+            u.rights.append(Userright(o)) 
+        
+        offer.providers={"bla":Provider("bla", {"sms":["a","b","c"]})}
+
+        for l in [['bla'],['oh'],['oh','bla'],['bla','oh']]:
+            ret = yield offer.extendProvider(u, "sms", l)
+            self.assertEqual(ret, ["oh"])
+
+    @inlineCallbacks
+    def testExtendProviderDouble(self):
+        '''test the extendProvider Function (provider and name doubles)'''
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="oh", provider="bla", route="a", typ="sms")
+            u.rights.append(Userright(o)) 
+            o=DBOffer(name="a", provider="bla", route="b", typ="sms")
+            u.rights.append(Userright(o)) 
+        
+        offer.providers={"bla":Provider("bla",  {"sms":["a","b","c"]})}
+
+        ret = yield offer.extendProvider(u, "sms",  ["bla"])
+        self.assertEqual(ret, ["oh","a"])
+    
+        ret = yield offer.extendProvider(u, "sms",  ["a","bla"])
+        self.assertEqual(ret, ["a","oh"])
+
+        ret = yield offer.extendProvider(u, "sms",  ["bla", "a"])
+        self.assertEqual(ret, ["oh","a"])
+
+    @inlineCallbacks
+    def testExtendProviderDefault(self):
+        '''test the extendProvider Function (default)'''
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="blub3", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o,default=2)) 
+            o=DBOffer(name="blub", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o,default=1)) 
+            o=DBOffer(name="blub2", provider="bla2", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            o=DBOffer(name="blub4", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o,default=3)) 
+        
+        ret = yield offer.extendProvider(u, "sms", ["default"])
+        self.assertEqual(ret, ["blub","blub3",'blub4'])
+
+        ret = yield offer.extendProvider(u, "sms", "default")
+        self.assertEqual(ret, ["blub","blub3", 'blub4'])
+
+    
+    @inlineCallbacks
+    def testLoadOffers(self):
+        
+        class TestProvider(Provider):
+            def __init__(self,name):
+                Provider.__init__(self,name,{"sms":["a",]})
+        
+        with self.session() as session:
+            session.add(DBOffer(name="oh", provider="p", route="a", typ="sms"))
+
+        sample_config = """[p]
+typ = test
+"""
+        configParser.readfp(io.BytesIO(sample_config))
+        OfferProviders["test"]=TestProvider
+        yield offer.loadOffers()        
+        self.assertEqual(offer.offers.keys(),["oh"])
+        self.assertEqual(offer.providers.keys(),["p"])
+        self.assertEqual(offer.providers["p"].name,"p")
+        self.assertEqual(offer.providers["p"].typ,"test")
+        self.assertEqual(offer.offers["oh"],Offer(provider=offer.providers["p"], name="oh", route="a", typ="sms"))
+
+    def testLoadOffersUnknown(self):
+        with self.session() as session:
+            session.add(DBOffer(name="oh", provider="p", route="a", typ="sms"))
+
+        sample_config = """[p]
+typ = unknown
+"""
+        configParser.readfp(io.BytesIO(sample_config))
+        d = offer.loadOffers()        
+        self.assertFailure(d, NoProvider)
+        return d
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/offer_integrated.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,222 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.internet.defer import inlineCallbacks
+
+from datetime import datetime
+from decimal import Decimal 
+from mock import patch, Mock
+
+from iro.model.job import exJobs
+from iro.model.pool import data
+from iro.model.schema import User, Offer as DBOffer, Userright
+from iro.model.message import SMS, Mail
+from iro.model import offer
+
+from iro.controller.task import Task, taskPool
+from iro.telnumber import Telnumber
+
+from iro.offer import Smstrade, SMTP, Offer
+from iro.offer.smstrade import SmstradeException, StatusCode
+from iro.error import NoRouteForTask
+
+from ..test_helpers.dbtestcase import DBTestCase
+from ..test_helpers.utils import DummyPool
+
+def run( f,*a,**k):
+    return f(*a,**k)
+
+class IntegratedOfferTests(DBTestCase):
+    def setUp(self):
+        DBTestCase.setUp(self)
+        self.pool = data.pool
+        data.pool = DummyPool()
+        
+        self.taskPool = taskPool.run
+        taskPool.run = run 
+
+    def tearDown(self):
+        exJobs.clear()
+        data.pool = self.pool
+        self.pool = None
+        taskPool.run = self.taskPool
+        self.taskPool = None
+        DBTestCase.tearDown(self)
+
+
+    @patch("iro.model.job.datetime")
+    @patch("urllib.urlopen")
+    @inlineCallbacks
+    def testSmstrade(self, p_u, p_dt):
+        f = Mock()
+        f.readlines.return_value = ["100","12345678","0.055","1"]
+        p_u.return_value = f
+
+        p_dt.today.return_value = datetime(2000, 1, 2, 3, 4, 5)
+
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="s", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o))
+
+        
+        offer.providers["bla"] = Smstrade("bla")
+        offer.providers["bla"].key = "XXXXXX"
+        offer.offers["s"] = Offer("s",offer.providers["bla"],"basic","sms")
+
+        j = yield exJobs.create(u,[Telnumber("0123456789")],SMS("bla"),['s'],'tesched')
+        t = Task(Telnumber("0123456789"),j)
+        yield t.start()
+
+        self.assertEqual(self.log.e[2]['message'], ("Job(%s) to '0049123456789' ended sucecessfully via bla:basic."%j.dbjob,))
+        
+        with self.session() as session:
+            u = session.merge(u)
+            o = session.merge(o)
+            job = u.job(j.dbjob)
+            self.assertEqual(job.status,"sended")
+            self.assertEqual(job.info,"tesched")
+            self.assertEqual(len(job.messages),1)
+            
+            msg = job.messages[0]
+            self.assertEqual(msg.price,Decimal('0.0550'))
+            self.assertEqual(msg.isBilled,False)
+            self.assertEqual(msg.recipient,str(Telnumber('123456789')))
+            self.assertEqual(msg.date,datetime(2000, 1, 2, 3, 4, 5))
+            self.assertEqual(msg.offer,o)
+            self.assertEqual(msg.exID,"12345678")
+            self.assertEqual(msg.count,1)
+
+
+    @patch("urllib.urlopen")
+    @inlineCallbacks
+    def testSmstradeException(self, mock_urlopen):
+        f = Mock()
+        f.readlines.return_value = ["703"]
+        mock_urlopen.return_value = f
+
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="s", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+
+        offer.providers["bla"] = Smstrade("bla")
+        offer.providers["bla"].key = "XXXXXX"
+        offer.offers["s"] = Offer("s",offer.providers["bla"],"basic","sms")
+
+        j = yield exJobs.create(u,[Telnumber("0123456789")],SMS("bla"),['s'],'tesched')
+        t = Task(Telnumber("0123456789"),j)
+        yield t.start()
+
+        errors = self.flushLoggedErrors(SmstradeException)
+        self.assertEqual(len(errors), 1)
+        errors = self.flushLoggedErrors(NoRouteForTask)
+        self.assertEqual(len(errors), 1)
+        self.assertEqual(self.log.e[1]['why'], "Job(%s): Send to '0049123456789' failed via 's'"%j.dbjob)
+        self.assertEqual(self.log.e[3]['why'], "Error: Job(%s) to '0049123456789' failed."%j.dbjob)
+        
+        self.assertEqual(t.error, True)
+        self.assertEqual(str(self.log.e[1]['failure'].value),str(SmstradeException(StatusCode(703))))
+        
+        with self.session() as session:
+            u = session.merge(u)
+            o = session.merge(o)
+            job = u.job(j.dbjob)
+            self.assertEqual(job.status,"error")
+            self.assertEqual(len(job.messages),0)
+
+    @patch("smtplib.SMTP")
+    @inlineCallbacks
+    def testSmtp(self, p_s ):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="s", provider="bla", route=None, typ="mail")
+            u.rights.append(Userright(o)) 
+
+        offer.providers["bla"] = SMTP("bla")
+        offer.providers["bla"].SSL = False
+        offer.providers["bla"].TLS = False
+        offer.providers["bla"].host = "localhost"
+        offer.providers["bla"].port = 12345
+        offer.providers["bla"].user = ""
+        offer.providers["bla"].send_from = "frm@test.de"
+        offer.offers["s"] = Offer("s",offer.providers["bla"],None,"mail")
+
+        j = yield exJobs.create(u,["t@test.de"],Mail("bla",'msg',None),['s'],'tesched')
+        t = Task("t@test.de",j)
+        yield t.start()
+        
+        self.assertEqual(self.log.e[2]['message'], ("Job(%s) to 't@test.de' ended sucecessfully via bla:None."%j.dbjob,))
+        
+        with self.session() as session:
+            u = session.merge(u)
+            o = session.merge(o)
+            job = u.job(j.dbjob)
+            self.assertEqual(job.status,"sended")
+            self.assertEqual(job.info,"tesched")
+            self.assertEqual(len(job.messages),0)
+    
+    @patch("smtplib.SMTP")
+    @inlineCallbacks
+    def testSmtpException(self, p_s):
+        p_s.side_effect = IOError(111,"Connection refused")
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="s", provider="bla", route=None, typ="mail")
+            u.rights.append(Userright(o)) 
+
+        offer.providers["bla"] = SMTP("bla")
+        offer.providers["bla"].SSL = False
+        offer.providers["bla"].TLS = False
+        offer.providers["bla"].host = "localhost"
+        offer.providers["bla"].port = 12345
+        offer.providers["bla"].user = ""
+        offer.providers["bla"].send_from = "frm@test.de"
+        offer.offers["s"] = Offer("s",offer.providers["bla"],None,"mail")
+
+        j = yield exJobs.create(u,["t@test.de"],Mail("bla",'msg',None),['s'],'tesched')
+        t = Task("t@test.de",j)
+        yield t.start()
+
+        errors = self.flushLoggedErrors(NoRouteForTask)
+        self.assertEqual(len(errors), 1)
+        errors = self.flushLoggedErrors(IOError)
+        self.assertEqual(len(errors), 1)
+        self.assertEqual(self.log.e[1]['why'], "Job(%s): Send to 't@test.de' failed via 's'"%j.dbjob)
+        self.assertEqual(self.log.e[3]['why'], "Error: Job(%s) to 't@test.de' failed."%j.dbjob)
+        self.assertEqual(t.error, True)
+        self.assertEqual(str(self.log.e[1]['failure'].value),str(IOError(111,"Connection refused")))
+
+        with self.session() as session:
+            u = session.merge(u)
+            o = session.merge(o)
+            job = u.job(j.dbjob)
+            self.assertEqual(job.status,"error")
+            self.assertEqual(len(job.messages),0)
+       
+    def testUnicode(self):
+        '''tests unicode spefiica'''
+        pass
+    testUnicode.todo="to implement"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/pool.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,42 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.trial import unittest
+from mock import Mock
+import copy
+
+from iro.controller import pool
+
+class PoolTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self._pools = copy.copy(pool.pools)
+        
+    def tearDown(self):
+        del pool.pools[:]
+        pool.pools.extend(self._pools)
+
+    def testStartPool(self):
+        del pool.pools[:]
+        pool.pools.extend([Mock(),Mock()])
+        pool.startPool("blafo")
+        for i in pool.pools:
+            i.start.assert_called_with("blafo")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/reload.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,35 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from ..test_helpers.dbtestcase import DBTestCase
+
+
+class TestReload(DBTestCase):
+    '''tests for reloading feature'''
+
+    def testLog(self):
+        pass
+    testLog.todo = "reloading logfile is not implemented"
+
+    def testOffers(self):
+        pass
+    testOffers.todo = "reloading offers test not implemented"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/sipgate.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,106 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*-
+from twisted.trial import unittest
+from twisted.web import xmlrpc, server
+from twisted.internet import reactor
+from decimal import Decimal
+
+from iro.error import NoRoute, NoTyp, NeededOption, RejectRecipient
+from iro.telnumber import Telnumber
+from iro.model.message import SMS, Fax
+from iro.offer.sipgate import Sipgate
+
+
+class XMLRPCServer(xmlrpc.XMLRPC):
+    def lookupProcedure(self, procedurePath):
+        return self.l[procedurePath]
+
+class TestSipgateProvider(unittest.TestCase):
+
+    def setUp(self):
+        self.server = XMLRPCServer()
+        self.p = reactor.listenTCP(0, server.Site(self.server),
+                                   interface="127.0.0.1")
+        self.port = self.p.getHost().port
+
+    def tearDown(self):
+        self.server = None
+        return self.p.stopListening()
+
+    def getProvider(self, c=None):
+        _c={"username":"XXXXXXXX",
+            "password":"PPPPPPPP",
+            "typ": "sipgate"
+            }
+
+        if c:
+            _c.update(c)
+
+        ret = Sipgate("test")
+        ret.url="http://%%s:%%s@127.0.0.1:%d/" % self.port
+        ret.load(_c.items())
+        return ret
+
+    def testSendSMS(self):
+        p=self.getProvider()
+        content = u"Hello World - äüöß'\""
+       
+        @xmlrpc.withRequest
+        def f(request, args):
+            self.assertEqual(request.getUser(),"XXXXXXXX")
+            self.assertEqual(request.getPassword(),"PPPPPPPP")
+            self.assertEqual(args,{'Content': u'Hello World - äüöß\'"',
+                'TOS': 'text',
+                'RemoteUri': 'sip:491701234567@sipgate.net'})
+            return {"SessionID":"", "StatusCode":200, "StatusString":"Method success"}
+
+        self.server.l = {"samurai.SessionInitiate": f}
+
+        def s(r):
+            self.assertEqual(r.provider, p)
+            self.assertEqual(r.route, None)
+            self.assertEqual(r.costs, Decimal('0.079'))
+            self.assertEqual(r.exID, '')
+            self.assertEqual(r.count, 1)
+
+        d = p.send("sms", Telnumber("01701234567"), SMS(content,None))
+        d.addCallback(s)
+        return d
+
+
+    def testNeededOption(self):
+        s= self.getProvider()
+        self.assertEqual(s.username, "XXXXXXXX")
+        self.assertEqual(s.password, "PPPPPPPP")
+        self.assertEqual(s.price_sms, '0.079')
+        self.assertEqual(s.price_fax, '0.03')
+
+        self.assertRaises(NeededOption, s.load,[])
+
+    def testSendFunc(self):
+        s = self.getProvider()
+        p = s.getSendFunc("sms",None)
+        self.assertEqual(p.func, s.send)
+        self.assertEqual(p.args, ("sms",))
+        self.assertRaises(NoRoute,s.getSendFunc,"sms","foo")
+        self.assertRaises(NoTyp,s.getSendFunc,"mail2",None)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/smstrade.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,151 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.trial import unittest
+from decimal import Decimal
+from mock import patch, Mock
+
+from iro.error import NoRoute, NoTyp, NeededOption, RejectRecipient
+from iro.telnumber import Telnumber
+from iro.model.message import SMS
+from iro.offer.smstrade import Smstrade, SmstradeException, StatusCode
+
+
+HOST = "localhost"
+PORT = 9999
+
+class TestSMStradeProvider(unittest.TestCase):
+
+    def getProvider(self, c=None):
+        _c={"key":"XXXXXXXX",
+            "typ":"smstrade",
+            }
+
+        if c:
+            _c.update(c)
+
+        ret = Smstrade("test")
+        ret.load(_c.items())
+        return ret
+
+    @patch("urllib.urlopen")
+    def testSendSMS(self,mock_urlopen):
+        f = Mock()
+        f.readlines.return_value = ["100","12345678","0.055","1"]
+        mock_urlopen.return_value = f
+
+        params = ["key=XXXXXXXX","to=00491701234567", "message=Hello+World", "route=gold", "message_id=1", "cost=1","count=1",'charset=utf-8']
+        params.sort()
+        
+        p=self.getProvider()
+        content = "Hello World"
+        r = p.send("gold", Telnumber("01701234567"), SMS(content,None))
+
+        ca = mock_urlopen.call_args[0]
+        c=ca[1].split("&")
+        c.sort()
+        
+        self.assertEqual(ca[0],"https://gateway.smstrade.de")
+        self.assertEqual(c,params)
+        self.assertEqual(f.readlines.call_count,1)
+
+        self.assertEqual(r.provider, p)
+        self.assertEqual(r.route, 'gold')
+        self.assertEqual(r.costs, Decimal('0.055'))
+        self.assertEqual(r.exID, '12345678')
+        self.assertEqual(r.count, 1)
+
+    def testStatusCode(self):
+        s = StatusCode(10,"12345678","1.10",1)
+        self.assertEqual(str(s),'10: Empfaengernummer nicht korrekt.')
+        self.assertEqual(int(s),10)
+        self.assertEqual(s.count,1)
+        self.assertEqual(s.costs,Decimal("1.10"))
+        self.assertEqual(s.exID,'12345678')
+
+
+    def testUnknownStatusCode(self):
+        s = StatusCode(999)
+        self.assertEqual(str(s),'999: unknown statuscode.')
+        self.assertEqual(int(s),999)
+        self.assertEqual(s.count,0)
+        self.assertEqual(s.costs,Decimal("0.00"))
+        self.assertEqual(s.exID, None)
+
+
+
+    def testRejectRecipient(self):
+        p=self.getProvider()
+        content = "Hello World"
+        e = self.assertRaises(RejectRecipient, p.send, "basic", Telnumber("+331701234567"), SMS(content,None))
+        self.assertEqual(str(e),'Reject recipient(00331701234567): None')
+
+    @patch("urllib.urlopen")
+    def testRejectRecipient70(self,mock_urlopen):
+        f = Mock()
+        f.readlines.return_value = ["70"]
+        mock_urlopen.return_value = f
+
+        p=self.getProvider()
+        content = "Hello World"
+        self.assertRaises(RejectRecipient, p.send , "basic", Telnumber("01701234567") ,SMS(content,None))
+
+        f.readlines.return_value = ["71"]
+        e = self.assertRaises(RejectRecipient, p.send , "basic", Telnumber("01701234567"), SMS(content,None))
+        self.assertEqual(str(e),'Reject recipient(00491701234567): 71: Feature nicht ueber diese Route moeglich.')
+
+    @patch("urllib.urlopen")
+    def testUnknwonStatuscode(self,mock_urlopen):
+        f = Mock()
+        f.readlines.return_value = ["703"]
+        mock_urlopen.return_value = f
+
+        p=self.getProvider()
+        content = "Hello World"
+        e = self.assertRaises(SmstradeException, p.send , "basic", Telnumber("01701234567"), SMS(content,None))
+        self.assertEqual(str(e),'950: Error in external API.\n703: unknown statuscode.')
+
+    @patch("urllib.urlopen")
+    def testSmstradeException(self,mock_urlopen):
+        f = Mock()
+        f.readlines.return_value = ["10"]
+        mock_urlopen.return_value = f
+
+        p=self.getProvider()
+        content = "Hello World"
+        e = self.assertRaises(SmstradeException, p.send , "basic", Telnumber("01701234567"), SMS(content,None))
+        self.assertEqual(str(e),'950: Error in external API.\n10: Empfaengernummer nicht korrekt.')
+
+
+
+    def testNeededOption(self):
+        s= self.getProvider()
+        self.assertEqual(s.key, "XXXXXXXX")
+
+        self.assertRaises(NeededOption, s.load,[])
+
+    def testSendFunc(self):
+        s = self.getProvider()
+        p = s.getSendFunc("sms","basic")
+        self.assertEqual(p.func, s.send)
+        self.assertEqual(p.args, ("basic",))
+        self.assertRaises(NoRoute,s.getSendFunc,"sms","foo")
+        self.assertRaises(NoTyp,s.getSendFunc,"mail2","basic")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/smtp.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,170 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.trial import unittest
+
+import email
+from email.header import decode_header
+import base64
+from ..test_helpers.smtp_helper import TestSMTPServer
+
+from mock import patch, Mock
+import smtplib
+
+from iro.error import NoRoute, NoTyp, NeededOption
+from iro.model.message import Mail
+from iro.offer.smtp import SMTP
+
+HOST = "localhost"
+PORT = 9999
+
+class TestSMTPProvider(unittest.TestCase):
+    def setUp(self):
+        self.smtp_server = TestSMTPServer((HOST, PORT))
+        self.smtp_server.start()
+
+    def tearDown(self):
+        self.smtp_server.close()
+
+    def getSMTP(self, c=None):
+        _c={"send_from":"send@t.de",
+            "host":HOST,
+            "port":PORT,
+            "typ":"smtp",
+            }
+
+        if c:
+            _c.update(c)
+
+        ret = SMTP("test")
+        ret.load(_c.items())
+        return ret
+
+    @patch("iro.model.message.formatdate")
+    def testSendMail(self,mock_f):
+        mock_f.return_value="Wed, 21 Mar 2012 17:16:11 +0100"
+        p=self.getSMTP()
+        content = "sadfadfgwertsdgsdf\n\nsdfgaerasdfsad\nadfasdf"
+        p.send("t@t.de", Mail("sub", content, None))
+
+
+        self.assertEqual(len(self.smtp_server.rcvd), 1)
+        fromaddr, toaddrs, message = self.smtp_server.rcvd[0]
+        msg = email.message_from_string(message)
+        
+        self.assertEqual(fromaddr,"send@t.de")
+        self.assertEqual(msg.get_all("From"),["send@t.de"])
+        self.assertEqual(toaddrs,["t@t.de"])
+        self.assertEqual(msg.get_all("To"),["t@t.de"])
+        self.assertEqual(msg.get_all("Date"),["Wed, 21 Mar 2012 17:16:11 +0100"])
+        self.assertEqual(decode_header(msg.get("Subject")),[("sub","utf-8")])
+        self.assertEqual(base64.b64decode(msg.get_payload()),content)
+
+    def testSendMailExtraFrm(self):
+        p=self.getSMTP()
+        content = ""
+        p.send("t@t.de", Mail("sub", content, "f@t.de"))
+
+        self.assertEqual(len(self.smtp_server.rcvd), 1)
+        fromaddr, toaddrs, message = self.smtp_server.rcvd[0]
+        msg = email.message_from_string(message)
+        
+        self.assertEqual(fromaddr,"f@t.de")
+        self.assertEqual(msg.get_all("From"),["f@t.de"])
+
+    def testSendMailException(self):
+        p=self.getSMTP({"port":PORT-1})
+        content = ""
+        self.assertRaises(IOError, p.send, "t@t.de", Mail("sub", content, "f@t.de"))
+
+        self.assertEqual(len(self.smtp_server.rcvd), 0)
+
+    @patch("smtplib.SMTP_SSL")
+    def testSSLSendMail(self,mock_ssl):
+        def se(*args):
+            return smtplib.SMTP(*args)
+        mock_ssl.side_effect=se
+
+        p=self.getSMTP({"SSL":True})
+        content = "sadfadfgwertsdgsdf\n\nsdfgaerasdfsad\nadfasdf"
+        p.send("t@t.de", Mail("sub", content, None))
+
+        self.assertEqual(mock_ssl.call_count,1)
+
+        self.assertEqual(len(self.smtp_server.rcvd), 1)
+
+    @patch("smtplib.SMTP")
+    def testTLSSendMail(self,mock_smtp):
+        mock_s = Mock()
+        mock_smtp.return_value = mock_s
+
+        p=self.getSMTP({"TLS":True})
+        content = "sadfadfgwertsdgsdf\n\nsdfgaerasdfsad\nadfasdf"
+        p.send("t@t.de", Mail("sub", content, None))
+        
+        mock_s.starttls.assert_called_once_with()
+        self.assertEqual(mock_s.sendmail.call_count,1)
+        self.assertEqual([i[0] for i in mock_s.method_calls],["starttls","sendmail","quit"])
+
+    @patch("smtplib.SMTP")
+    def testLoginSendMail(self,mock_smtp):
+        mock_s = Mock()
+        mock_smtp.return_value = mock_s
+
+        p=self.getSMTP({"user":"user","password":"pw"})
+        content = "sadfadfgwertsdgsdf\n\nsdfgaerasdfsad\nadfasdf"
+        p.send("t@t.de", Mail("sub", content, None))
+        
+        mock_s.login.assert_called_once_with("user","pw")
+        self.assertEqual(mock_s.sendmail.call_count,1)
+        self.assertEqual([i[0] for i in mock_s.method_calls],["login","sendmail","quit"])
+
+    
+    def testNeededOption(self):
+        c={"send_from":"send@t.de",
+            "host":HOST,
+            "port":PORT,
+            "user":"u",
+            "password":"p",
+            "typ":"smtp",
+            }
+        s = self.getSMTP(c)
+        self.assertEqual(s.send_from, "send@t.de")
+        self.assertEqual(s.host, HOST)
+        self.assertEqual(s.port, PORT)
+        self.assertEqual(s.user, "u")
+        self.assertEqual(s.password, "p")
+        self.assertEqual(s.SSL,False)
+        self.assertEqual(s.TLS,False)
+
+        c.update({"TLS":True, "SSL":True})
+        s = self.getSMTP(c)
+        self.assertEqual(s.SSL,True)
+        self.assertEqual(s.TLS,True)
+        
+        del c["host"]
+        self.assertRaises(NeededOption, s.load, c)
+
+    def testSendFunc(self):
+        s = self.getSMTP()
+        self.assertEqual(s.getSendFunc("mail",None), s.send)
+        self.assertRaises(NoRoute,s.getSendFunc,"mail","foo")
+        self.assertRaises(NoTyp,s.getSendFunc,"mail2","foo")
--- a/iro/tests/stopableServer.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-import ConfigParser
-
-import threading
-
-from multiprocessing import Queue
-from multiprocessing.managers import BaseManager
-
-from iro import xmlrpc,anbieter
-from iro.user import User, Admin
-from iro.iro import MySMTP,MySmstrade,MyUserDB
-from iro.job import SMSJob, FAXJob, MailJob
-from iro.joblist import Joblist
-from iro.providerlist import Providerlist
-
-class StoppableXMLRPCServer(xmlrpc.SecureUserDBXMLRPCServer, threading.Thread):
-    running=False
-    def __init__(self, *args, **kwargs):
-        xmlrpc.SecureUserDBXMLRPCServer.__init__(self, *args, **kwargs)
-        threading.Thread.__init__(self)
-        self.timeout=.5
-   
-    def start(self):
-        self.running=True
-        threading.Thread.start(self)
- 
-   
-    def run(self):
-        # *serve_forever* muss in einem eigenen Thread laufen, damit man es
-        # unterbrochen werden kann!
-        while (self.running):
-            try:
-                self.handle_request()
-            except :
-                break
-   
-    def stop(self):
-        if (self.running):
-            self.running=False
-            self.server_close()
-            self.join()
-
-    def __del__(self):
-        self.stop()
- 
-    def __enter__(self):
-        self.start()
-        return self
-
-    def __exit__(self,type,value,traceback):
-        self.stop()
-
-
-class Internal:
-    pass
-
-def init_server():
-    userlist=[{"name":"test","password":"test",  "class":User},
-              {"name":"test2","password":"test2", "class": User},
-              {"name":"admin","password":"admin", "class": Admin}]
-
-
-    
-    class MyManager(BaseManager):
-        pass
-
-    internal=Internal()
-    
-    MyManager.register('SMSJob', SMSJob) 
-    MyManager.register('FaxJob', FAXJob) 
-    MyManager.register('MailJob',MailJob) 
-    MyManager.register('Providerlist',Providerlist) 
-    manager = MyManager()
-    manager.start()
-    
-    internal.manager=manager
-    
-    #anbieter erzeugen und konfigurieren
-    sip=anbieter.sipgate()
-    sip.read_basic_config("iro.conf")
-    
-    localhost=MySMTP()
-    localhost.read_basic_config("iro.conf")
-
-    smstrade=MySmstrade()
-    smstrade.read_basic_config("iro.conf")
-
-    #Benutzerdatenbank erstellen
-    queue = Queue()
-    internal.queue=queue
-    provider=Providerlist()
-    internal.provider=provider
-    provider.add("sipgate", sip, ["sms", "fax", ])
-    provider.add("smstrade", smstrade, ["sms", ])
-    provider.add("geonet", None, ["sms", "fax", ])
-    provider.add("fax.de", None, ["sms", "fax", ])
-    provider.add("localhost", localhost, ["mail", ])
-    provider.setDefault("sms","smstrade")
-    provider.setDefault("fax","sipgate")
-    provider.setDefault("mail","localhost")
-    jobqueue=Joblist(manager,  queue, provider)
-    internal.jobqueue=jobqueue
-    userdb=MyUserDB(userlist,jobqueue)
-    internal.userdb=userdb
-
-
-    #Server starten
-    cp = ConfigParser.ConfigParser()
-    cp.read(["iro.conf"])
-    cert=cp.get('server', 'cert')
-    key=cp.get('server', 'key')
-    serv = StoppableXMLRPCServer(addr=("localhost", 8000), 
-                                      userdb=userdb,
-                                      certificate=cert,privatekey=key,
-                                      logRequests=False)
-    serv.relam="xmlrpc"
-    internal.serv=serv
-    return internal
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/task.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,188 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, Deferred
+
+from Queue import deque
+
+from iro.model.schema import User, Offer as DBOffer, Userright
+from iro.model.message import SMS
+from iro.model.status import Status
+from iro.model.offer import offers
+from iro.model.job import exJobs
+
+from iro.controller.task import createJob, Task
+from iro.controller.pool  import taskPool, dbPool
+
+from iro.offer import Offer, Provider
+
+from iro.error import NoRouteForTask,RejectRecipient
+from iro.telnumber import Telnumber
+
+from ..test_helpers.dbtestcase import DBTestCase
+
+class TaskTestCase(DBTestCase):
+    def setUp(self):
+        DBTestCase.setUp(self)
+        dbPool.start(reactor)
+
+    def tearDown(self):
+        exJobs.clear()
+        offers.clear()
+        dbPool.pool.stop()
+        taskPool.pool.q.queue = deque()
+        DBTestCase.tearDown(self)
+ 
+class TestTasks(TaskTestCase):
+   
+    @inlineCallbacks
+    def testCreateSMS(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+        
+        job = yield createJob(u,[Telnumber('0123325456')],SMS('sms'),[])
+        
+        self.assertEqual(taskPool.pool.q.qsize(),1)
+        
+        self.assertEqual(job.tasks.keys(),[Telnumber('0123325456')])
+        self.assertIsInstance(job.tasks[Telnumber('0123325456')], Task)
+
+    @inlineCallbacks
+    def testRun(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="test", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+        
+        p=Provider(name="p", typs={"sms":["test",]})
+        def send(typ,route,recipient,message):
+            return Status(provider=p, route=route)
+        p.send=send
+        offers["test"] = Offer("test",provider=p, route="test", typ="sms")
+
+        exjob = yield exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test'])
+        
+        task=Task(Telnumber('123456789'), exjob)
+        task.d=Deferred()
+        task._run()
+        ret = yield task.d
+        self.assertIsInstance(ret, Status)
+        self.assertEqual(ret.provider, p)
+        self.assertEqual(ret.route, "test")
+
+    def testNoRoute(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+        
+        def c(exjob):
+            task = Task(Telnumber('123456789'), exjob)
+            task.d = Deferred()
+            task._run()
+            self.assertFailure(task.d, NoRouteForTask)
+            return task.d
+
+        d = exJobs.create(u, [Telnumber('123456789')], SMS('test'), [])
+        d.addCallback(c)
+        return d
+        
+    def testNoRoute2(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="test", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            o=DBOffer(name="test2", provider="bla", route="basic2", typ="sms")
+            u.rights.append(Userright(o)) 
+
+        p=Provider(name="p", typs={"sms":["basic","basic2"]})
+        def send(typ,route,recipient,message):
+            raise RejectRecipient(recipient)
+        p.send=send
+        offers["test"] = Offer("test",provider=p, route="basic", typ="sms")
+        offers["test2"] = Offer("test2",provider=p, route="basic2", typ="sms")
+
+        def c(exjob):
+            task = Task(Telnumber('123456789'), exjob)
+            task.d = Deferred()
+            task._run()
+            self.assertFailure(task.d, NoRouteForTask)
+            return task.d
+
+        d = exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test','test2'])
+        d.addCallback(c)
+        return d
+
+    def testChaining(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(u)
+            o=DBOffer(name="test", provider="bla", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            o=DBOffer(name="test2", provider="bla", route="basic2", typ="sms")
+            u.rights.append(Userright(o)) 
+
+        p=Provider(name="p", typs={"sms":["basic","basic2"]})
+        def send(typ,route,recipient,message):
+            if route=="basic":
+                raise Exception("oh my god an exception")
+            return Status(p,route)
+        p.send=send
+        offers["test"] = Offer("test",provider=p, route="basic", typ="sms")
+        offers["test2"] = Offer("test2",provider=p, route="basic2", typ="sms")
+
+        def c(exjob):
+            task = Task(Telnumber('123456789'), exjob)
+            task.d = Deferred()
+            task.d.addCallback(stat, task)
+            task._run()
+            return task.d
+       
+        def stat(status, task): 
+            errors = self.flushLoggedErrors(Exception)
+            self.assertEqual(len(errors), 1)
+            self.assertEqual(self.log.e[1]['why'], "Job(%s): Send to '0049123456789' failed via 'test'"% task.job.dbjob)
+            self.assertIsInstance(status, Status)
+            self.assertEqual(status.provider, p)
+            self.assertEqual(status.route, "basic2")
+
+        d = exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test','test2'])
+        d.addCallback(c)
+        return d
+
+
+
+    def testSetStatus(self):
+        task=Task(Telnumber('123456789'), None)
+        self.assertEqual(task.status,None)
+        self.assertEqual(task.error,False)
+
+        self.assertEqual(task.setStatus("fooja"),"fooja")
+        self.assertEqual(task.status,"fooja")
+
+    def testSetError(self):
+        task=Task(Telnumber('123456789'), None)
+        self.assertEqual(task.setError("fooja"),"fooja")
+        self.assertEqual(task.status,"fooja")
+        self.assertEqual(task.error,True)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/telnumber.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,105 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+try:
+    import unittest
+    unittest.case
+except AttributeError:
+    import unittest2 as unittest
+
+from iro.validate import vTel
+from iro.error import InvalidTel
+from iro.telnumber import Telnumber
+
+class testTelefonnumbers(unittest.TestCase):
+    """tests for telefonnumbers"""
+
+    def testMultipleTelnumbers(self):
+        '''test the telefon validator'''
+        ret = vTel(["0123/456(78)","+4912346785433","00123435456-658"], None)
+        x=[Telnumber('+4912345678'),Telnumber('012346785433'),Telnumber('+123435456658')]
+        self.assertEqual(ret,x)
+
+    def testInvalidTelnumbers(self):
+        '''invalid telnumbers'''
+        
+        numbers=['xa','+1','1-23',';:+0','0123']
+        
+        for number in numbers:
+            with self.assertRaises(InvalidTel) as e:
+                vTel([number], None)
+            self.assertEqual(e.exception.number,number)
+
+        with self.assertRaises(InvalidTel) as e:
+            vTel(['01234']+numbers, None)
+        self.assertEqual(e.exception.number,numbers[0])
+
+    def testDoubles(self):
+        ret = vTel(["0123/456(78)","+4912345678","004912345678"], None)
+        x=[Telnumber('+4912345678')]
+        self.assertEqual(ret,x)
+
+    def equalNumber(self, tel1, tel2):
+        self.assertEqual(tel1.number, tel2.number)
+        self.assertEqual(tel1.land, tel2.land)
+        
+    def testWrongNumber(self):
+        telnum=Telnumber()
+        self.assertRaises(InvalidTel, telnum.createNumber,  "hallo")
+        self.assertRaises(InvalidTel, telnum.createNumber,  "0?242")
+   
+    def testNumber(self):
+        telnum=Telnumber("0345-94103640")
+        telnum2=Telnumber("+49345/94103640")
+        telnum3=Telnumber("00493459410364-0")
+        telnum4=Telnumber("+49(0)345-94103640")
+        
+        self.assertEqual(telnum.land, "49")
+        self.assertEqual(telnum.number, "34594103640")
+        
+        self.equalNumber(telnum, telnum2)
+        self.equalNumber(telnum, telnum3)
+        self.equalNumber(telnum, telnum4)
+
+    def testEqual(self):
+        telnum=Telnumber("0345-94103640")
+        telnum2=Telnumber("+49345/94103640")
+        li=[]
+        self.assertEqual(telnum == telnum2, True)
+        self.assertEqual(telnum <> telnum2, False)
+        self.assertEqual(telnum,  telnum2)
+        self.assertEqual(telnum in li,False)
+        li.append(telnum)
+        self.assertEqual(telnum in li,True)
+        self.assertEqual(telnum2 in li,True)
+
+    def testHash(self):
+        telnum=Telnumber("0345-94103640")
+        self.assertEqual(hash(telnum),hash("004934594103640"))
+        self.assertNotEqual(hash(telnum),hash("004934594103641"))
+
+    def testString(self):
+        telnum=Telnumber("0345-94103640")
+        self.assertEqual(str(telnum),"004934594103640")
+
+    def testRepr(self):
+        telnum=Telnumber("0345-94103640") 
+        self.assertEqual(repr(telnum),"<Telnumber 004934594103640>")
--- a/iro/tests/testJob.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-import xmlrpclib
-from stopableServer import init_server
-from iro.anbieter.content import SMS,FAX,Mail
-
-class TestServer(unittest.TestCase):
-    
-    def setUp(self):
-        self.i = init_server()
-        self.serv=self.i.serv
-        self.serv.start()
-
-    def tearDown(self):
-        self.serv.stop()
-
-   
-    def SendSMS(self,msg):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)
-        id=client.startSMS(msg,["01234", ] )
-        self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name':  unicode(msg)}} )
-        ele=self.i.queue.get(.1)
-        self.assertEqual(ele.getRecipients(),["01234", ] )
-        self.assertNotEqual(ele.getMessage(),SMS('') )
-        self.assertEqual(ele.getMessage(),SMS(msg) )
-    
-    def testSimpleSMS(self):
-        self.SendSMS("test")
-    
-    def testSpecialCharacters(self):
-        self.SendSMS(u"!\"§$%&/()=?\'")
-        self.SendSMS(u"@ł€ł€¶ŧł¼¼½¬¬↓ŧ←ĸ↓→øđŋħ“”µ·…–|")
-
-    def testSendFAX(self):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)
-        msg="2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ"
-        id=client.startFAX("test",xmlrpclib.Binary(msg),["01234", ] )
-        self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name':  'test'}} )
-        ele=self.i.queue.get(.1)
-        self.assertEqual(ele.getRecipients(),["01234", ] )
-        self.assertEqual(ele.getMessage(),FAX('test','',[msg]))
-
-    def testDoubleFAX(self):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)
-        msg="2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ"
-        pdf=open('tests/test.pdf').read()
-        id=client.startFAX("test",[xmlrpclib.Binary(msg),xmlrpclib.Binary(pdf)],["01234", ] )
-        self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name':  'test'}} )
-        ele=self.i.queue.get(.1)
-        self.assertEqual(ele.getRecipients(),["01234", ] )
-        self.assertEqual(ele.getMessage(),FAX('test','',[msg, pdf]))
-
-    def testSendMail(self):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)
-        msg=u"2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ"
-        id=client.startMail("test",msg,["test@test.de", ],'absender@test.de' )
-        self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name':  'test'}} )
-        ele=self.i.queue.get(.1)
-        self.assertEqual(ele.getRecipients(),["test@test.de", ] )
-        self.assertEqual(ele.getMessage(),Mail('test',msg,'absender@test.de'))
-        self.assertEqual(ele.getMessage().as_string(),"""Content-Type: text/plain; charset="utf-8"
-MIME-Version: 1.0
-Content-Transfer-Encoding: base64
-Subject: =?utf-8?q?test?=
-
-MjEzNHdlcmdzZGZnNHc1NnEzNDEzNMOmxb/DsMSRw6bDsMW/xJHFi8KzQMK8xafDpsOwxJHFi8WC
-4oKswrbFp+KCrMK2xac=
-""")
-        sub=u"³¼½ſðđŋſ€¼½ÖÄÜß"
-        id=client.startMail(sub,msg,["test@test.de", ],'absender@test.de' )
-        self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': sub}})
-        ele=self.i.queue.get(.1)
-        self.assertEqual(ele.getMessage(),Mail(sub, msg, 'absender@test.de'))
-
-if __name__ == "__main__":
-    unittest.main()  
--- a/iro/tests/testWorker.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-import logging
-from time import sleep
-
-from multiprocessing import Queue
-from multiprocessing.managers import BaseManager, ListProxy
-
-from iro.worker import Worker
-from iro.job import Job, SMSJob
-from iro.anbieter.anbieter import anbieter
-from iro.anbieter.content import SMS
-from iro.providerlist import Providerlist
-
-from logging.handlers import BufferingHandler
-
-class MyHandler(BufferingHandler):
-    def __init__(self,buffer=None):
-        '''BufferingHandler takes a "capacity" argument
-        so as to know when to flush. As we're overriding
-        shouldFlush anyway, we can set a capacity of zero.
-        You can call flush() manually to clear out the
-        buffer.
-            buffer: log messages to this buffer, needed f.ex for processes or threads'''
-        BufferingHandler.__init__(self, 0)
-        self.buffer=buffer
-
-    def shouldFlush(self):
-        return False
-
-    def emit(self, record):
-        if record.exc_info:                                                      #sonst geht das append schief, weil nicht picklebar
-            record.exc_info=record.exc_info[:-1]                                              
-        self.buffer.append(record.__dict__)
-
-
-class BadJob(Job):
-    def start(self,id=None):
-        Job.start(self,id)
-        raise Exception("Error")
-
-#einen Manager anlegen, der Job und eine Liste anbietet
-class MyManager(BaseManager):
-    pass
-MyManager.register('Job', Job) 
-MyManager.register('SMSJob', SMSJob) 
-MyManager.register('BadJob', BadJob) 
-MyManager.register('list', list, ListProxy)
-MyManager.register('Providerlist',Providerlist) 
-
-class TestWorker(unittest.TestCase):
-    def setUp(self):
-        #erstelle eine Queue&Manager
-        self.manager = MyManager()
-        self.queue = Queue()
-        
-        #manager starten, damit wir Logging anpassen können
-        self.manager.start()
-        self.setUpLogging()
-
-        self.providerlist=self.manager.Providerlist()
-        self.providerlist.add("test", anbieter() , ["sms",  ])
-        
-        #eigentlich Workerprocess starten
-        self.worker= Worker(self.queue)
-        self.worker.start()
-
-    def tearDown(self):
-        #Thread&Queue stoppen
-        self.stop()
-        
-        #Logging änderungen rückgängig
-        self.tearDownLogging()
-        self.manager.shutdown()
-    
-    def stop(self):
-        self.queue.close()
-        self.queue.join_thread()
-        self.worker.terminate()
-
-
-    def setUpLogging(self):
-        '''Logging so umbasteln, das wir alle logging Meldung in self.buf sind'''
-        #wir brauchen eine threadsichere liste
-        self.buffer=self.manager.list()
-        
-        #Handler erstellen, der in den Buffer schreibt
-        self.handler = h = MyHandler(self.buffer)
-        self.logger =l= logging.getLogger()
-        
-        #Level anpassen 
-        l.setLevel(logging.DEBUG)
-        h.setLevel(logging.DEBUG)
-        l.addHandler(h)
-
-    def tearDownLogging(self):
-        '''crazy logging hacks wieder entfernen'''
-        self.logger.removeHandler(self.handler)
-        self.logger.setLevel(logging.NOTSET)
-        self.handler.close()
-
-
-    def testJob(self):
-        '''einen Job verarbeiten'''
-        job=self.manager.Job(None,None,"test")
-        self.assertEqual(job.getStatus(),("init",{}))
-        self.queue.put(job)
-        sleep(.1)
-        self.stop()
-        self.assertEqual(job.getStatus(),("started",{}))
-        self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"],
-                [(20,'Workerprocess läuft nun...'),
-                 (20,'ein neuer Job(1)'),
-                 (20,'Job(1) fertig ;)')])
-
-    def testBadJob(self):
-        '''einen Job verarbeiten, der fehlschlägt'''
-        job=self.manager.BadJob(None,None,"test")
-        self.assertEqual(job.getStatus(),("init",{}))
-        self.queue.put(job)
-        sleep(.1)
-        self.stop()
-        self.assertEqual(job.getStatus(),("error",{}))
-        self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"],
-                [(20,'Workerprocess läuft nun...'),
-                 (20,'ein neuer Job(1)'),
-                 (40,'Job(1) fehlgeschlagen :(')])
-        error=Exception('Error')
-        self.assertEqual(self.buffer[-1]['exc_info'][0],type(error))
-        self.assertEqual(str(self.buffer[-1]['exc_info'][1]),str(error))
-
-    def testSMSJob(self):
-        job=self.manager.SMSJob(self.providerlist, "test", "name", SMS("message"),[012345])
-        self.assertEqual(job.getStatus(),("init",{}))
-        self.queue.put(job)
-        sleep(.1)
-        self.stop()
-        self.assertEqual(job.getStatus(),("sended",{'failed': [], 'good': []}))
-        self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"],
-                [(20,'Workerprocess läuft nun...'),
-                 (20,'ein neuer Job(1)'),
-                 (20,'Job(1) fertig ;)')])
-
-
-if __name__ == "__main__":
-    unittest.main()  
--- a/iro/tests/testXMLRPCServer.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-import xmlrpclib
-from stopableServer import init_server
-
-class TestServer(unittest.TestCase):
-    
-    def setUp(self):
-        self.i = init_server()
-        self.serv=self.i.serv
-        
-        self.serv.start()
-
-    def tearDown(self):
-        self.serv.stop()
-
-    def testLogin(self):
-        self.assertEqual(xmlrpclib.Server("https://test:test@localhost:8000").status(), {})
-        self.assertEqual(xmlrpclib.Server("https://test2:test2@localhost:8000").status(), {})
-        self.assertRaises(xmlrpclib.ProtocolError, xmlrpclib.Server("https://test2:test@localhost:8000").status)
-        self.assertRaises(xmlrpclib.ProtocolError,xmlrpclib.Server ("https://test:test2@localhost:8000").status)
-    
-    def testsendSMS(self):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)
-        id=client.startSMS("test",["01234", ] )
-        self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name':  'test'}} )
-        
-    def testTwoUser(self):
-        u1="https://test:test@localhost:8000"
-        u2="https://test2:test2@localhost:8000"
-        admin="https://admin:admin@localhost:8000"
-        client1=xmlrpclib.Server(u1)
-        client2=xmlrpclib.Server(u2)
-        admin=xmlrpclib.Server(admin)
-        id1=client1.startSMS("test",["01234"] )
-        self.assertEqual(client2.status(),{} ) 
-        self.assertEqual(admin.status(id1),{id1: {'status': ['init', {}], 'name':  'test'}} )
-        id2=client2.startSMS("test2",["01234"] )
-        self.assertNotEqual(id1, id2)
-        self.assertEqual(client1.status(),{id1: {'status': ['init', {}], 'name':  'test'}})
-        self.assertEqual(client2.status(),{id2: {'status': ['init', {}], 'name':  'test2'}})
-        self.assertEqual(admin.status(),{id1: {'status': ['init', {}], 'name':   'test'},
-                        id2: {'status': ['init', {}], 'name':   'test2'}} )
-        
-        self.assertEqual(client2.status(id1), {})
-        self.assertEqual(client1.status(id2), {})
-        
-    def testGetProvider(self):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)       
-        self.assertEqual(client.getProvider("sms"), ["fax.de","geonet", "sipgate", "smstrade"])
-        self.assertEqual(client.getProvider("fax"), ["fax.de","geonet", "sipgate"])
-        self.assertEqual(client.getProvider("mail"), ["localhost"])
-        
-        self.assertRaises(xmlrpclib.ProtocolError,client.getProvider, "temp")
-    
-    def testGetDefault(self):
-        servstr="https://test:test@localhost:8000"
-        client=xmlrpclib.Server(servstr)       
-        self.assertEqual(client.getDefaultProvider("sms"), "smstrade")
-        self.assertEqual(client.getDefaultProvider("fax"),"sipgate")
-        self.assertEqual(client.getDefaultProvider("mail"), "localhost")       
-        
-        self.assertRaises(xmlrpclib.ProtocolError,client.getDefaultProvider, "temp")        
-
-    
-if __name__ == "__main__":
-    unittest.main()  
--- a/iro/tests/testloglock.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-
-import unittest
-import os
-import tempfile
-import signal
-import threading
-import time
-
-class testLogLock(unittest.TestCase):
-    def test_ThreadingAndLocks(self):
-        #create a thread, have that thread grab a lock, and sleep.
-        fd, file = tempfile.mkstemp('.nonlog')
-        _lock = threading.RLock()
-        os.close(fd)
-        def locker():
-            os.write(fd, 'Thread acquiring lock\n')
-            _lock.acquire()
-            os.write(fd, 'Thread acquired lock\n')
-            time.sleep(0.4)
-            os.write(fd, 'Thread releasing lock\n')
-            _lock.release()
-            os.write(fd, 'Thread released lock\n')
-
-        #Then in the main thread, throw a signal to self
-        def handleSignal(sigNum, frame):
-            os.write(fd, 'Main Thread acquiring lock\n')
-            lock = False
-            endtime = time.time() + 1.0
-            while not lock:
-                lock = _lock.acquire(blocking=0)
-                time.sleep(0.01)
-                if time.time() > endtime:
-                    break
-            if not lock:
-                os.write(fd, 'Main Thread could not acquire lock\n')
-                return
-            os.write(fd, 'Main Thread acquired lock\n')
-            os.write(fd, 'Main Thread releasing lock\n')
-            _lock.release()
-            os.write(fd, 'Main Thread released lock\n')
-
-        sighndlr = signal.signal(signal.SIGUSR1, handleSignal)
-        try:
-            fd = os.open(file, os.O_SYNC | os.O_WRONLY | os.O_CREAT)
-            thread = threading.Thread(target=locker)
-            thread.start()
-            time.sleep(0.1)
-            os.kill(os.getpid(), signal.SIGUSR1)
-            thread.join()
-
-            #check the results
-            os.close(fd)
-            fileconts = open(file, 'r').read()
-            self.assertTrue('Main Thread released lock' in fileconts)
-            self.assertEqual(fileconts,
-                '''Thread acquiring lock
-Thread acquired lock
-Main Thread acquiring lock
-Thread releasing lock
-Thread released lock
-Main Thread acquired lock
-Main Thread releasing lock
-Main Thread released lock
-''')
-
-            #Now try after acquiring the lock from the main thread
-            fd = os.open(file, os.O_SYNC | os.O_WRONLY | os.O_CREAT)
-            _lock.acquire()
-            thread = threading.Thread(target=locker)
-            thread.start()
-            time.sleep(0.1)
-            os.kill(os.getpid(), signal.SIGUSR1)
-            _lock.release()
-            thread.join()
-            os.close(fd)
-            fileconts = open(file, 'r').read()
-            self.assertEqual(fileconts,
-                '''Thread acquiring lock
-Main Thread acquiring lock
-Main Thread acquired lock
-Main Thread releasing lock
-Main Thread released lock
-Thread acquired lock
-Thread releasing lock
-Thread released lock
-''')
-
-        finally:
-            signal.signal(signal.SIGUSR1, sighndlr)
-            try:
-                os.close(fd)
-            except OSError:
-                pass
-            try:
-                os.unlink(file)
-            except OSError:
-                pass
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/validate.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,156 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.trial import unittest
+from mock import Mock
+
+from iro.validate import vBool, vInteger, vHash, validate
+from iro.error import ValidateException
+
+class testValidators(unittest.TestCase):
+    '''test for simple validators'''
+
+    def testBool(self):
+        self.assertEqual(vBool(True,None),True)
+        self.assertEqual(vBool(1,None),True)
+        self.assertEqual(vBool("true",None),True)
+        self.assertEqual(vBool("True",None),True)
+        self.assertEqual(vBool("TRUE",None),True)
+
+        self.assertEqual(vBool(False,None),False)
+        self.assertEqual(vBool(0,None),False)
+        self.assertEqual(vBool("false",None),False)
+        self.assertEqual(vBool("False",None),False)
+        self.assertEqual(vBool("FALSE",None),False)
+
+        e = self.assertRaises(ValidateException, vBool, "TRue","test")
+        self.assertEqual(e.msg,"test is not boolean")
+
+    def testInteger(self):
+        self.assertEqual(vInteger(123,None),123)
+        self.assertEqual(vInteger("123",None),123)
+        self.assertEqual(vInteger("-123",None),-123)
+
+        self.assertRaises(ValidateException, vInteger, "a123",None)
+
+    def testIntegerLimits(self):
+        self.assertEqual(vInteger(10,None,maxv=10),10)
+        self.assertRaises(ValidateException, vInteger, 11, None, maxv=10)
+
+        self.assertEqual(vInteger(4,None,minv=4),4)
+        self.assertRaises(ValidateException, vInteger, 3, None, minv=4)
+
+        self.assertRaises(ValidateException, vInteger, -1, None, minv=0)
+        self.assertRaises(ValidateException, vInteger, 1, None, maxv=0)
+
+    def testIntegerNoneAllowed(self):
+        self.assertEqual(vInteger(None,None,none_allowed=True),None)
+        self.assertEqual(vInteger('',None,none_allowed=True),None)
+
+        self.assertRaises(ValidateException, vInteger, "", None)
+        self.assertRaises(ValidateException, vInteger, None, None)
+
+    def testHash(self):
+        self.assertEqual(vHash("0123456789abcdef",None),"0123456789abcdef")
+        self.assertEqual(vHash("0123456789ABCDEF",None),"0123456789abcdef")
+        self.assertEqual(vHash("F",None),"f")
+        self.assertEqual(vHash("",None),'')
+
+        self.assertRaises(ValidateException, vHash, "GHIJKL", None)
+        
+    def testHashLimits(self):
+        self.assertEqual(vHash("F",None,minlength=1),"f")
+        self.assertRaises(ValidateException, vHash, "", None, minlength=1)
+
+        self.assertEqual(vHash("Fa",None,maxlength=2),"fa")
+        self.assertRaises(ValidateException, vHash, "123", None, maxlength=1)
+
+    
+    def testValidate(self):
+        f = Mock()
+        f.return_value = "valid"
+        @validate("t",f,True,1,2,3,4,k=5)
+        def g(u=False, t="bla"):
+            return t
+        d = g(t="uhuhu")
+        def r(t):
+            f.called_once_with("uhuhu","t",1,2,3,4,k=5)
+            self.assertEqual(t,"valid")
+        d.addCallback(r)
+        return d
+
+    def testValidateMissingNeed(self):
+        f = Mock()
+        @validate("t",f,True,1,2,3,4,k=5)
+        def g(u, t="buuh"):
+            return t
+        e = self.assertRaises(ValidateException, g, u="uhuhu", t = None)
+        self.assertEqual(str(e),'700: t is nessasary')
+
+    def testValidateMissingNeedNonExplicit(self):
+        f = Mock()
+        @validate("t",f,True,1,2,3,4,k=5)
+        def g(u, **k):
+            return k["t"]
+        e = self.assertRaises(ValidateException, g, u="uhuhu")
+        self.assertEqual(str(e),'700: t is nessasary')
+
+
+    def testValidateMissingNeed2(self):
+        f = Mock()
+        f.return_value = "valid"
+        @validate("t",f,True,1,2,3,4,k=5)
+        def g(u, t="buuh"):
+            return t
+
+        d = g(True)
+
+        def r(t):
+            f.called_once_with("buuh","t",1,2,3,4,k=5)
+            self.assertEqual(t,"valid")
+        d.addCallback(r)
+        return d
+
+    def testvalidateNoNeed(self):
+        f = Mock()
+        f.return_value = "valid"
+        @validate("t",f,False,1,2,3,4,k=5)
+        def g(u, t="buuh"):
+            return t
+        d = g("uhu")
+        def r(t):
+            self.assertEqual(f.called,True)
+            self.assertEqual(t,"valid")
+        d.addCallback(r)
+        return d
+
+    def testvalidateNoNeed2(self):
+        f = Mock()
+        f.return_value = "valid"
+        @validate("t",f,False,1,2,3,4,k=5)
+        def g(u, **k):
+            return k["t"]
+        d = g("uhu")
+        def r(t):
+            self.assertEqual(f.called,False)
+            self.assertEqual(t,None)
+        d.addCallback(r)
+        return d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/viewinterface.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,292 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.internet.defer import inlineCallbacks
+from datetime import datetime
+from Queue import deque
+from mock import patch
+
+from iro.model.schema import User, Offer, Userright, Job, Message
+from iro.controller.viewinterface import Interface
+from iro.controller.pool  import taskPool
+
+from iro.model.message import SMS, Fax, Mail
+from iro.model.pool import data
+from iro.model.offer import offers
+from iro.model.job import exJobs
+
+import iro.error as IroError
+
+from ..test_helpers.dbtestcase import DBTestCase
+from ..test_helpers.utils import DummyPool
+
+class ViewInterfaceTest(DBTestCase):
+    """tests for the xmlrpc interface"""
+    def setUp(self):
+        DBTestCase.setUp(self)
+        self.pool = data.pool
+        data.pool = DummyPool()
+
+    def tearDown(self):
+        exJobs.clear()
+        offers.clear()
+        taskPool.pool.q.queue = deque()
+        data.pool = self.pool
+        self.pool = None
+        DBTestCase.tearDown(self)
+    
+    @inlineCallbacks    
+    def testStatus(self):
+        ''' test the status function'''
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(User(name='test',apikey='abcdef123456789'))
+        st = yield Interface().status('abcdef123456789')
+        self.assertEqual(st, {})
+        
+        with self.session() as session:
+            u = session.merge(u)
+            j = Job(info='info', status="started")
+            j.user=u
+            session.add(j)
+            session.commit()
+            jid=j.id
+        status = {str(jid):{"status":"started"}}
+        st = yield Interface().status('abcdef123456789',jid)
+        self.assertEqual(st, status)
+        st = yield Interface().status('abcdef123456789')
+        self.assertEqual(st, status)
+        st = yield Interface().status('abcdef123456789', '', 'false')
+        self.assertEqual(st, status)
+        st = yield Interface().status('abcdef123456789', '', 0)
+        self.assertEqual(st, status)
+
+        #JobNotFound
+        d = Interface().status('abcdef123456789',jid+1)
+        self.assertFailure(d, IroError.JobNotFound)
+        yield d
+
+        #self.assertEqual(self.__rpc2().status('abcdef123456789','abcde', True), ["<User('test','abcdef123456789')>",'abcde', True])
+        #self.assertEqual(self.__rpc2().status('abcdef123456789', '', 'true'), ["<User('test','abcdef123456789')>", '', True])
+        #self.assertEqual(self.__rpc2().status('abcdef123456789', '', 1), ["<User('test','abcdef123456789')>", '', True])
+    
+    def testNoSuchUser(self):
+        '''a unknown user should raise a UserNotNound Exception
+        bewcause xmlrpc only has a Fault exception this Exception has to be deliverd through a xmlrpclib.Fault Exception'''
+        d = Interface().status('abcdef123456789')
+        self.assertFailure(d, IroError.UserNotFound)
+        return d
+
+    
+    def testValidationFault(self):
+        '''a validate Exception should be translated to a xmlrpclib.Fault.'''
+        d = Interface().status('xxx')
+        self.assertFailure(d, IroError.ValidateException)
+
+    @inlineCallbacks
+    def testRoutes(self):
+        '''test the route function'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            session.add(u)
+        r = yield Interface().routes('abcdef123456789','sms')
+        self.assertEqual(r, ['sipgate_basic'])
+
+        d = Interface().routes('abcdef123456789','fax')
+        self.assertFailure(d,IroError.ValidateException)
+        yield d
+
+        with self.session() as session:
+            o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms")
+            u = session.query(User).filter_by(name="test").first()
+            u.rights.append(Userright(o)) 
+            o=Offer(name="faxde", provider="faxde", route="", typ="fax")
+            session.add(o)
+            session.commit()
+        
+        r = yield Interface().routes('abcdef123456789','sms')
+        self.assertEqual(r, ['sipgate_basic','sipgate_plus'])
+        r = yield Interface().routes('abcdef123456789','fax')
+        self.assertEqual(r, [])
+        
+
+        with self.session() as session:
+            u = session.query(User).filter_by(name="test").first()
+            u.rights.append(Userright(o)) 
+        
+        r = yield Interface().routes('abcdef123456789','sms')
+        self.assertEqual(r, ['sipgate_basic','sipgate_plus'])
+        r = yield Interface().routes('abcdef123456789','fax')
+        self.assertEqual(r, ['faxde'])
+
+    @inlineCallbacks
+    def testDefaultRoutes(self):
+        '''test the defaultRoute function'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o,True)) 
+            o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms")
+            u.rights.append(Userright(o))
+            session.add(u)
+        r = yield Interface().defaultRoute('abcdef123456789','sms')
+        self.assertEqual(r, ['sipgate_basic'])
+
+    @inlineCallbacks
+    def testTelnumbers(self):
+        '''test the telefon validator'''
+        r = yield Interface().telnumber(["0123/456(78)","+4912346785433","00123435456-658"])
+        self.assertEqual(r, True)
+
+        invalid=['xa','+1','1-23',';:+0','0123']
+
+        d = Interface().telnumber(['01234']+invalid) 
+        def x(failure):
+            self.assertEqual(failure.getErrorMessage(),"701: No valid telnumber: '%s'"%invalid[0])
+            failure.raiseException()
+        d.addErrback(x)
+        self.assertFailure(d, IroError.InvalidTel)
+        yield d
+
+    @inlineCallbacks
+    def testVaildEmail(self):
+        '''test vaild email adresses (got from wikipedia)'''
+        validmails=["niceandsimple@example.com"]
+        r = yield Interface().email(validmails)
+        self.assertEqual(r,True)
+    
+    def testInvaildEmail(self):
+        '''test invaild email adresses (got from wikipedia)'''
+        invalid=["Abc.example.com",]
+        d = Interface().email(invalid)
+        self.assertFailure(d, IroError.InvalidMail)
+        return d
+
+    @inlineCallbacks
+    def testBill(self):
+        '''test bill function'''
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+        
+        r = yield  Interface().bill(apikey)
+        self.assertEqual(r,{'total':{'price':0.0,'anz':0}})
+        
+        with self.session() as session:
+            u = session.merge(u)
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            u.rights.append(Userright(o))
+            j = Job(info='i',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o))
+            u.jobs.append(j)
+ 
+            j = Job(info='a',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o))
+            u.jobs.append(j) 
+        
+        ret=yield Interface().bill(apikey) 
+        self.assertEqual(ret['total'],{'price':0.8,'anz':2})
+        self.assertEqual(ret['sipgate_basic'],
+                {'price':0.8,'anz':2,
+                    'info':{'i':{'price':0.4,'anz':1},
+                        'a':{'price':0.4,'anz':1},
+                        }
+                    })
+
+    @inlineCallbacks
+    def testSMS(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            u.rights.append(Userright(o))
+            session.add(u)
+        
+        jobid = yield Interface().sms('abcdef123456789','message',['0123325456'],['sipgate_basic'])
+       
+        with self.session() as session:
+            u = session.merge(u)
+            job = u.job(jobid)
+            exJob = job.extend
+        
+        self.assertEqual(exJob.message,SMS("message",None))
+        self.assertEqual(taskPool.pool.q.qsize(),1)
+
+
+    @patch("iro.model.message.formatdate")
+    @inlineCallbacks
+    def testMail(self,mock_f):
+        mock_f.return_value="Wed, 21 Mar 2012 17:16:11 +0100"
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            o = Offer(name='loc',provider="localhost",route="",typ="mail")
+            u.rights.append(Userright(o))
+            session.add(u)
+        
+        jobid = yield Interface().mail('abcdef123456789','sub', "hey body!", ['t@te.de'], "frm@t.de" ,['loc'])
+        with self.session() as session:
+            u = session.merge(u)
+            job = u.job(jobid)
+            exJob = job.extend
+        
+        self.assertEqual(exJob.message,Mail("sub",'hey body!','frm@t.de'))
+        self.assertEqual(taskPool.pool.q.qsize(),1)
+
+    @patch("iro.model.message.formatdate")
+    @inlineCallbacks
+    def testMailFrmNone(self,mock_f):
+        mock_f.return_value="Wed, 21 Mar 2012 17:16:11 +0100"
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            o = Offer(name='loc',provider="localhost",route="",typ="mail")
+            u.rights.append(Userright(o))
+            session.add(u)
+        
+        jobid = yield Interface().mail('abcdef123456789','sub', "hey body!", ['t@te.de'], None,['loc'])
+        with self.session() as session:
+            u = session.merge(u)
+            job = u.job(jobid)
+            exJob = job.extend
+        
+        self.assertEqual(exJob.message,Mail("sub",'hey body!',None))
+        self.assertEqual(taskPool.pool.q.qsize(),1)
+
+    
+
+    @inlineCallbacks
+    def testFax(self):
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            o = Offer(name='b',provider="sipgate",route="b",typ="fax")
+            u.rights.append(Userright(o))
+            session.add(u)
+        
+        jobid = yield Interface().fax('abcdef123456789','subject', 'blublbubblu',['0123325456'],['b'])
+       
+        with self.session() as session:
+            u = session.merge(u)
+            job = u.job(jobid)
+            exJob = job.extend
+        
+        self.assertEqual(exJob.message,Fax("subject","blublbubblu"))
+        self.assertEqual(taskPool.pool.q.qsize(),1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/tests/xmlrpc.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,237 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from multiprocessing import Process
+import unittest
+
+from datetime import datetime
+
+import time
+
+from xmlrpclib import Server as xServer, ServerProxy, Fault
+
+from iro.model.schema import User, Offer, Userright, Job, Message
+
+from iro.main import runReactor
+
+import iro.error as IroError
+
+from ..test_helpers.dbtestcase import DBTestCase
+
+
+class XMLRPCTest(DBTestCase):
+    """tests for the xmlrpc interface"""
+    def setUp(self):
+        DBTestCase.setUp(self)
+        self.s = Process(target=startReactor, args=(self.engine,))
+        self.s.start()
+        #the new process needs time to get stated, so this process has to sleep
+        time.sleep(.2)
+
+    def tearDown(self):
+        self.__debug().stop()
+        time.sleep(.2)
+        self.s.join()
+        DBTestCase.tearDown(self)
+
+    def __debug(self):
+        return xServer('http://localhost:7080/debug')
+
+    def __rpc2(self):
+        return ServerProxy('http://localhost:7080/RPC2')
+    
+    def testDebugHello(self):
+        '''simple test for the connection to xmlrpc server'''
+        ret=self.__debug().hello()
+        self.failUnlessEqual(ret,'hello')
+
+    def testListMethods(self):
+        '''list of all offical Methods, that can be executed'''
+        ret=self.__rpc2().listMethods()
+        self.failUnlessEqual(ret, ['listMethods', 'status', 'stop', 'sms', 'fax', 'mail', 'routes', 'defaultRoute', 'bill', 'telnumber','email'])
+
+    def testStatus(self):
+        ''' test the status function'''
+        with self.session() as session:
+            u = User(name='test',apikey='abcdef123456789')
+            session.add(User(name='test',apikey='abcdef123456789'))
+        self.failUnlessEqual(self.__rpc2().status('abcdef123456789'), {})
+        
+        with self.session() as session:
+            u = session.merge(u)
+            j = Job(info='info', status="started")
+            j.user=u
+            session.add(j)
+            session.commit()
+            jid=j.id
+        status = {str(jid):{"status":"started"}}
+        self.failUnlessEqual(self.__rpc2().status('abcdef123456789',jid), status)
+        self.failUnlessEqual(self.__rpc2().status('abcdef123456789'), status)
+        self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 'false'), status)
+        self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 0), status)
+
+        #JobNotFound
+        exc =  self.assertRaises(Fault, self.__rpc2().status, 'abcdef123456789',jid+1)
+        unf = IroError.JobNotFound()
+        self.failUnlessEqual(exc.faultCode, unf.code)
+        self.failUnlessEqual(exc.faultString, unf.msg)
+
+        #self.failUnlessEqual(self.__rpc2().status('abcdef123456789','abcde', True), ["<User('test','abcdef123456789')>",'abcde', True])
+        #self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 'true'), ["<User('test','abcdef123456789')>", '', True])
+        #self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 1), ["<User('test','abcdef123456789')>", '', True])
+    
+    def testNoSuchUser(self):
+        '''a unknown user should raise a UserNotNound Exception
+        bewcause xmlrpc only has a Fault exception this Exception has to be deliverd through a xmlrpclib.Fault Exception'''
+        exc = self.assertRaises(Fault, self.__rpc2().status, 'abcdef123456789')
+        unf=IroError.UserNotFound()
+        self.failUnlessEqual(exc.faultCode, unf.code)
+        self.failUnlessEqual(exc.faultString, unf.msg)
+
+    def testNoSuchMethod(self):
+        '''a unknown mothod should raise a Exception '''
+        exc =  self.assertRaises(Fault, self.__rpc2().nosuchmethod)
+        self.failUnlessEqual(exc.faultCode, 8001)
+        self.failUnlessEqual(exc.faultString, "procedure nosuchmethod not found")
+    
+    def testValidationFault(self):
+        '''a validate Exception should be translated to a xmlrpclib.Fault.'''
+        exc =  self.assertRaises(Fault, self.__rpc2().status,'xxx')
+        self.failUnlessEqual(exc.faultCode, 700)
+        self.failUnlessEqual(exc.faultString, "Validation of 'apikey' failed.")
+
+    def testRoutes(self):
+        '''test the route function'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o)) 
+            session.add(u)
+        self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','sms'),['sipgate_basic'])
+
+        exc = self.assertRaises(Fault, self.__rpc2().routes,'abcdef123456789','fax')
+        self.failUnlessEqual(exc.faultCode, 700)
+        self.failUnlessEqual(exc.faultString, "Typ is not valid.")
+        
+        with self.session() as session:
+            o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms")
+            u = session.query(User).filter_by(name="test").first()
+            u.rights.append(Userright(o)) 
+            o=Offer(name="faxde", provider="faxde", route="", typ="fax")
+            session.add(o)
+            session.commit()
+        self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','sms'),['sipgate_basic','sipgate_plus'])
+        self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','fax'),[])
+
+        with self.session() as session:
+            u = session.query(User).filter_by(name="test").first()
+            u.rights.append(Userright(o)) 
+        
+        self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','sms'),['sipgate_basic','sipgate_plus'])
+        self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','fax'),['faxde'])
+
+    def testDefaultRoutes(self):
+        '''test the defaultRoute function'''
+        with self.session() as session:
+            u=User(name='test',apikey='abcdef123456789')
+            o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms")
+            u.rights.append(Userright(o,True)) 
+            o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms")
+            u.rights.append(Userright(o))
+            session.add(u)
+        self.failUnlessEqual(self.__rpc2().defaultRoute('abcdef123456789','sms'),['sipgate_basic'])
+
+    def testTelnumbers(self):
+        '''test the telefon validator'''
+        self.failUnlessEqual(self.__rpc2().telnumber(["0123/456(78)","+4912346785433","00123435456-658"]),True)
+
+        invalid=['xa','+1','1-23',';:+0','0123']
+
+        exc =  self.assertRaises(Fault, self.__rpc2().telnumber,['01234']+invalid)
+        self.failUnlessEqual(exc.faultCode, 701)
+        self.failUnlessEqual(exc.faultString, "No valid telnumber: '%s'" % invalid[0])
+
+
+    def testVaildEmail(self):
+        '''test vaild email adresses (got from wikipedia)'''
+        validmails=["niceandsimple@example.com"]
+        self.failUnlessEqual(self.__rpc2().email(validmails),True)
+    
+    def testInvaildEmail(self):
+        '''test invaild email adresses (got from wikipedia)'''
+        invalid=["Abc.example.com",]
+        exc= self.assertRaises(Fault, self.__rpc2().email, invalid)
+        self.failUnlessEqual(exc.faultCode, 702)
+        self.failUnlessEqual(exc.faultString, "No valid email: '%s'" % invalid[0])
+    
+    def testBill(self):
+        '''test bill function'''
+        apikey='abcdef123456789'
+        with self.session() as session:
+            u=User(name='test',apikey=apikey)
+            session.add(u)
+      
+        self.failUnlessEqual(self.__rpc2().bill(apikey),{'total':{'price':0.0,'anz':0}})
+        
+        with self.session() as session:
+            u = session.merge(u)
+            o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms")
+            u.rights.append(Userright(o))
+            j = Job(info='i',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o))
+            u.jobs.append(j)
+ 
+            j = Job(info='a',status='sended')
+            j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o))
+            u.jobs.append(j) 
+        
+        ret=self.__rpc2().bill(apikey) 
+        self.failUnlessEqual(ret['total'],{'price':0.8,'anz':2})
+        self.failUnlessEqual(ret['sipgate_basic'],
+                {'price':0.8,'anz':2,
+                    'info':{'i':{'price':0.4,'anz':1},
+                        'a':{'price':0.4,'anz':1},
+                        }
+                    })
+        
+       
+def startReactor(engine):
+    """starts the Rector with a special debug Child, so that the reactor can be stopped remotly. """
+    from twisted.internet import reactor
+    from twisted.web import xmlrpc, resource
+    
+    from iro.view.xmlrpc import appendResource
+    
+    class XMLRPCDebug(xmlrpc.XMLRPC): 
+        def xmlrpc_stop(self):
+            reactor.callLater(0.1,reactor.stop)
+            return ""
+
+        def xmlrpc_hello(self):
+            return "hello"
+
+    root = resource.Resource()
+    root = appendResource(root)
+    root.putChild('debug', XMLRPCDebug())
+    runReactor(reactor, engine, root)
+
+if __name__ == '__main__':
+        unittest.main()
--- a/iro/user.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +0,0 @@
-# -*- coding: utf-8 -*-
-#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
-
-#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 3 of the License, or 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, see <http://www.gnu.org/licenses/>.
-import logging
-logger=logging.getLogger("iro.user")
-class NotSupportedFeature (Exception):
-    def __init__(self,name):
-        self.name=name
-    
-    def __str__(self):
-        return ("This is not a supported feature:", self.name)
-
-class NoID(Exception):
-    def __init__(self,i):
-        self.i=i
-    
-    def __str__(self):
-        return ("No Job with id:", self.i)
-
-
-class User: 
-    '''
-    class for a xmlrpc user
-    '''
-    def __init__(self, name, jobqueue):
-        self.jobqueue=jobqueue
-        self.jobs={}
-        self.name=name
-        self.features=["mail", "sms", "fax", ]
-
-    def status(self,id=None,detailed=False):
-        u'''Gibt den aktuellen Status eines Auftrages zurück.
-
-        Keywords:
-        id[hash]: Eine Auftragsnummer
-        detailed[boolean]: Details ausgeben
-
-        Return:
-        jobs[list]: Eine Liste der Aufträge.
-        job.name[string]: Angebener Name
-        job.status[string]: Status des Auftrages
-
-
-        '''
-        try:
-            jobs={}
-            if id==None:
-                jobs=self.jobs
-            else:
-                try:
-                    jobs={id:self.jobs[id]}
-                except:
-                    logger.error("No Job ID %s",id)
-                    #raise NoID(id)
-            ret={}
-            if not jobs:
-                return {}
-
-            for key in jobs:
-                job=jobs[key]
-                ret[key]={"name":job.getName(),"status":job.getStatus(detailed)}
-            
-            return ret
-        except:
-            logger.exception("Fehler in iro.user.status")
-            return {}
-
-    def stop(self,id):
-        u'''Stoppt den angegeben Auftrag.
-
-        Keywords:
-        id[hash]: Eine Auftragsnummer
-
-        Return:
-
-        '''
-        try:
-            job=self.jobs[id]
-            job.stop()
-        except:
-            raise NoID(id)
-        job.stop()
-
-    
-    def startSMS(self, message, recipients, provider="default"):
-        u'''Versendet eine SMS.
-
-        Keywords:
-        message[string]: Nachricht
-        recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
-        provider[string]: Provider über den geschickt werden soll
-
-        Return:
-        id[hash]: Die ID des Auftrages
-
-        '''
-        if not "sms" in self.features:
-            raise NotSupportedFeature("sms")
-        id = self.jobqueue.newSMS(message,recipients,provider,user=self.name)
-        self.jobs[id]=self.jobqueue[id]
-        return id
-    
-    
-    def startFAX(self, subject, fax, recipients, provider="default"):
-        u'''Versendet ein FAX.
-
-        Keywords:
-        subject[string]: der Betreff
-        fax[string]: das pdf base64 kodiert
-        recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
-        provider[string]: Provider über den geschickt werden soll
-
-        Return:
-        id[hash]: Die ID des Auftrages
-
-        '''
-        logger.debug("startFAX(%s,%s,%s,%s)"%(subject, fax, recipients, provider))
-        if not "fax" in self.features:
-            raise NotSupportedFeature("fax")
-        
-        if type(fax) != list:
-            fax=[fax]
-        f=[i.data for i in fax]
-
-        id = self.jobqueue.newFAX(subject, f,recipients,provider,user=self.name)
-        self.jobs[id]=self.jobqueue[id]
-        return id
-
-    def startMail(self, subject,  body, recipients, frm, provider="default"):
-        u'''Versendet eine Email.
-
-        Keywords:
-        subject[string]: der Betreff
-        body[string]: der Email Body
-        recipients[list]: eine Liste von Emailadressen
-        frm[string]: Die Absender Emailadresse
-        provider[string]: Provider über den geschickt werden soll
-
-        Return:
-        id[hash]: Die ID des Auftrages
-
-        '''
-        if not "mail" in self.features:
-            raise NotSupportedFeature("mail")
-        logger.debug("startMail(%s,%s,%s,%s,%s)"%(subject, body, recipients, frm, provider))
-        id = self.jobqueue.newMail(subject, body, recipients, frm,  provider,user=self.name)
-        self.jobs[id]=self.jobqueue[id]
-        return id    
-       
-    def getProvider(self, typ):
-        u'''Gibt eine Liste aller verfügbaren Provider zurück.
-
-        Keywords:
-        typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
-                     Einer der Liste ["sms","fax","mail"]
-
-        Return:
-        providerlist[list]: Eine Liste aller möglichen Provider
-
-        '''
-        if not typ in self.features:
-            raise NotSupportedFeature(typ)
-       
-        return self.jobqueue.providerlist.getProviderlist(typ)
-        
-    def getDefaultProvider(self, typ):
-        u'''Gibt den Standardprovider zurück.
- 
-        Keywords:
-        typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
-                     Einer der Liste ["sms","fax","mail"]
-
-        Return:
-        provider[string]: Der Standardprovider für den angeben Typ
-
-
-        '''
-        if not typ in self.features:
-            raise NotSupportedFeature(typ)
-       
-        return self.jobqueue.providerlist.getDefault(typ)["name"]
-        
-class Admin(User):
-    def __init__(self, name, jobqueue):
-        User.__init__(self, name, jobqueue)
-        self.jobs=jobqueue.jobs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/validate.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,258 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.internet import defer
+
+import re
+from decorator import decorator
+try:
+    from inspect import getcallargs
+except ImportError:
+    from .inspect_getcallargs import getcallargs
+import types
+
+from .error import ValidateException, InvalidTel, InvalidMail
+from .telnumber import Telnumber
+
+def vBool(value, field):
+    '''Validate function  for boolean values
+    
+    :return: **value**
+    :raises: :exc:`iro.error.ValidateException`
+    '''
+    t=[True, 1, "true", "True", "TRUE"]
+    f=[False, 0, "false", "False", "FALSE"]
+    if value in t:
+        return True
+    elif value in f:
+        return False
+    else:
+        raise ValidateException(field=field, msg='%s is not boolean' % field)
+
+def vNumber(value,field, nval,  minv=None, maxv=None, none_allowed=False):
+    """validate function for integer values.
+
+    :param integer minv: minimum value
+    :param integer maxv: maximum value
+    :param func nval: function that give back a number
+    :param boolean none_allowed: is None or empty string allowed
+    :return: **value**
+    :raises: :exc:`iro.error.ValidateException`
+    """
+    if none_allowed and value in [None,'']:
+        return None
+
+    try:
+        ret = nval(value)
+    except ValueError:
+        raise ValidateException(field=field)
+    except TypeError:
+        raise ValidateException(field=field)
+
+    if minv is not None and ret < minv:
+        raise ValidateException(field=field)
+
+    if maxv is not None and ret > maxv:
+        raise ValidateException(field=field)
+
+    return ret
+
+def vInteger(value, field, minv=None, maxv=None, none_allowed=False):
+    """validate function for integer values.
+
+    :param integer minv: minimum value
+    :param integer maxv: maximum value
+    :param boolean none_allowed: is None or empty string allowed
+    :return: **value**
+    :raises: :exc:`iro.error.ValidateException`
+
+    see also :func:vNumber
+    """
+    return vNumber(value, field, int, minv, maxv, none_allowed)
+
+def vFloat(value, field, minv=None, maxv=None, none_allowed=False):
+    """validate function for float values.
+
+    :param integer minv: minimum value
+    :param integer maxv: maximum value
+    :param boolean none_allowed: is None or empty string allowed
+    :return: **value**
+    :raises: :exc:`iro.error.ValidateException`
+
+    see also :func:vNumber
+    """
+    return vNumber(value, field, float, minv, maxv, none_allowed)
+
+
+
+def vHash(value,field,minlength=None,maxlength=None):
+    '''Validate function for hash values
+    
+    :param integer minlength: minimum length of value string
+    :param integer maxlength: maximum length of value string
+    :return: **value**
+    :raises: :exc:`iro.error.ValidateException`
+    '''
+    if not re.match(r'^[a-f0-9]*$', value.lower()):
+        raise ValidateException(field=field)
+    if minlength and len(value)<minlength:
+        raise ValidateException(field=field)
+    if maxlength and len(value)>maxlength:
+        raise ValidateException(field=field)
+    return value.lower()
+
+def vTel(value,field):
+    '''Validator for telefon numbers
+    :return: **value**
+    :raises: :exc:`iro.error.InvalidTel`
+    '''
+
+    ret = []
+    for v in value:
+        try:
+            tel=Telnumber(v)
+            if tel not in ret:
+                ret.append(tel)
+        except InvalidTel, e:
+            e.field=field
+            raise e
+    return ret
+
+def vEmail(value, field, allowString=True, allowList=True):
+    '''validator for emailadresses (see wikipeda for strange mailadresses and RFC3696)
+    
+    valid:
+
+    - "very.(),:;<>[]\\".VERY.\\"very@\\\ \\"very\\".unusual"@strange.example.com
+    - ""@example.org
+    - "very.unusual.@.unusual.com"@example.com'
+
+    not valid:
+    
+    - Abc.@example.com
+    - Abc..123@example.com
+    - thisis."notallowed@example.com
+    - this\\ still\\"not\\allowed@example.com
+   
+    :param boolean allowString: value can be a string -> a string is returned
+    :param boolean allowList: value is a a list -> a list is returned
+    :return: **value**
+    :raises: :exc:`iro.error.ValidateException`, :exc:`iro.error.InvalidMail`
+    '''
+    ret = []
+    str_=False
+    if type(value) is types.StringType:
+        if not allowString:
+            raise ValidateException('%s must be a list of email addresses.'%field)
+        str_=True
+        value=[value]
+    elif not allowList:
+        raise ValidateException('%s must be a email address - No list of email addresses.'%field)
+    for v in value:
+        parts= re.match(r'^(.*)@(.+?)$',v)
+        if not parts:
+            raise InvalidMail(v,field)
+        local=parts.group(1)
+        domain=parts.group(2)
+        
+        if not re.match(r'^(\[[0-9\.]{7,16}\]|\[[0-9a-f:]{3,}\]|([a-z0-9+\-%_]+\.)+[a-z]{2,6})$',domain.lower()):
+            raise InvalidMail(v,field)
+       
+        if local == "":
+            ret.append(v)
+            continue
+    
+        if local.startswith(".") or local.endswith("."):
+            raise InvalidMail(v,field)
+        unquote = True
+        parts = local.split('"')
+        c=0
+        i=0
+        for part in parts:
+            if unquote and part != "":                     #unquoted is not allowd so much
+                if not re.match(r'^[^\\,\[\];\(\)@<>: ]+$',part) or ".." in part:
+                    raise InvalidMail(v,field)
+            if i == 0:
+                if unquote and part != "" and len(parts) > 1 and part[-1] != '.': #quoted parts must be seperated by a dot
+                    raise InvalidMail(v,field)
+                unquote = not unquote
+                c+=1
+            elif part == ''  or part[-1] != "\\":
+                if unquote and part != "":         #quoted parts must be seperated by a dot
+                    if part[0] != ".":
+                        raise InvalidMail(v,field)
+                    if i < len(parts)-1 and part[-1] != '.':
+                        raise InvalidMail(v,field)
+                unquote = not unquote
+                c += 1
+            i += 1
+        if c%2 == 0 and c > 1:                        #no single quote allowed
+            raise InvalidMail(v,field)
+        if v not in ret:
+            ret.append(v)
+    if str_:
+        ret=ret[0]
+    return ret
+
+def validate(kwd,func, need=True,*args,**kargs):
+    '''validate decorator.
+
+    :param string kwd: keyword to validate
+    :param func func: validate function
+    :param boolean need: ``False`` -- ``None`` is a valid value for kwd
+    :params args: arguments for validate function
+    :params kargs: keyword arguments for validate function
+
+    .. note:: this decorator can handle function that returns a defer object.
+    
+    use it like this::
+
+      @validate(kwd=userhash, func=vuserhash)
+      f(userhash)
+    
+    that will validate ``userhash`` with the function **vuserhash**.
+    Every validate function should raise an Exception, if the the value is not valid.
+    All **args** and **kargs** are used to call the validate function.
+    if **need** is True, the kwd can't be `None`.
+    '''
+    @decorator
+    def v(f,*a,**k):
+        kp=getcallargs(f,*a,**k)
+        def dfunc(*x,**y):
+            return None
+        try:
+            if kp[kwd] is not None:
+                dfunc=func
+            elif need:
+                raise ValidateException(field=kwd,msg="%s is nessasary"%kwd)
+        except KeyError:
+            if need:
+                raise ValidateException(field=kwd,msg="%s is nessasary"%kwd)
+            kp[kwd] = None
+
+        def _gotResult(value):
+            kp[kwd] = value
+            e = defer.maybeDeferred(f,**kp)
+            return e
+        d = defer.maybeDeferred(dfunc, kp[kwd],kwd,*args,**kargs)
+        return d.addCallback(_gotResult)
+    return v
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/view/__init__.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,20 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/view/jsonresource.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+#
+# This file is part of Iro.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.web import resource, server, http
+from twisted.python import log, failure
+from twisted.internet import defer
+
+from ..controller.viewinterface import Interface
+from ..error import ValidateException
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+
+class TwistedInterface(Interface):
+    """Class that addes needed function for JSON"""
+    def __init__(self):
+        Interface.__init__(self)
+
+    def listMethods(self):
+        """Since we override lookupProcedure, its suggested to override
+        listProcedures too.
+        """
+        return self.listProcedures()
+
+
+    def listProcedures(self):
+        """returns a list of all functions that are allowed to call via XML-RPC."""
+        return ['listMethods','status','sms','fax','mail','routes','defaultRoute','bill','telnumber','email']
+
+class MethodFactory(resource.Resource):
+    def __init__(self,method,twistedInterface):
+        self.method = method
+        self.twistedInterface = twistedInterface
+
+    def render(self,request):
+        try:
+            args = []
+            if request.getHeader('Content-Type') == 'application/x-www-form-urlencoded':
+                args = {}
+                for a in request.args:
+                    value = request.args[a]
+                    if a != "recipients" and len(value) == 1:
+                        value = value[0]
+                    args[a] = value
+            elif request.getHeader('Content-Type') == 'application/json':
+                content = request.content.read()
+                if content:
+                    args = json.loads(content)
+                if args is None:
+                    args = []
+            else:
+                request.setResponseCode(http.NOT_ACCEPTABLE)
+                return "Only application/x-www-form-urlencoded or application/json ist allowed for Content-Type"
+            if isinstance(args,list):
+                d =  defer.maybeDeferred(getattr(self.twistedInterface,self.method),*args)
+            else:
+                d =  defer.maybeDeferred(getattr(self.twistedInterface,self.method), **args)
+            d.addCallback(self._cbRender, request)
+            d.addErrback(self._ebRender, request)
+            d.addBoth(lambda _: request.finish())
+            return server.NOT_DONE_YET
+        except Exception:
+            log.err(failure.Failure())
+            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
+            err= {
+                    "code" : 999,
+                    "msg"  : "Unknown error.",
+                }
+            request.setHeader('Content-Type', 'application/json')
+            return json.dumps({"status":False, "error":err})
+
+
+    def _cbRender(self,result,request):
+        request.setHeader('Content-Type', 'application/json')
+        request.write(json.dumps({"status":True, "result":result}))
+
+    def _ebRender(self, failure, request):
+        if isinstance(failure.value, ValidateException):
+            request.setResponseCode(http.BAD_REQUEST)
+        else:
+            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
+
+        err= {
+                "code" : 999,
+                "msg"  : "Unknown error.",
+            }
+
+        try:
+            err["code"]=failure.value.code
+            err["msg"]=failure.value.msg
+        except Exception:
+            log.err(failure)
+            pass
+        request.setHeader('Content-Type', 'application/json')
+        request.write(json.dumps({"status":False, "error":err}))
+
+class JSONFactory(resource.Resource):
+    """JSON factory"""
+    def __init__(self):
+        resource.Resource.__init__(self)
+        self.twistedInterface = TwistedInterface()
+        for method in self.twistedInterface.listProcedures():
+            self.putChild(method, MethodFactory(method, self.twistedInterface))
+
+
+def appendResource(root):
+    """adding JSON to root."""
+    root.putChild('json', JSONFactory())
+
+if __name__ == '__main__':
+    from twisted.web import resource
+    from twisted.internet import reactor
+
+    root = resource.Resource()
+    root = appendResource(root)
+    reactor.listenTCP(7080, server.Site(root))
+    reactor.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/view/jsonrpc.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+#
+# This file is part of Iro.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from txjsonrpc.web import jsonrpc
+from txjsonrpc import jsonrpclib
+from ..controller.viewinterface import Interface
+
+from ..error import InterfaceException, ValidateException
+
+class TwistedInterface(Interface):
+    """Class that addes needed function for XML-RPC/SOAP"""
+    def __init__(self):
+        Interface.__init__(self)
+
+    def listMethods(self):
+        """Since we override lookupProcedure, its suggested to override
+        listProcedures too.
+        """
+        return self.listProcedures()
+
+
+    def listProcedures(self):
+        """returns a list of all functions that are allowed to call via XML-RPC."""
+        return ['listMethods','status','sms','fax','mail','routes','defaultRoute','bill','telnumber','email']
+
+
+class JSONRPCInterface(TwistedInterface,jsonrpc.JSONRPC):
+    """JSON-RPC interface"""
+    NOT_FOUND = jsonrpclib.METHOD_NOT_FOUND
+    def __init__(self):
+        jsonrpc.JSONRPC.__init__(self)
+        TwistedInterface.__init__(self)
+
+    def _getFunction(self, procedurePath):
+        if procedurePath not in self.listProcedures():
+            raise jsonrpclib.NoSuchFunction(self.NOT_FOUND,
+                    "procedure %s not found" % procedurePath)
+        try:
+            return getattr(self,procedurePath)
+        except KeyError:
+            raise jsonrpclib.NoSuchFunction(self.NOT_FOUND,
+                    "procedure %s not found" % procedurePath)
+
+    def _ebRender(self, failure, id):
+        if isinstance(failure.value, InterfaceException):
+            return jsonrpclib.Fault(failure.value.code, failure.value.msg)
+        if isinstance(failure.value, ValidateException):
+            return jsonrpclib.Fault(failure.value.code, failure.value.msg)
+        return jsonrpc.JSONRPC._ebRender(self, failure, id)
+
+
+def appendResource(root):
+    """adding JSONRPC to root."""
+    root.putChild('jsonrpc', JSONRPCInterface())
+
+if __name__ == '__main__':
+    from twisted.web import resource, server
+    from twisted.internet import reactor
+
+    root = resource.Resource()
+    root = appendResource(root)
+    reactor.listenTCP(7080, server.Site(root))
+    reactor.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/view/xmlrpc.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,97 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*-
+from twisted.web import soap, xmlrpc
+
+from ..controller.viewinterface import Interface
+
+from ..error import InterfaceException, ValidateException
+
+class TwistedInterface(Interface):
+    """Class that addes needed function for XML-RPC/SOAP"""
+    def __init__(self):
+        Interface.__init__(self)
+
+    def listMethods(self):
+        """Since we override lookupProcedure, its suggested to override
+        listProcedures too.
+        """
+        return self.listProcedures()
+  
+
+    def listProcedures(self):
+        """returns a list of all functions that are allowed to call via XML-RPC."""
+        return ['listMethods','status','sms','fax','mail','routes','defaultRoute','bill','telnumber','email']
+
+
+class XMLRPCInterface(TwistedInterface,xmlrpc.XMLRPC): 
+    """XML-RPC interface"""
+    def __init__(self):
+        xmlrpc.XMLRPC.__init__(self) 
+        TwistedInterface.__init__(self)
+    
+    def lookupProcedure(self, procedurePath):
+        if procedurePath not in self.listProcedures():
+            raise xmlrpc.NoSuchFunction(self.NOT_FOUND,
+                        "procedure %s not found" % procedurePath)
+        try:
+            return getattr(self,procedurePath)
+        except KeyError:
+            raise xmlrpc.NoSuchFunction(self.NOT_FOUND,
+                        "procedure %s not found" % procedurePath)
+
+    def _ebRender(self, failure):
+        if isinstance(failure.value, InterfaceException):
+            return xmlrpc.Fault(failure.value.code, failure.value.msg)
+        if isinstance(failure.value, ValidateException):
+            return xmlrpc.Fault(failure.value.code, failure.value.msg)
+        return xmlrpc.XMLRPC._ebRender(self, failure)
+
+
+class SOAPInterface(TwistedInterface,soap.SOAPPublisher): 
+    """SOAP interface"""
+    def __init__(self):
+        soap.SOAPPublisher.__init__(self) 
+        TwistedInterface.__init__(self)
+        
+    def lookupFunction(self, functionName):
+        if functionName in self.listProcedures():
+            function = getattr(self, functionName, None)
+            if function:
+                return function, getattr(function, "useKeywords", False)
+            return None
+        else:
+            return None
+
+def appendResource(root):
+    """adding XML-RPC and SOAP to root."""
+    root.putChild('RPC2', XMLRPCInterface())
+    root.putChild('SOAP', SOAPInterface())
+
+if __name__ == '__main__':
+    from twisted.web import resource, server
+    from twisted.internet import reactor
+    
+    root = resource.Resource()
+    root = appendResource(root)
+    reactor.listenTCP(7080, server.Site(root))
+    reactor.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/view/xmlrpc_old.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,206 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# -*- coding: utf-8 -*-
+""".. deprecated:: 1.0a"""
+
+import logging
+logger=logging.getLogger("iro.user")
+class NotSupportedFeature (Exception):
+    def __init__(self,name):
+        self.name=name
+    
+    def __str__(self):
+        return ("This is not a supported feature:", self.name)
+
+class NoID(Exception):
+    def __init__(self,i):
+        self.i=i
+    
+    def __str__(self):
+        return ("No Job with id:", self.i)
+
+
+class User: 
+    '''class for a xmlrpc user
+    '''
+    def __init__(self, name, jobqueue):
+        self.jobqueue=jobqueue
+        self.jobs={}
+        self.name=name
+        self.features=["mail", "sms", "fax", ]
+
+    def status(self,id=None,detailed=False):
+        '''Gibt den aktuellen Status eines Auftrages zurück.
+
+        Keywords:
+        id[hash]: Eine Auftragsnummer
+        detailed[boolean]: Details ausgeben
+
+        Return:
+        jobs[list]: Eine Liste der Aufträge.
+        job.name[string]: Angebener Name
+        job.status[string]: Status des Auftrages
+
+
+        '''
+        try:
+            jobs={}
+            if id==None:
+                jobs=self.jobs
+            else:
+                try:
+                    jobs={id:self.jobs[id]}
+                except:
+                    logger.error("No Job ID %s",id)
+                    #raise NoID(id)
+            ret={}
+            if not jobs:
+                return {}
+
+            for key in jobs:
+                job=jobs[key]
+                ret[key]={"name":job.getName(),"status":job.getStatus(detailed)}
+            
+            return ret
+        except:
+            logger.exception("Fehler in iro.user.status")
+            return {}
+
+    def stop(self,id):
+        '''Stoppt den angegeben Auftrag.
+
+        Keywords:
+        id[hash]: Eine Auftragsnummer
+
+        Return:
+
+        '''
+        try:
+            job=self.jobs[id]
+            job.stop()
+        except:
+            raise NoID(id)
+        job.stop()
+
+    
+    def startSMS(self, message, recipients, provider="default"):
+        '''Versendet eine SMS.
+
+        Keywords:
+        message[string]: Nachricht
+        recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
+        provider[string]: Provider über den geschickt werden soll
+
+        Return:
+        id[hash]: Die ID des Auftrages
+
+        '''
+        if not "sms" in self.features:
+            raise NotSupportedFeature("sms")
+        id = self.jobqueue.newSMS(message,recipients,provider,user=self.name)
+        self.jobs[id]=self.jobqueue[id]
+        return id
+    
+    
+    def startFAX(self, subject, fax, recipients, provider="default"):
+        '''Versendet ein FAX.
+
+        Keywords:
+        subject[string]: der Betreff
+        fax[string]: das pdf base64 kodiert
+        recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
+        provider[string]: Provider über den geschickt werden soll
+
+        Return:
+        id[hash]: Die ID des Auftrages
+
+        '''
+        logger.debug("startFAX(%s,%s,%s,%s)"%(subject, fax, recipients, provider))
+        if not "fax" in self.features:
+            raise NotSupportedFeature("fax")
+        
+        if type(fax) != list:
+            fax=[fax]
+        f=[i.data for i in fax]
+
+        id = self.jobqueue.newFAX(subject, f,recipients,provider,user=self.name)
+        self.jobs[id]=self.jobqueue[id]
+        return id
+
+    def startMail(self, subject,  body, recipients, frm, provider="default"):
+        '''Versendet eine Email.
+
+        Keywords:
+        subject[string]: der Betreff
+        body[string]: der Email Body
+        recipients[list]: eine Liste von Emailadressen
+        frm[string]: Die Absender Emailadresse
+        provider[string]: Provider über den geschickt werden soll
+
+        Return:
+        id[hash]: Die ID des Auftrages
+
+        '''
+        if not "mail" in self.features:
+            raise NotSupportedFeature("mail")
+        logger.debug("startMail(%s,%s,%s,%s,%s)"%(subject, body, recipients, frm, provider))
+        id = self.jobqueue.newMail(subject, body, recipients, frm,  provider,user=self.name)
+        self.jobs[id]=self.jobqueue[id]
+        return id    
+       
+    def getProvider(self, typ):
+        '''Gibt eine Liste aller verfügbaren Provider zurück.
+
+        Keywords:
+        typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
+                     Einer der Liste ["sms","fax","mail"]
+
+        Return:
+        providerlist[list]: Eine Liste aller möglichen Provider
+
+        '''
+        if not typ in self.features:
+            raise NotSupportedFeature(typ)
+       
+        return self.jobqueue.providerlist.getProviderlist(typ)
+        
+    def getDefaultProvider(self, typ):
+        '''Gibt den Standardprovider zurück.
+ 
+        Keywords:
+        typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
+                     Einer der Liste ["sms","fax","mail"]
+
+        Return:
+        provider[string]: Der Standardprovider für den angeben Typ
+
+
+        '''
+        if not typ in self.features:
+            raise NotSupportedFeature(typ)
+       
+        return self.jobqueue.providerlist.getDefault(typ)["name"]
+        
+class Admin(User):
+    def __init__(self, name, jobqueue):
+        User.__init__(self, name, jobqueue)
+        self.jobs=jobqueue.jobs
--- a/iro/worker.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-# Worker code
-
-from multiprocessing import Process
-import logging
-logger = logging.getLogger("iro.worker")
-
-class Worker(Process):
-    def __init__(self,queue):
-        Process.__init__(self)
-        self.queue=queue
-        
-    def run(self):
-        logger.info('Workerprocess läuft nun...')
-        indifier=0
-        while 1:
-            job=self.queue.get()
-            if job is None:
-                break # reached end of queue
-            indifier+=1
-            logger.info('ein neuer Job(%d)' %(indifier))
-            try:
-                job.start(indifier)
-                logger.info('Job(%d) fertig ;)'%(indifier))
-            except:
-                job.setStatus("error")
-                logger.exception('Job(%d) fehlgeschlagen :('%(indifier))
-
--- a/iro/xmlrpc/AuthentificateXMLRPCServer.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-# Server code
-import SimpleXMLRPCServer
-import string,base64
-
-class AuthentificateXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
-    def do_POST(self):
-        try:
-            header =  self.headers['Authorization']
-            type, user_passwd = header.split()
-            username, password = string.split(base64.decodestring(user_passwd), ':')
-            if self.testUser(username,password):
-                SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST(self)
-            else:
-                self.report_error(401)  
-        except:
-            self.report_error(401)
-
-    def report_error (self,code):
-        '''
-        Send back an errorcode
-        '''
-        #it is important to read out the complete sended request , 
-        # but throw the data away, because an error should be send back
-        try:
-            max_chunk_size = 10*1024*1024
-            size_remaining = int(self.headers["content-length"])
-            while size_remaining:
-                chunk_size = min(size_remaining, max_chunk_size)
-                size_remaining -= len(self.rfile.read(chunk_size))
-        except:
-            pass
-
-        #now just send the error back
-        special_errors={401:self.report_401, 
-                                    404:self.report_404}
-        if special_errors.has_key(code):
-             special_errors[code]()
-        else:
-            self.send_response(code)
-            self.end_headers()
-            self.connection.shutdown(1)
-    
-    def report_401(self):
-        self.send_response(401)
-        self.send_header("WWW-Authenticate", 'Basic realm="%s"'% self.server.relam)
-        response = 'Unauthorised'
-        self.send_header("Content-type", "text/plain")
-        self.send_header("Content-length", str(len(response)))
-        self.end_headers()
-        self.wfile.write(response)
-        # shut down the connection
-        self.wfile.flush()
-        self.connection.shutdown(1)
-        
-    def testUser(self,username,password):
-        """
-        Function for testing authentification
-        """
-        if username=="test" and password=="test":
-                return True
-
-        return False
-
-
-
-def test():
-    server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 8000),AuthentificateXMLRPCRequestHandler)
-    server.relam="xmlrpc"
-    server.register_introspection_functions()
-    server.register_function(lambda x: x*x, 'square')
-    server.serve_forever()
-
-if __name__ == '__main__':
-    test()
-
-
--- a/iro/xmlrpc/SecureAuthentificateXMLRPCServer.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-# Server code
-import AuthentificateXMLRPCServer
-import SecureXMLRPCServer
-
-class SecureAuthentificateXMLRPCRequestHandler(SecureXMLRPCServer.SecureXMLRPCRequestHandler,AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler):
-    def do_POST(self):
-        AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.do_POST(self)
-
-
-def test():
-    server = SecureXMLRPCServer.SecureXMLRPCServer(("localhost", 8000),requestHandler=SecureAuthentificateXMLRPCRequestHandler,certificate="./certs/test.cert.pem",privatekey="./certs/test.key.pem")
-    server.relam="xmlrpc"
-    server.register_introspection_functions()
-    server.register_function(lambda x: x*x, 'square')
-    server.serve_forever()
-
-if __name__ == '__main__':
-    test()
-
-
--- a/iro/xmlrpc/SecureUserdbXMLRPCServer.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-from SecureAuthentificateXMLRPCServer import  SecureAuthentificateXMLRPCRequestHandler
-from SecureXMLRPCServer import SecureXMLRPCServer
-import os, hashlib
-
-class UserDB:
-    '''
-    class for managing all xmlrpc users
-    - each user is indyfied via a hash value, which is created out of the username + password
-    '''
-    def __init__(self,userClass, userlist,jobqueue):
-        self.salt=os.urandom(20)
-        self.jobqueue=jobqueue
-        self.userClass=userClass
-        self.userlist={}
-        for user in userlist:
-            self.createUser(user)
-
-    def createHash(self,user):
-        """
-        returns a hash out of username and the password and self.salt
-        user is a directory with two keys: username and password
-        """
-        m=hashlib.sha512()
-        m.update(user["name"])
-        m.update(self.salt)
-        m.update(user["password"])
-        return m.hexdigest()
-
-    def createUser(self,user):
-        self.userlist[self.createHash(user)]=self.userClass(user["name"],self.jobqueue)
-
-    def __getitem__(self,key):
-        return self.userlist[key]
-
-        
-class SecureAuthentificateXMLRPCRequestHandler2(SecureAuthentificateXMLRPCRequestHandler):
-    def testUser(self,username,password):
-        """
-        Function for testing authentification
-        """
-        return self.server.activateUser(username,password)
-
-class SecureUserDBXMLRPCServer(SecureXMLRPCServer):
-    def __init__(self, addr, userdb, 
-                 requestHandler=SecureAuthentificateXMLRPCRequestHandler2,
-                 certificate="server.cert", privatekey="server.pem",
-                 logRequests=1):
-        SecureXMLRPCServer.__init__(self, addr, requestHandler, certificate, privatekey, logRequests)
-        self.relam="xmlrpc"
-        self.userdb=userdb
-
-    def activateUser(self,username,password):
-        """
-        Function is executed, if someone ant to login
-        -searches for a regular user in the userdb and then put all methods of the user as XMLRPC
-        returns weather a user was found or not
-        """
-        try:
-            user = self.userdb[self.userdb.createHash({"name":username,"password":password})]
-            self.register_instance(user)
-            return True
-        except KeyError:
-            return False
--- a/iro/xmlrpc/SecureXMLRPCServer.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-"""
-SecureXMLRPCServer module using pyOpenSSL 0.5
-Written 0907.2002
-by Michal Wallace
-http://www.sabren.net/
-
-This acts exactly like SimpleXMLRPCServer
-from the standard python library, but
-uses secure connections. The technique
-and classes should work for any SocketServer
-style server. However, the code has not
-been extensively tested.
-
-This code is in the public domain.
-It is provided AS-IS WITH NO WARRANTY WHATSOEVER.
-"""
-import SocketServer
-import os, socket
-import SimpleXMLRPCServer
-from OpenSSL import SSL
-
-class SSLWrapper:
-    """
-    This whole class exists just to filter out a parameter
-    passed in to the shutdown() method in SimpleXMLRPC.doPOST()
-    """
-    def __init__(self, conn):
-        """
-        Connection is not yet a new-style class,
-        so I'm making a proxy instead of subclassing.
-        """
-        self.__dict__["conn"] = conn
-    def __getattr__(self,name):
-        return getattr(self.__dict__["conn"], name)
-    def __setattr__(self,name, value):
-        setattr(self.__dict__["conn"], name, value)
-    def shutdown(self, how=1):
-        """
-        SimpleXMLRpcServer.doPOST calls shutdown(1),
-        and Connection.shutdown() doesn't take
-        an argument. So we just discard the argument.
-        """
-        self.__dict__["conn"].shutdown()
-    def accept(self):
-        """
-        This is the other part of the shutdown() workaround.
-        Since servers create new sockets, we have to infect
-        them with our magic. :)
-        """
-        c, a = self.__dict__["conn"].accept()
-        return (SSLWrapper(c), a)
-
-
-
-class SecureTCPServer(SocketServer.TCPServer):
-    """
-    Just like TCPServer, but use a socket.
-    This really ought to let you specify the key and certificate files.
-    """
-    def __init__(self, server_address, RequestHandlerClass,certificate,privatekey):
-        SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass)
-
-        ## Same as normal, but make it secure:
-        ctx = SSL.Context(SSL.SSLv23_METHOD)
-        ctx.set_options(SSL.OP_NO_SSLv2)
-
-        ctx.use_privatekey_file (privatekey)
-        ctx.use_certificate_file(certificate)
-
-        self.socket = SSLWrapper(SSL.Connection(ctx, socket.socket(self.address_family,
-                                                                  self.socket_type)))
-        self.server_bind()
-        self.server_activate()
-
-
-class SecureXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
-    def setup(self):
-        """
-        We need to use socket._fileobject Because SSL.Connection
-        doesn't have a 'dup'. Not exactly sure WHY this is, but
-        this is backed up by comments in socket.py and SSL/connection.c
-        """
-        self.connection = self.request # for doPOST
-        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
-        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
-    
-
-class SecureXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer, SecureTCPServer):
-    def __init__(self, addr,
-                 requestHandler=SecureXMLRPCRequestHandler,
-                 certificate="server.cert",privatekey="server.pem",
-                 logRequests=1):
-        """
-        This is the exact same code as SimpleXMLRPCServer.__init__
-        except it calls SecureTCPServer.__init__ instead of plain
-        old TCPServer.__init__
-        """
-        self.funcs = {}
-        self.logRequests = logRequests
-        self.instance = None
-        SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self,False,None)
-        SecureTCPServer.__init__(self, addr, requestHandler,certificate=certificate,privatekey=privatekey)
-
-
-def test():
-    server = SecureXMLRPCServer.SecureXMLRPCServer(("localhost", 8000),requestHandler=SecureXMLRPCRequestHandler,certificate="./certs/test.cert.pem",privatekey="./certs/test.key.pem")
-    server.register_introspection_functions()
-    server.register_function(lambda x: x*x, 'square')
-    server.serve_forever()
-
-if __name__ == '__main__':
-    test()
-
--- a/iro/xmlrpc/__init__.py	Wed Dec 21 22:07:48 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-from AuthentificateXMLRPCServer import AuthentificateXMLRPCRequestHandler
-from SecureXMLRPCServer import SecureXMLRPCRequestHandler, SecureXMLRPCServer
-from SecureAuthentificateXMLRPCServer import SecureAuthentificateXMLRPCRequestHandler
-from SecureUserdbXMLRPCServer import SecureUserDBXMLRPCServer, UserDB
--- a/setup.py	Wed Dec 21 22:07:48 2011 +0100
+++ b/setup.py	Thu Sep 27 17:15:46 2012 +0200
@@ -1,2 +1,59 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
 from setuptools import setup
-setup(name="iro",version='0.1',install_requires=["xmlrpclib","ConfigParser","base64","multiprocessing"])
+from iro import __version__
+
+def refresh_plugin_cache():
+        from twisted.plugin import IPlugin, getPlugins
+        list(getPlugins(IPlugin))
+
+setup(name='iro',
+        version=__version__,
+        packages=['iro','iro.controller','iro.view','iro.model','iro.offer','iro.tests', 'iro.test_helpers','twisted.plugins'],
+        setup_requires = ["ngmodules>=0.2","mock"],
+        install_requires=['twisted>=11.1.0',"ConfigParser","sqlalchemy","MySQL-python","SOAPpy",'decorator', 'txJSON-RPC'],
+        #test_suite="tests",                               #ToDo switch to trial
+        description='Non Blocking Interface for sending a bunsh of SMSes, FAXes and Mails.',
+        author='Sandro Knauß',
+        author_email='knauss@netzguerilla.net',
+        url='https://netzguerilla.net/iro',
+        scripts=["bin/iro-install"],
+        package_data={
+            'twisted': ['plugins/iro_plugin.py'],
+        },
+        license = "MIT",
+        classifiers=[
+            "Development Status :: 3 - Alpha",
+            "Environment :: No Input/Output (Daemon)",
+            "Framework :: Twisted",
+            "Topic :: Communications :: Fax",
+            "Topic :: Communications :: Email",
+            "Topic :: Communications :: Sms",
+            "Programming Language :: Python",
+            "License :: OSI Approved :: MIT License",
+            ]
+        )
+
+refresh_plugin_cache()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sqlalchemy_schemadisplay3.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,194 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# updated SQLA schema display to work with pydot 1.0.2
+# download from: http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SchemaDisplay
+
+
+from sqlalchemy.orm.properties import PropertyLoader
+import pydot
+import types
+
+__all__ = ['create_uml_graph', 'create_schema_graph', 'show_uml_graph', 'show_schema_graph']
+
+def _mk_label(mapper, show_operations, show_attributes, show_datatypes, bordersize):
+    html = '<<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="%d" BALIGN="LEFT"><TR><TD><FONT POINT-SIZE="10">%s</FONT></TD></TR>' % (bordersize, mapper.class_.__name__)
+    def format_col(col):
+        colstr = '+%s' % (col.name)
+        if show_datatypes:
+            colstr += ' : %s' % (col.type.__class__.__name__)
+        return colstr
+            
+    if show_attributes:
+        html += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % '<BR ALIGN="LEFT"/>'.join(format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key))
+    else:
+        [format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key)]
+    if show_operations:
+        html += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % '<BR ALIGN="LEFT"/>'.join(
+            '%s(%s)' % (name,", ".join(default is _mk_label and ("%s") % arg or ("%s=%s" % (arg,repr(default))) for default,arg in 
+                zip((func.func_defaults and len(func.func_code.co_varnames)-1-(len(func.func_defaults) or 0) or func.func_code.co_argcount-1)*[_mk_label]+list(func.func_defaults or []), func.func_code.co_varnames[1:])
+            ))
+            for name,func in mapper.class_.__dict__.items() if isinstance(func, types.FunctionType) and func.__module__ == mapper.class_.__module__
+        )
+    html+= '</TABLE>>'
+    return html
+
+
+def create_uml_graph(mappers, show_operations=True, show_attributes=True, show_multiplicity_one=False, show_datatypes=True, linewidth=1.0, font="Bitstream-Vera Sans"):
+    graph = pydot.Dot(prog='neato',mode="major",overlap="0", sep="0.01",dim="3", pack="True", ratio=".75")
+    relations = set()
+    for mapper in mappers:
+        graph.add_node(pydot.Node(mapper.class_.__name__,
+            shape="plaintext", label=_mk_label(mapper, show_operations, show_attributes, show_datatypes, linewidth),
+            fontname=font, fontsize="8.0",
+        ))
+        if mapper.inherits:
+            graph.add_edge(pydot.Edge(mapper.inherits.class_.__name__,mapper.class_.__name__,
+                arrowhead='none',arrowtail='empty', style="setlinewidth(%s)" % linewidth, arrowsize=str(linewidth)))
+        for loader in mapper.iterate_properties:
+            if isinstance(loader, PropertyLoader) and loader.mapper in mappers:
+                if hasattr(loader, 'reverse_property'):
+                    relations.add(frozenset([loader, loader.reverse_property]))
+                else:
+                    relations.add(frozenset([loader]))
+
+    for relation in relations:
+        #if len(loaders) > 2:
+        #    raise Exception("Warning: too many loaders for join %s" % join)
+        args = {}
+        def multiplicity_indicator(prop):
+            if prop.uselist:
+                return ' *'
+            if any(col.nullable for col in prop.local_side):
+                return ' 0..1'
+            if show_multiplicity_one:
+                return ' 1'
+            return ''
+        
+        if len(relation) == 2:
+            src, dest = relation
+            from_name = src.parent.class_.__name__
+            to_name = dest.parent.class_.__name__
+            
+            def calc_label(src,dest):
+                return '+' + src.key + multiplicity_indicator(src)
+            args['headlabel'] = calc_label(src,dest)
+            
+            args['taillabel'] = calc_label(dest,src)
+            args['arrowtail'] = 'none'
+            args['arrowhead'] = 'none'
+            args['constraint'] = False
+        else:
+            prop, = relation
+            from_name = prop.parent.class_.__name__
+            to_name = prop.mapper.class_.__name__
+            args['headlabel'] = '+%s%s' % (prop.key, multiplicity_indicator(prop))
+            args['arrowtail'] = 'none'
+            args['arrowhead'] = 'vee'
+        
+        graph.add_edge(pydot.Edge(from_name,to_name,
+            fontname=font, fontsize="7.0", style="setlinewidth(%s)"%linewidth, arrowsize=str(linewidth),
+            **args)
+        )
+
+    return graph
+
+#from sqlalchemy import Table, text
+
+def _render_table_html(table, metadata, show_indexes, show_datatypes):
+    def format_col_type(col):
+        try:
+            return col.type.get_col_spec()
+        except NotImplementedError:
+            return str(col.type)
+        except AttributeError:
+            return str(col.type)
+    def format_col_str(col):
+         if show_datatypes:
+             return "- %s : %s" % (col.name, format_col_type(col))
+         else:
+             return "- %s" % col.name
+    html = '<<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0"><TR><TD ALIGN="CENTER">%s</TD></TR><TR><TD BORDER="1" CELLPADDING="0"></TD></TR>' % table.name 
+
+    html += ''.join('<TR><TD ALIGN="LEFT" PORT="%s">%s</TD></TR>' % (col.name, format_col_str(col)) for col in table.columns)
+    html += '</TABLE>>'
+    return html
+
+def create_schema_graph(tables=None, metadata=None, show_indexes=True, show_datatypes=True, font="Bitstream-Vera Sans",
+    concentrate=True, relation_options={}, rankdir='TB'):
+    relation_kwargs = {
+        'fontsize':"7.0"
+    }
+    relation_kwargs.update(relation_options)
+    
+    if not metadata and len(tables):
+        metadata = tables[0].metadata
+    elif not tables and metadata:
+        if not len(metadata.tables):
+            metadata.reflect()
+        tables = metadata.tables.values()
+    else:
+        raise Exception("You need to specify at least tables or metadata")
+    
+    graph = pydot.Dot(prog="dot",mode="ipsep",overlap="ipsep",sep="0.01",concentrate=str(concentrate), rankdir=rankdir)
+    for table in tables:
+        graph.add_node(pydot.Node(str(table.name),
+            shape="plaintext",
+            label=_render_table_html(table, metadata, show_indexes, show_datatypes),
+            fontname=font, fontsize="7.0"
+        ))
+    
+    for table in tables:
+        for fk in table.foreign_keys:
+            edge = [table.name, fk.column.table.name]
+            is_inheritance = fk.parent.primary_key and fk.column.primary_key
+            if is_inheritance:
+                edge = edge[::-1]
+            graph_edge = pydot.Edge(
+                headlabel="+ %s"%fk.column.name, taillabel='+ %s'%fk.parent.name,
+                arrowhead=is_inheritance and 'none' or 'odot' ,
+                arrowtail=(fk.parent.primary_key or fk.parent.unique) and 'empty' or 'crow' ,
+                fontname=font, 
+                #samehead=fk.column.name, sametail=fk.parent.name,
+                *edge, **relation_kwargs
+            )
+            graph.add_edge(graph_edge)
+
+# not sure what this part is for, doesn't work with pydot 1.0.2
+#            graph_edge.parent_graph = graph.parent_graph
+#            if table.name not in [e.get_source() for e in graph.get_edge_list()]:
+#                graph.edge_src_list.append(table.name)
+#            if fk.column.table.name not in graph.edge_dst_list:
+#                graph.edge_dst_list.append(fk.column.table.name)
+#            graph.sorted_graph_elements.append(graph_edge)
+    return graph
+
+def show_uml_graph(*args, **kwargs):
+    from cStringIO import StringIO
+    from PIL import Image
+    iostream = StringIO(create_uml_graph(*args, **kwargs).create_png())
+    Image.open(iostream).show(command=kwargs.get('command','gwenview'))
+
+def show_schema_graph(*args, **kwargs):
+    from cStringIO import StringIO
+    from PIL import Image
+    iostream = StringIO(create_schema_graph(*args, **kwargs).create_png())
+    Image.open(iostream).show(command=kwargs.get('command','gwenview'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/twisted/plugins/iro_plugin.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,47 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from zope.interface import implements
+
+from twisted.python import usage
+from twisted.application.service import IServiceMaker
+from twisted.plugin import IPlugin
+
+from iro import iro
+
+class Options(usage.Options):
+    synopsis = "[options]"
+    longdesc = "Make an iro server."
+    optParameters = [
+        ['config', 'c', 'iro.conf', 'configuration file.'],
+    ]
+    
+class MyServiceMaker(object):
+    implements(IServiceMaker, IPlugin)
+    
+    tapname = "iro"
+    description = "An iro server."
+    options = Options
+    
+    def makeService(self, config):
+        return iro.makeService(config)
+
+serviceMaker = MyServiceMaker()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/css/960.css	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,1 @@
+.container_12,.container_16{margin-left:auto;margin-right:auto;width:960px}.grid_1,.grid_2,.grid_3,.grid_4,.grid_5,.grid_6,.grid_7,.grid_8,.grid_9,.grid_10,.grid_11,.grid_12,.grid_13,.grid_14,.grid_15,.grid_16{display:inline;float:left;margin-left:10px;margin-right:10px}.push_1,.pull_1,.push_2,.pull_2,.push_3,.pull_3,.push_4,.pull_4,.push_5,.pull_5,.push_6,.pull_6,.push_7,.pull_7,.push_8,.pull_8,.push_9,.pull_9,.push_10,.pull_10,.push_11,.pull_11,.push_12,.pull_12,.push_13,.pull_13,.push_14,.pull_14,.push_15,.pull_15{position:relative}.container_12 .grid_3,.container_16 .grid_4{width:220px}.container_12 .grid_6,.container_16 .grid_8{width:460px}.container_12 .grid_9,.container_16 .grid_12{width:700px}.container_12 .grid_12,.container_16 .grid_16{width:940px}.alpha{margin-left:0}.omega{margin-right:0}.container_12 .grid_1{width:60px}.container_12 .grid_2{width:140px}.container_12 .grid_4{width:300px}.container_12 .grid_5{width:380px}.container_12 .grid_7{width:540px}.container_12 .grid_8{width:620px}.container_12 .grid_10{width:780px}.container_12 .grid_11{width:860px}.container_16 .grid_1{width:40px}.container_16 .grid_2{width:100px}.container_16 .grid_3{width:160px}.container_16 .grid_5{width:280px}.container_16 .grid_6{width:340px}.container_16 .grid_7{width:400px}.container_16 .grid_9{width:520px}.container_16 .grid_10{width:580px}.container_16 .grid_11{width:640px}.container_16 .grid_13{width:760px}.container_16 .grid_14{width:820px}.container_16 .grid_15{width:880px}.container_12 .prefix_3,.container_16 .prefix_4{padding-left:240px}.container_12 .prefix_6,.container_16 .prefix_8{padding-left:480px}.container_12 .prefix_9,.container_16 .prefix_12{padding-left:720px}.container_12 .prefix_1{padding-left:80px}.container_12 .prefix_2{padding-left:160px}.container_12 .prefix_4{padding-left:320px}.container_12 .prefix_5{padding-left:400px}.container_12 .prefix_7{padding-left:560px}.container_12 .prefix_8{padding-left:640px}.container_12 .prefix_10{padding-left:800px}.container_12 .prefix_11{padding-left:880px}.container_16 .prefix_1{padding-left:60px}.container_16 .prefix_2{padding-left:120px}.container_16 .prefix_3{padding-left:180px}.container_16 .prefix_5{padding-left:300px}.container_16 .prefix_6{padding-left:360px}.container_16 .prefix_7{padding-left:420px}.container_16 .prefix_9{padding-left:540px}.container_16 .prefix_10{padding-left:600px}.container_16 .prefix_11{padding-left:660px}.container_16 .prefix_13{padding-left:780px}.container_16 .prefix_14{padding-left:840px}.container_16 .prefix_15{padding-left:900px}.container_12 .suffix_3,.container_16 .suffix_4{padding-right:240px}.container_12 .suffix_6,.container_16 .suffix_8{padding-right:480px}.container_12 .suffix_9,.container_16 .suffix_12{padding-right:720px}.container_12 .suffix_1{padding-right:80px}.container_12 .suffix_2{padding-right:160px}.container_12 .suffix_4{padding-right:320px}.container_12 .suffix_5{padding-right:400px}.container_12 .suffix_7{padding-right:560px}.container_12 .suffix_8{padding-right:640px}.container_12 .suffix_10{padding-right:800px}.container_12 .suffix_11{padding-right:880px}.container_16 .suffix_1{padding-right:60px}.container_16 .suffix_2{padding-right:120px}.container_16 .suffix_3{padding-right:180px}.container_16 .suffix_5{padding-right:300px}.container_16 .suffix_6{padding-right:360px}.container_16 .suffix_7{padding-right:420px}.container_16 .suffix_9{padding-right:540px}.container_16 .suffix_10{padding-right:600px}.container_16 .suffix_11{padding-right:660px}.container_16 .suffix_13{padding-right:780px}.container_16 .suffix_14{padding-right:840px}.container_16 .suffix_15{padding-right:900px}.container_12 .push_3,.container_16 .push_4{left:240px}.container_12 .push_6,.container_16 .push_8{left:480px}.container_12 .push_9,.container_16 .push_12{left:720px}.container_12 .push_1{left:80px}.container_12 .push_2{left:160px}.container_12 .push_4{left:320px}.container_12 .push_5{left:400px}.container_12 .push_7{left:560px}.container_12 .push_8{left:640px}.container_12 .push_10{left:800px}.container_12 .push_11{left:880px}.container_16 .push_1{left:60px}.container_16 .push_2{left:120px}.container_16 .push_3{left:180px}.container_16 .push_5{left:300px}.container_16 .push_6{left:360px}.container_16 .push_7{left:420px}.container_16 .push_9{left:540px}.container_16 .push_10{left:600px}.container_16 .push_11{left:660px}.container_16 .push_13{left:780px}.container_16 .push_14{left:840px}.container_16 .push_15{left:900px}.container_12 .pull_3,.container_16 .pull_4{left:-240px}.container_12 .pull_6,.container_16 .pull_8{left:-480px}.container_12 .pull_9,.container_16 .pull_12{left:-720px}.container_12 .pull_1{left:-80px}.container_12 .pull_2{left:-160px}.container_12 .pull_4{left:-320px}.container_12 .pull_5{left:-400px}.container_12 .pull_7{left:-560px}.container_12 .pull_8{left:-640px}.container_12 .pull_10{left:-800px}.container_12 .pull_11{left:-880px}.container_16 .pull_1{left:-60px}.container_16 .pull_2{left:-120px}.container_16 .pull_3{left:-180px}.container_16 .pull_5{left:-300px}.container_16 .pull_6{left:-360px}.container_16 .pull_7{left:-420px}.container_16 .pull_9{left:-540px}.container_16 .pull_10{left:-600px}.container_16 .pull_11{left:-660px}.container_16 .pull_13{left:-780px}.container_16 .pull_14{left:-840px}.container_16 .pull_15{left:-900px}.clear{clear:both;display:block;overflow:hidden;visibility:hidden;width:0;height:0}.clearfix:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}* html .clearfix,*:first-child+html .clearfix{zoom:1}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/css/reset.css	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,1 @@
+html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/css/style-ie.css	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,15 @@
+#head #menu {
+	
+	width: 750px;
+	
+}
+
+	#head #menu li {
+		
+		display: inline;
+		position: relative;
+		float: left;
+		margin: 0px;
+		padding: 0px;
+		
+	}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/css/style.css	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,975 @@
+
+body {
+	
+	background-color: #eee;
+	color: #111;
+	margin: 0px;
+	padding: 0px;
+	font-family: "DroidSans", "Trebuchet MS", sans-serif;
+	line-height: 1.6em;
+	font-size: 1.3em;
+	
+}
+
+	#head-container {
+		
+		display: block;
+		position: relative;
+		width: 100%;
+		height: 90px;
+		
+		background: #111 url(../images/head.png) top left repeat-x;
+		
+	}
+	
+		#head {
+			
+			display: block;
+			position: relative;
+			width: 960px;
+			height: 90px;
+			margin: 0px auto;			
+			
+		}
+			
+			#head #logo {
+			
+				display: block;
+				position: absolute;
+				left: 0px;
+				bottom: 10px;
+				width: 200px;
+				height: 70px;
+			
+			}
+
+				#head #logo a {
+					
+					display: block;
+					position: relative;
+					width: 200px;
+					padding-left: 70px;
+					height: 70px;
+					background: url(../images/netzguerilla-3.png) 0px 0px no-repeat;
+					text-decoration: none;
+					
+				}
+
+				#head #logo a span {
+			
+					display: none;
+			
+				}
+				
+			#head #menu {
+				
+				display: block;
+				position: absolute;
+				right: 0px;
+				bottom: 0px;
+				margin: 0px;
+				padding: 0px;
+				
+			}
+
+				#head #menu li {
+					
+					display: inline-block;
+					position: relative;
+					margin: 0px;
+					padding: 0px;
+					
+				}
+				
+					#head #menu li a {
+					
+						display: block;
+						position: relative;
+						
+						color: #c03;
+						text-decoration: none;
+						text-transform: uppercase;
+						padding: 0px 7px;
+						background: #333 url(../images/fade-top.png) bottom left repeat-x;
+						margin-left: 5px;
+						font-size: .8em;
+						font-weight: bold;
+						
+						-webkit-border-top-left-radius: 4px;
+						-webkit-border-top-right-radius: 4px;
+						-moz-border-radius-topleft: 4px;
+						-moz-border-radius-topright: 4px;
+						border-top-left-radius: 4px;
+						border-top-right-radius: 4px;
+						
+					}
+					
+						#head #menu li a:hover {
+						
+							background: #c03 url(../images/fade-top.png) bottom left repeat-x;
+							color: #fff;
+							
+						}
+
+						#head #menu li a.active {
+							
+							background-color: #eee;
+							background-image: none;
+							color: #000;
+							
+						}
+					
+	#content-container {
+		
+		display: block;
+		position: relative;
+		
+	}
+		#content { 
+		
+			padding: 20px 0px;		
+			
+		}
+	
+		#main {}
+		
+			#main h2 {
+				
+				display: block;
+				position: relative;
+				color: #a01;
+				font-size: 1.8em;
+				line-height: 1.4em;
+				margin-bottom: 15px;
+				text-shadow: 0px 0px 5px #fff;
+				
+			}
+			
+			#main h3 {
+			
+				display: block;
+				position: relative;
+				color: #b02;
+				font-size: 1.4em;
+				line-height: 1.4em;
+				margin: 10px 0px;
+			
+			}
+			
+			#main h4 {
+			
+				display: block;
+				position: relative;
+				color: #a01;
+				font-size: 1.3em;
+				line-height: 1.4em;
+				margin: 10px 0px;
+			
+			}
+			
+			#main h5 {
+			
+				display: block;
+				position: relative;
+				color: #900;
+				font-size: 1.2em;
+				line-height: 1.4em;
+				margin: 10px 0px;
+			
+			}
+			
+			#main .item {
+				
+				display: block;
+				position: relative;
+				
+				padding: 30px 40px;
+				
+				border-radius: 15px;
+				-o-border-radius: 15px;
+				-moz-border-radius: 15px;
+				-webkit-border-radius: 15px;
+
+				-moz-box-shadow: 0px 0px 20px #eee;
+				-o-box-shadow: 0px 0px 20px #eee;
+				-webkit-box-shadow: 0px 0px 20px #eee;
+				box-shadow: 0px 0px 20px #eee;
+				
+				background-color: #fff;
+				margin-bottom: 20px;
+				font-size: .9em;
+				
+			}
+			
+				#main .item p {
+					
+					margin: 5px 0px;
+					
+				}
+						
+				#main .item a {
+					
+					text-decoration: none;
+					color: #b02;
+					
+				}
+				
+				#main .item ol {
+					
+					list-style-type: none;
+					list-style-position: inside;
+					
+				}
+				
+					#main .item ol li {
+						
+						margin-left: 2em;
+						
+					}
+																			
+				#main .item table {
+
+					width: 100%;
+					margin: 0px;
+
+					-webkit-border-radius: 5px;
+					-moz-border-radius: 5px;
+					border-radius: 5px;
+
+					-moz-box-shadow: 0px 0px 15px #ccc;
+					-o-box-shadow: 0px 0px 15px #ccc;
+					-webkit-box-shadow: 0px 0px 15px #ccc;
+					box-shadow: 0px 0px 15px #ccc;
+
+
+				}
+				
+					#main .item table.docs {
+				
+						font-size: .8em;
+
+					}
+
+					#main .item table.docs td {
+				
+						font-weight: bold;
+						padding-right: 0.2em;
+						min-width: 120px;
+
+					}
+
+					#main .item table.docs td+td {
+				
+						font-weight: normal;
+						padding-right: 0.2em;
+						min-width: 80px;
+
+					}
+
+					#main .item table.docs td+td+td {
+					
+						font-weight: normal;
+						padding-right: 0em;
+						white-space: normal;
+
+					}
+
+					#main .item table td {
+
+						background-color: #eee;
+						line-height: 1.7em;
+
+					}
+
+					#main .item table thead tr td {
+
+						background-color: #c03;
+						color: #fff;
+						font-size: .8em;
+						padding: 5px;
+
+					}
+
+					#main .item table tbody tr td {
+
+						color: #222;
+						vertical-align: top;
+
+					}
+
+					#main .item table tbody tr td.content {
+
+						padding: 10px;
+						font-size: .8em;
+						
+					}
+
+					#main .item table tbody tr td.content strong.title {
+
+						display: inline-block;
+						position: relative;
+						max-width: 140px;
+						overflow: hidden;
+						
+					}
+
+					#main .item table tbody tr td.content span.meta {
+
+						display: block;
+						position: relative;
+						max-width: 140px;
+						overflow: hidden;
+						line-height: 1.4em;
+						font-size: .8em;
+						
+					}
+
+					#main .item table tbody tr td.content span.hate {
+
+						display: block;
+						position: relative;
+						line-height: 1.4em;
+						border: 1px solid #ccc;
+						padding: 10px;
+						text-align: justify;
+						
+					}
+					#main .item table tbody tr:nth-child(odd) td {
+
+						background-color: #f9f9f9;
+
+					}
+
+					#main .item table tbody tr:nth-child(even) td {
+
+						background-color: #f6f6f6;
+
+					}
+
+					#main .item table tr td:first-child {
+
+						padding-left: 5px;
+
+					}
+
+					#main .item table tr td:last-child {
+
+						padding-right: 5px;
+
+					}
+
+					#main .item table tr td.controls {
+
+						width: 80px;
+						text-align: right;
+
+					}
+
+					#main .item table tr td.controls a {
+
+						display: inline-block;
+						width: 20px;
+						height: 20px;
+						overflow: hidden;
+						text-indent: 30px;
+						background-color: #eee;
+
+						-moz-box-shadow: 0px 0px 5px #ccc;
+						-o-box-shadow: 0px 0px 5px #ccc;
+						-webkit-box-shadow: 0px 0px 5px #ccc;
+						box-shadow: 0px 0px 5px #ccc;
+
+						-webkit-border-radius: 3px;
+						-moz-border-radius: 3px;
+						border-radius: 3px;
+
+						vertical-align: bottom;
+						margin: 0px 0px 4px 2px;
+
+						background-image: url(../images/icons/bug.png);
+						background-position: center center;
+						background-repeat: no-repeat;
+
+					}	
+
+						#main .item table tr td.controls a:hover {
+
+							background-color: #c03;
+
+						}
+
+						#main .item table tr td.controls a.edit {
+
+							background-image: url(../images/icons/pencil.png);		
+
+						}
+
+						#main .item table tr td.controls a.delete {
+
+							background-image: url(../images/icons/bin.png);		
+
+						}
+
+						#main .item table tr td.controls a.details {
+
+							background-image: url(../images/icons/zoom.png);		
+
+						}
+
+						#main .item table tr td.controls a.accept {
+
+							background-image: url(../images/icons/accept.png);		
+
+						}
+
+					/* round corners */
+
+					#main .item table thead tr:first-child td:first-child {
+
+						-webkit-border-top-left-radius: 5px;
+						-moz-border-radius-topleft: 5px;
+						border-top-left-radius: 5px;								
+
+					}
+
+					#main .item table thead tr:first-child td:last-child {
+
+						-webkit-border-top-right-radius: 5px;
+						-moz-border-radius-topright: 5px;
+						border-top-right-radius: 5px;								
+
+					}
+
+					#main .item table tbody tr:last-child td:first-child {
+
+						-webkit-border-bottom-left-radius: 5px;
+						-moz-border-radius-bottomleft: 5px;
+						border-bottom-left-radius: 5px;								
+
+					}
+
+					#main .item table tbody tr:last-child td:last-child {
+
+						-webkit-border-bottom-right-radius: 5px;
+						-moz-border-radius-bottomright: 5px;
+						border-bottom-right-radius: 5px;								
+
+					}
+					
+					#main .item code.copyme {
+						
+						display: block;
+						margin: 0px 0px 10px;
+						padding: 10px;
+						line-height: 1em;
+						border: 1px solid #ccc;
+						background-color: #eee;
+						
+					}
+					
+				/* main view */
+				
+				#main .hate-pagination {
+					
+					display: block;
+					position: relative;
+					line-height: 1.2em;
+					padding: 10px 0px;
+					
+				}
+
+					#main .hate-pagination-back {
+					
+						display: block;
+						position: relative;
+						width: 300px;
+						text-align: left;
+						float: left;
+					
+					}
+
+					#main .hate-pagination-forward {
+					
+						display: block;
+						position: relative;
+						width: 300px;
+						text-align: right;
+						float: right;
+					
+					}
+
+				#main .ad-item {
+
+					display: block;
+					position: relative;
+					border-bottom: 1px solid #ccc;
+					line-height: 1.2em;
+					padding: 20px 0px;
+					text-align: center;
+
+				}
+
+				#main .hate-item {
+					
+					display: block;
+					position: relative;
+					border-bottom: 1px solid #ccc;
+					line-height: 1.2em;
+					padding: 10px 0px;
+					
+				}
+				
+					#main .hate-item .hate-item-icon {
+				
+						display: block;
+						position: relative;
+						width: 48px;
+						padding: 5px 0px 5px 10px;
+						float: left;
+				
+					}
+
+					#main .hate-item .hate-item-main {
+					
+						display: block;
+						position: relative;
+						width: 550px;
+						float: right;
+					
+					}
+				
+					#main .hate-item .hate-item-author {
+					
+						display: block;
+						position: relative;
+						font-size: .8em;
+						font-weight: bold;
+						color: #222;
+					
+					}
+
+						#main .hate-item .hate-item-author a {
+						
+							color: #222;
+							
+						}
+				
+					#main .hate-item .hate-item-content {
+					
+						display: block;
+						position: relative;
+						font-size: .8em;
+						font-weight: normal;
+						color: #444;
+					
+					}
+				
+					#main .hate-item .hate-item-meta {
+					
+						display: block;
+						position: relative;
+						font-size: .6em;
+						color: #666;
+					
+					}
+				
+					#main .hate-item .hate-item-meta a {
+
+						color: #666;
+
+					}
+					
+					#main .hate-item .hate-item-meta a.facepalm {
+
+						display: inline-block;
+						padding-left: 20px;
+						line-height: 16px;
+						background: url(../images/icons/facepalm.png) top left no-repeat;
+
+					}
+					
+					#main .hate-item:hover .hate-item-meta a.facepalm {
+
+						background: url(../images/icons/facepalm-hl.png) top left no-repeat;
+						
+					}
+
+					#main .hate-item .hate-item-meta span.godwin {
+
+						display: inline-block;
+						padding-left: 20px;
+						line-height: 16px;
+						background: url(../images/icons/godwinbonus.png) top left no-repeat;
+
+					}					
+					
+				#main .hate-item:hover {
+					
+					background-color: #fce;
+					
+				}
+					
+					#main .hate-item:hover a {
+					
+						color: #c03;
+					
+					}
+					
+				#main .hate-detail {
+
+					display: block;
+					position: relative;
+					border-bottom: 1px solid #ccc;
+					line-height: 1.2em;
+					padding: 10px 0px;
+
+				}
+
+					#main .hate-detail .hate-item-icon {
+
+						display: block;
+						position: relative;
+						width: 48px;
+						padding: 5px 0px;
+						float: left;
+
+					}
+
+					#main .hate-detail .hate-item-main {
+
+						display: block;
+						position: relative;
+
+					}
+
+					#main .hate-detail .hate-item-author {
+
+						display: block;
+						position: relative;
+						font-size: 1.2em;
+						line-height: 50px;
+						font-weight: bold;
+						color: #222;
+						width: 560px;
+						float: right;
+
+					}
+
+					#main .hate-detail .hate-item-content {
+
+						display: block;
+						position: relative;
+						font-size: 1.2em;
+						line-height: 1.4em;
+						font-weight: normal;
+						color: #444;
+						padding: 10px 0px;
+
+					}
+
+					#main .hate-detail .hate-item-meta {
+
+						display: block;
+						position: relative;
+						font-size: .8em;
+						color: #666;
+
+					}
+
+					#main .hate-detail .hate-item-meta a {
+
+						color: #666;
+
+					}
+
+						#main .hate-detail .hate-item-meta a.facepalm {
+
+							display: inline-block;
+							padding-left: 20px;
+							line-height: 16px;
+							background: url(../images/icons/facepalm.png) top left no-repeat;
+
+						}
+					
+						#main .hate-detail:hover .hate-item-meta a.facepalm {
+
+							background: url(../images/icons/facepalm-hl.png) top left no-repeat;
+						
+						}
+						
+						#main .hate-detail .hate-item-meta span.godwin {
+
+							display: inline-block;
+							padding-left: 20px;
+							line-height: 16px;
+							background: url(../images/icons/godwinbonus.png) top left no-repeat;
+
+						}					
+						
+
+				/* special stuff */
+				
+				#main span.redacted {
+					
+					display: inline-block;
+					background-color: #333;
+					color: #c03;
+					line-height: 1.1em;
+					padding: 0px 5px;
+					
+				}
+				
+				#main code.apikey {
+					
+					font-size: 1.1em; 
+					background-color: #a01; 
+					color: #fff; 
+					display: inline-block; 
+					padding: 0px 5px;
+					
+				}
+				
+				#main a.bookmarklet {
+					
+					font-size: 1.4em; 
+					background-color: #ccc; 
+					color: #666; 
+					border: 1px solid #666;
+					display: inline-block; 
+					padding: 5px 10px;					
+					
+					-webkit-border-radius: 10px;
+					-moz-border-radius: 10px;
+					border-radius: 10px;
+					
+				}
+				
+			#main form input[type=text],
+			#main form input[type=password] {
+
+				display: block;
+				position: relative;
+				border: 1px solid #999;
+				font-size: .9em;
+				padding: 3px;
+
+				border-radius: 5px;
+				-o-border-radius: 5px;
+				-moz-border-radius: 5px;
+				-webkit-border-radius: 5px;
+
+			}
+
+			#main form input[type=submit] {
+
+				display: block;
+				position: relative;
+				border: 1px solid #ccc;
+				color: #111;
+				font-size: .9em;
+				padding: 2px;
+
+				border-radius: 5px;
+				-o-border-radius: 5px;
+				-moz-border-radius: 5px;
+				-webkit-border-radius: 5px;
+
+
+			}
+
+			#main form span.input {
+
+				display: inline-block;
+				position: relative;
+				border: 1px solid #999;
+				font-size: .9em;
+				padding: 0px 2px;
+				background-color: #fff;
+				border-radius: 5px;
+				-o-border-radius: 5px;
+				-moz-border-radius: 5px;
+				-webkit-border-radius: 5px;
+
+
+			}
+
+				#main form span.input input[type=text] {
+
+					display: inline-block;
+					position: relative;
+					border-width: 0px;
+					font-size: 1em;
+					padding: 0px;
+					background-color: #fff;
+
+				}
+
+				#main form span.input input[type=text].right {
+
+					text-align: right;
+
+				}
+
+				#main form span.input input.size-2 {
+
+					width: 1.5em !important;
+
+				}
+
+				#main form span.input input.size-4 {
+
+					width: 2.5em !important;
+
+				}
+
+			#main form label {
+
+				display: block;
+				position: relative;
+				font-size: .75em;
+				color: #666;
+
+			}
+
+			#main form textarea {
+
+				display: block;
+				position: relative;
+				border: 1px solid #999;
+				font-size: .9em;
+				font-family: sans-serif;
+				padding: 3px;
+
+				border-radius: 5px;
+				-o-border-radius: 5px;
+				-moz-border-radius: 5px;
+				-webkit-border-radius: 5px;
+
+			}
+
+
+			#main form fieldset {
+
+				border: 1px solid #ccc;
+				padding: 5px;
+				margin: 5px 0px;
+
+			}
+
+				#main form fieldset legend {
+
+					font-size: .75em;
+					color: #666;
+
+				}
+				
+		#sidebar {}
+		
+			#sidebar h3 {
+				
+				display: block;
+				position: relative;
+				color: #fff;
+				font-size: 1.2em;
+				line-height: 1.4em;
+				margin: 5px 0px;
+				
+			}
+
+			#sidebar .item {
+				
+				display: block;
+				position: relative;
+				
+				padding: 0px 10px;
+				
+				border-radius: 10px;
+				-o-border-radius: 10px;
+				-moz-border-radius: 10px;
+				-webkit-border-radius: 10px;
+
+				-moz-box-shadow: 0px 0px 20px #eee;
+				-o-box-shadow: 0px 0px 20px #eee;
+				-webkit-box-shadow: 0px 0px 20px #eee;
+				box-shadow: 0px 0px 20px #eee;
+				
+				background-color: #333;
+				margin-bottom: 20px;
+				font-size: .8em;			
+				
+			}
+			
+				#sidebar .center {
+				
+					text-align: center;
+				
+				}
+			
+				#sidebar .counter-item .content {
+					
+					font-size: 3em;
+					color: #c03;
+					font-weight: bold;
+					line-height: 1em;
+					padding-bottom: 5px;
+					text-align: center;
+					
+				}
+				
+				#sidebar .submenu-item {
+					
+					background-color: #ddd;
+					
+				}
+
+					#sidebar .submenu-item ul {}
+
+						#sidebar .submenu-item ul li {}
+
+							#sidebar .submenu-item ul li+li {
+								
+								border-top: 1px dotted #999;
+								
+							}
+
+							#sidebar .submenu-item ul a {
+								
+								color: #c03;
+								text-decoration: none;
+								text-transform: uppercase;
+								
+							}
+							
+								#sidebar .submenu-item ul a:hover {
+									
+									color: #a01;
+									
+								}
+
+	#foot-container {
+		
+		display: block;
+		position: relative;
+		background: #222;
+		color: #ddd;
+		
+	}
+	
+		#foot {
+			
+			display: block;
+			position: relative;
+			width: 960px;
+			margin: 0px auto;
+			padding: 20px;
+			font-size: .8em;
+			
+		}
+		
+			#foot a {
+				
+				text-decoration: none;
+				color: #c03;
+				
+			}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/images/db-schema.svg	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.26.3 (20100126.1600)
+ -->
+<!-- Title: G Pages: 1 -->
+<svg width="468pt" height="126pt"
+ viewBox="0.00 0.00 468.00 125.74" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph1" class="graph" transform="scale(0.83274 0.83274) rotate(0) translate(4 147)">
+<title>G</title>
+<polygon fill="white" stroke="white" points="-4,5 -4,-147 559,-147 559,5 -4,5"/>
+<!-- message -->
+<g id="node1" class="node"><title>message</title>
+<polygon fill="none" stroke="black" points="8,-18.5 8,-133.5 112,-133.5 112,-18.5 8,-18.5"/>
+<text text-anchor="start" x="43.5" y="-124.367" font-family="Bitstream-Vera Sans" font-size="7.00">message</text>
+<polygon fill="none" stroke="black" points="9,-118 9,-120 111,-120 111,-118 9,-118"/>
+<text text-anchor="start" x="11" y="-110.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; id : INTEGER</text>
+<text text-anchor="start" x="11" y="-99.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; recipient : VARCHAR(100)</text>
+<text text-anchor="start" x="11" y="-88.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; isBilled : BOOLEAN</text>
+<text text-anchor="start" x="11" y="-77.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; date : DATETIME</text>
+<text text-anchor="start" x="11" y="-66.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; price : NUMERIC(8, 4)</text>
+<text text-anchor="start" x="11" y="-55.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; count : INTEGER</text>
+<text text-anchor="start" x="11" y="-44.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; exID : VARCHAR(100)</text>
+<text text-anchor="start" x="11" y="-33.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; job : VARCHAR(40)</text>
+<text text-anchor="start" x="11" y="-22.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; offer : VARCHAR(100)</text>
+</g>
+<!-- job -->
+<g id="node3" class="node"><title>job</title>
+<polygon fill="none" stroke="black" points="164.5,-79 164.5,-139 251.5,-139 251.5,-79 164.5,-79"/>
+<text text-anchor="start" x="202.5" y="-130.367" font-family="Bitstream-Vera Sans" font-size="7.00">job</text>
+<polygon fill="none" stroke="black" points="166,-124 166,-126 251,-126 251,-124 166,-124"/>
+<text text-anchor="start" x="168" y="-116.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; id : INTEGER</text>
+<text text-anchor="start" x="168" y="-105.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; info : VARCHAR(100)</text>
+<text text-anchor="start" x="168" y="-94.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; status : VARCHAR(7)</text>
+<text text-anchor="start" x="168" y="-83.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; user : VARCHAR(100)</text>
+</g>
+<!-- message&#45;&gt;job -->
+<g id="edge2" class="edge"><title>message&#45;&gt;job</title>
+<path fill="none" stroke="black" d="M120.197,-89.4224C129.482,-91.4926 139.067,-93.6298 148.34,-95.6974"/>
+<ellipse fill="none" stroke="black" cx="152.488" cy="-96.6224" rx="4.00001" ry="4.00001"/>
+<text text-anchor="middle" x="146.627" y="-97.3455" font-family="Bitstream-Vera Sans" font-size="7.00">+ id</text>
+<text text-anchor="middle" x="129.963" y="-84.9699" font-family="Bitstream-Vera Sans" font-size="7.00">+ job</text>
+</g>
+<!-- offer -->
+<g id="node5" class="node"><title>offer</title>
+<polygon fill="none" stroke="black" points="304.5,-4 304.5,-64 405.5,-64 405.5,-4 304.5,-4"/>
+<text text-anchor="start" x="347" y="-55.3667" font-family="Bitstream-Vera Sans" font-size="7.00">offer</text>
+<polygon fill="none" stroke="black" points="306,-49 306,-51 405,-51 405,-49 306,-49"/>
+<text text-anchor="start" x="308" y="-41.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; name : VARCHAR(100)</text>
+<text text-anchor="start" x="308" y="-30.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; provider : VARCHAR(100)</text>
+<text text-anchor="start" x="308" y="-19.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; route : VARCHAR(100)</text>
+<text text-anchor="start" x="308" y="-8.86667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; typ : VARCHAR(100)</text>
+</g>
+<!-- message&#45;&gt;offer -->
+<g id="edge4" class="edge"><title>message&#45;&gt;offer</title>
+<path fill="none" stroke="black" d="M120.053,-67.45C168.906,-60.4948 237.788,-50.6879 288.339,-43.4907"/>
+<ellipse fill="none" stroke="black" cx="292.329" cy="-42.9227" rx="4.00001" ry="4.00001"/>
+<text text-anchor="middle" x="287.912" y="-45.5203" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="128.43" y="-59.6886" font-family="Bitstream-Vera Sans" font-size="7.00">+ offer</text>
+</g>
+<!-- userright -->
+<g id="node2" class="node"><title>userright</title>
+<polygon fill="none" stroke="black" points="458,-49.5 458,-98.5 546,-98.5 546,-49.5 458,-49.5"/>
+<text text-anchor="start" x="485" y="-89.3667" font-family="Bitstream-Vera Sans" font-size="7.00">userright</text>
+<polygon fill="none" stroke="black" points="459,-83 459,-85 545,-85 545,-83 459,-83"/>
+<text text-anchor="start" x="461" y="-75.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; user : VARCHAR(100)</text>
+<text text-anchor="start" x="461" y="-64.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; offer : VARCHAR(100)</text>
+<text text-anchor="start" x="461" y="-53.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; default : INTEGER</text>
+</g>
+<!-- apiuser -->
+<g id="node4" class="node"><title>apiuser</title>
+<polygon fill="none" stroke="black" points="309,-89.5 309,-138.5 401,-138.5 401,-89.5 309,-89.5"/>
+<text text-anchor="start" x="341" y="-129.367" font-family="Bitstream-Vera Sans" font-size="7.00">apiuser</text>
+<polygon fill="none" stroke="black" points="310,-123 310,-125 400,-125 400,-123 310,-123"/>
+<text text-anchor="start" x="312" y="-115.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; name : VARCHAR(100)</text>
+<text text-anchor="start" x="312" y="-104.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; ng_kunde : INTEGER</text>
+<text text-anchor="start" x="312" y="-93.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; apikey : VARCHAR(50)</text>
+</g>
+<!-- job&#45;&gt;apiuser -->
+<g id="edge10" class="edge"><title>job&#45;&gt;apiuser</title>
+<path fill="none" stroke="black" d="M259.827,-110.763C270.366,-111.121 281.558,-111.502 292.424,-111.872"/>
+<ellipse fill="none" stroke="black" cx="296.657" cy="-112.015" rx="4" ry="4"/>
+<text text-anchor="middle" x="291.453" y="-113.767" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="269.028" y="-104.547" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
+</g>
+<!-- apiuser&#45;&gt;userright -->
+<g id="edge8" class="edge"><title>apiuser&#45;&gt;userright</title>
+<path fill="none" stroke="black" d="M409.32,-99.2191C422.569,-95.6138 436.713,-91.7653 449.858,-88.1883"/>
+<text text-anchor="middle" x="442.223" y="-92.3458" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="416.955" y="-90.4616" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
+</g>
+<!-- offer&#45;&gt;userright -->
+<g id="edge6" class="edge"><title>offer&#45;&gt;userright</title>
+<path fill="none" stroke="black" d="M413.521,-49.924C425.531,-53.192 438.098,-56.6116 449.865,-59.8135"/>
+<text text-anchor="middle" x="440.01" y="-59.2118" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="423.375" y="-45.9257" font-family="Bitstream-Vera Sans" font-size="7.00">+ offer</text>
+</g>
+</g>
+</svg>
Binary file web/images/favicon.png has changed
Binary file web/images/netzguerilla-3.png has changed