merging Verschiebe inst Dateien devel
authorSandro Knauß <knauss@netzguerilla.net>
Wed, 21 Dec 2011 21:51:47 +0100
branchdevel
changeset 86 acd4c2006602
parent 85 edf7e94cd607 (diff)
parent 83 cb50c2c53d11 (current diff)
child 91 191c2c1d6e53
merging Verschiebe inst Dateien
iro/iro.conf.inst
--- a/createdoc.py	Wed Dec 21 21:48:27 2011 +0100
+++ b/createdoc.py	Wed Dec 21 21:51:47 2011 +0100
@@ -8,7 +8,9 @@
 import re
 import inspect
 from iro.user import User as Current
-from iro.newuser import User as New
+from iro.newinterface import Interface as New
+
+from createerm import createSchemaPlot,tables
 
 
 
@@ -59,7 +61,7 @@
 
 
 def keywords(f):
-    doc=f.__doc__
+    doc=f.__doc__.decode('utf8')
     kwds=re.search("Keywords:\n(?P<keywords>(?P<whitespace>\s*)(.+\n)*)\n",doc)
     k=kwds.group("keywords")
     #get rid of beginning whitespaces
@@ -67,7 +69,7 @@
     return section(k)
 
 def ret(f):
-    doc=f.__doc__
+    doc=f.__doc__.decode('utf8')
     kwds=re.search("Return:\n(?P<ret>(?P<whitespace>\s*)(.+\n)*)\n",doc)
     k=kwds.group("ret")
     #get rid of beginning whitespaces
@@ -94,15 +96,24 @@
         (args, varargs, keywords, defaults)=inspect.getargspec(m)
         args= [b for b in args if b is not "self"]
         self.func_line=inspect.formatargspec(args, varargs, keywords, defaults)
-        self.description = m.__doc__.split("\n")[0]
+        self.description = m.__doc__.split("\n")[0].decode("utf8")
         self.args=[Arg(a,m) for a in args]
         _, 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 = cls.__doc__.split("\n")[0].decode("utf8") 
+
 
 def main():
     sites=[Site("index.html","Iro"),
            Site("current.html","API Documentation"),
            Site("new.html","geplante API Documentation"),
+           Site("database.html","Datenbank Schema"),
            Site("impressum.html","Impressum"),
            ]
 
@@ -131,7 +142,9 @@
             Method("routes",new_methods),
             Method("defaultRoute",new_methods),
             ]
-
+    
+    t = [Table(f.class_) for f in tables]
+    createSchemaPlot('doc/images/db-schema.svg')
 
     for site in sites:
         print("generiere %s" % site.name)
@@ -139,7 +152,7 @@
         def a(s):
             if s == site:
                 return {"class":"menu active"}
-        stream = tmpl.generate(sites=sites,active=a,current=current,new=newm)
+        stream = tmpl.generate(sites=sites,active=a,current=current,new=newm,tables=t)
         with open('doc/'+site.name, "w") as g:
             g.write(stream.render('html', doctype='html'))
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/createerm.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,48 @@
+from iro import schema
+
+from sqlalchemy.orm import class_mapper
+tables = []
+for attr in schema.__tables__:
+    if attr[0] == '_': continue
+    try:
+        cls = getattr(schema, attr)
+        tables.append(class_mapper(cls))
+    except:
+        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')
--- a/doc/current.html	Wed Dec 21 21:48:27 2011 +0100
+++ b/doc/current.html	Wed Dec 21 21:51:47 2011 +0100
@@ -19,7 +19,7 @@
 		<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>
+				<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="database.html" class="menu">Datenbank Schema</a></li><li><a href="impressum.html" class="menu">Impressum</a></li>
 			</ul>
 		</div>
 	</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/database.html	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,75 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+	
+
+	<head>
+		<title>Iro ·  Datenbank</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="database.html" class="menu active">Datenbank Schema</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>Datenbank Schema</h2>
+				<div class="item">
+			<p>
+			</p>
+			<ol>
+				<li value="1">1. <a href="#schema">Datenbankschema</a></li>
+				<li value="3">3.
+					<a href="#tables">Tabellen</a>
+					<ol>
+						<li value="2.1">2.1 <a href="#table-apiuser">Apiuser</a></li><li value="2.2">2.2 <a href="#table-job">Job</a></li><li value="2.3">2.3 <a href="#table-message">Message</a></li><li value="2.4">2.4 <a href="#table-offer">Offer</a></li><li value="2.5">2.5 <a href="#table-userright">Userright</a></li>
+					</ol>
+				</li>
+			</ol>
+		</div><div class="item" id="schema">
+			<h3>Schema</h3>
+			<img src="images/db-schema.svg">
+			<p>Dies ist eine Übersicht der benutzen Tabellen die Iro benötigt.</p>
+		</div><div class="item" id="tables">
+			<h3>Tabellen</h3>
+			<div class="item" id="table-apiuser">
+				<h4>Apiuser</h4>
+				<p>Die Benutzerdatenbank von Iro.</p>
+			</div><div class="item" id="table-job">
+				<h4>Job</h4>
+				<p>Ein kompletter Auftrag, der an Iro zum verschicken übergeben wird. Status zeigt den generellen Status des Auftrages an (<em>init</em>, <em>started</em>, <em>sending</em>, <em>sended</em> oder <em>error</em>). <em>info</em> wird verwendet um dem Benutzer eine Möglickeit zu geben verschiede Auftragsgruppen zu erstellen.</p>
+			</div><div class="item" id="table-message">
+				<h4>Message</h4>
+				<p>Wenn ein Vorgang von Iro Kosten erzeugt hat wird eine neue Zeile eingefügt. Solange nicht bezahlt wurde ist <em>isBilled=0</em>.</p>
+			</div><div class="item" id="table-offer">
+				<h4>Offer</h4>
+				<p>Alle Routen über die SMS, Faxe und Mails verschickt werden könnnen. <em>provider</em>, <em>typ</em> und <em>route</em> werden verwendet, um die entsprechenden Zugangsdaten laden zu können.</p>
+			</div><div class="item" id="table-userright">
+				<h4>Userright</h4>
+				<p>Über welche Routen darf ein Benutzer Daten verschicken und welches sind die Standardrouten (<em>isDefault=1</em>) für den Benuter.</p>
+			</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/images/db-schema.svg	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,96 @@
+<?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="131pt"
+ viewBox="0.00 0.00 468.00 131.29" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph1" class="graph" transform="scale(0.89313 0.89313) rotate(0) translate(4 143)">
+<title>G</title>
+<polygon fill="white" stroke="white" points="-4,5 -4,-143 521,-143 521,5 -4,5"/>
+<!-- message -->
+<g id="node1" class="node"><title>message</title>
+<polygon fill="none" stroke="black" points="8,-25.5 8,-118.5 98,-118.5 98,-25.5 8,-25.5"/>
+<text text-anchor="start" x="36.5" y="-109.367" font-family="Bitstream-Vera Sans" font-size="7.00">message</text>
+<polygon fill="none" stroke="black" points="9,-103 9,-105 97,-105 97,-103 9,-103"/>
+<text text-anchor="start" x="11" y="-95.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; id : INTEGER</text>
+<text text-anchor="start" x="11" y="-84.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; recipient : VARCHAR</text>
+<text text-anchor="start" x="11" y="-73.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; isBilled : BOOLEAN</text>
+<text text-anchor="start" x="11" y="-62.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; date : DATETIME</text>
+<text text-anchor="start" x="11" y="-51.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; price : NUMERIC(8, 2)</text>
+<text text-anchor="start" x="11" y="-40.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; job : INTEGER</text>
+<text text-anchor="start" x="11" y="-29.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; offer : VARCHAR</text>
+</g>
+<!-- job -->
+<g id="node3" class="node"><title>job</title>
+<polygon fill="none" stroke="black" points="150.5,-75 150.5,-135 235.5,-135 235.5,-75 150.5,-75"/>
+<text text-anchor="start" x="187.5" y="-126.367" font-family="Bitstream-Vera Sans" font-size="7.00">job</text>
+<polygon fill="none" stroke="black" points="152,-120 152,-122 235,-122 235,-120 152,-120"/>
+<text text-anchor="start" x="154" y="-112.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; hash : VARCHAR</text>
+<text text-anchor="start" x="154" y="-101.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; info : VARCHAR</text>
+<text text-anchor="start" x="154" y="-90.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; status : VARCHAR(7)</text>
+<text text-anchor="start" x="154" y="-79.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; user : VARCHAR</text>
+</g>
+<!-- message&#45;&gt;job -->
+<g id="edge2" class="edge"><title>message&#45;&gt;job</title>
+<path fill="none" stroke="black" d="M106.327,-84.57C115.412,-86.7114 124.894,-88.9465 134.112,-91.1194"/>
+<ellipse fill="none" stroke="black" cx="138.236" cy="-92.0912" rx="4.00001" ry="4.00001"/>
+<text text-anchor="middle" x="132.338" y="-92.7431" font-family="Bitstream-Vera Sans" font-size="7.00">+ hash</text>
+<text text-anchor="middle" x="116.118" y="-80.2359" 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="288,-4 288,-64 370,-64 370,-4 288,-4"/>
+<text text-anchor="start" x="320.5" y="-55.3667" font-family="Bitstream-Vera Sans" font-size="7.00">offer</text>
+<polygon fill="none" stroke="black" points="289,-49 289,-51 369,-51 369,-49 289,-49"/>
+<text text-anchor="start" x="291" y="-41.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; name : VARCHAR</text>
+<text text-anchor="start" x="291" y="-30.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; provider : VARCHAR</text>
+<text text-anchor="start" x="291" y="-19.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; route : VARCHAR</text>
+<text text-anchor="start" x="291" y="-8.86667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; typ : VARCHAR</text>
+</g>
+<!-- message&#45;&gt;offer -->
+<g id="edge4" class="edge"><title>message&#45;&gt;offer</title>
+<path fill="none" stroke="black" d="M106.216,-64.6732C153.699,-58.1357 223.047,-48.5878 271.726,-41.8855"/>
+<ellipse fill="none" stroke="black" cx="275.694" cy="-41.3393" rx="4.00001" ry="4.00001"/>
+<text text-anchor="middle" x="271.255" y="-43.9165" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="114.618" y="-56.9504" 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="422.5,-46.5 422.5,-95.5 507.5,-95.5 507.5,-46.5 422.5,-46.5"/>
+<text text-anchor="start" x="448.5" y="-86.3667" font-family="Bitstream-Vera Sans" font-size="7.00">userright</text>
+<polygon fill="none" stroke="black" points="424,-80 424,-82 507,-82 507,-80 424,-80"/>
+<text text-anchor="start" x="426" y="-72.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; user : VARCHAR</text>
+<text text-anchor="start" x="426" y="-61.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; offer : VARCHAR</text>
+<text text-anchor="start" x="426" y="-50.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; isDefault : BOOLEAN</text>
+</g>
+<!-- apiuser -->
+<g id="node4" class="node"><title>apiuser</title>
+<polygon fill="none" stroke="black" points="291.5,-90 291.5,-128 366.5,-128 366.5,-90 291.5,-90"/>
+<text text-anchor="start" x="315.5" y="-119.367" font-family="Bitstream-Vera Sans" font-size="7.00">apiuser</text>
+<polygon fill="none" stroke="black" points="293,-113 293,-115 366,-115 366,-113 293,-113"/>
+<text text-anchor="start" x="295" y="-105.867" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; name : VARCHAR</text>
+<text text-anchor="start" x="295" y="-94.8667" font-family="Bitstream-Vera Sans" font-size="7.00">&#45; apikey : VARCHAR</text>
+</g>
+<!-- job&#45;&gt;apiuser -->
+<g id="edge10" class="edge"><title>job&#45;&gt;apiuser</title>
+<path fill="none" stroke="black" d="M243.641,-106.489C253.891,-106.791 264.704,-107.109 275.063,-107.414"/>
+<ellipse fill="none" stroke="black" cx="279.255" cy="-107.537" rx="4" ry="4"/>
+<text text-anchor="middle" x="274.069" y="-109.312" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="252.825" y="-100.232" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
+</g>
+<!-- apiuser&#45;&gt;userright -->
+<g id="edge6" class="edge"><title>apiuser&#45;&gt;userright</title>
+<path fill="none" stroke="black" d="M374.664,-96.2408C387.32,-92.7048 401.136,-88.8443 414.071,-85.23"/>
+<text text-anchor="middle" x="406.48" y="-89.4392" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="382.256" y="-87.4316" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
+</g>
+<!-- offer&#45;&gt;userright -->
+<g id="edge8" class="edge"><title>offer&#45;&gt;userright</title>
+<path fill="none" stroke="black" d="M378.099,-47.3578C389.816,-50.5456 402.347,-53.9546 414.141,-57.1634"/>
+<text text-anchor="middle" x="404.286" y="-56.5621" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="387.954" y="-43.3591" font-family="Bitstream-Vera Sans" font-size="7.00">+ offer</text>
+</g>
+</g>
+</svg>
--- a/doc/impressum.html	Wed Dec 21 21:48:27 2011 +0100
+++ b/doc/impressum.html	Wed Dec 21 21:51:47 2011 +0100
@@ -19,7 +19,7 @@
 		<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>
+				<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="database.html" class="menu">Datenbank Schema</a></li><li><a href="impressum.html" class="menu active">Impressum</a></li>
 			</ul>
 		</div>
 	</div>
--- a/doc/index.html	Wed Dec 21 21:48:27 2011 +0100
+++ b/doc/index.html	Wed Dec 21 21:51:47 2011 +0100
@@ -19,7 +19,7 @@
 		<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>
+				<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="database.html" class="menu">Datenbank Schema</a></li><li><a href="impressum.html" class="menu">Impressum</a></li>
 			</ul>
 		</div>
 	</div>
--- a/doc/new.html	Wed Dec 21 21:48:27 2011 +0100
+++ b/doc/new.html	Wed Dec 21 21:51:47 2011 +0100
@@ -19,7 +19,7 @@
 		<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>
+				<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="database.html" class="menu">Datenbank Schema</a></li><li><a href="impressum.html" class="menu">Impressum</a></li>
 			</ul>
 		</div>
 	</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/tmpl/database.html	Wed Dec 21 21:51:47 2011 +0100
@@ -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>Datenbank</title>
+	</head>
+	<body>
+		<title>Datenbank Schema</title>
+		<div class="item">
+			<p>
+
+			</p>
+			<ol>
+				<li value="1">1. <a href="#schema">Datenbankschema</a></li>
+				<li value="3">3. 
+					<a href="#tables">Tabellen</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>Dies ist eine Übersicht der benutzen Tabellen die Iro benötigt.</p>
+		</div>
+		<div class="item" id="tables">
+			<h3>Tabellen</h3>
+			<div py:for="table in tables" class="item" id="table-${table.tablename}">
+				<h4>${table.title}</h4>
+				<p py:content="Markup(table.description)">
+					Dieser Tabelle fehlt noch die Beschreibung.
+				</p>
+			</div>
+		</div>
+	</body>
+</html>
--- a/iro/__init__.py	Wed Dec 21 21:48:27 2011 +0100
+++ b/iro/__init__.py	Wed Dec 21 21:51:47 2011 +0100
@@ -1,1 +1,1 @@
- 
+__version__='2.0rc0'
--- a/iro/dump_test_log.py	Wed Dec 21 21:48:27 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()
-
--- a/iro/merlin	Wed Dec 21 21:48:27 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 21:48:27 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/newinterface.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,269 @@
+# -*- 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 twisted.web import soap, xmlrpc, resource, server
+import logging
+logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s')
+
+
+class User(object):
+    def __init__(self,name,userhash):
+        self.name=name
+        self.userhash=userhash
+    
+    def __repr__(self):
+        return"User<'%s','%s'>"%(self.name,self.userhash)
+
+users={"1":User("spam","1"),
+       "2":User("foo","2")
+}
+
+def getuser(userhash):
+    try:
+        return users[userhash]
+    except KeyError:
+        raise UserNotFound()
+
+def with_user(f):
+    def new_f(*args,**kargs):
+        args=list(args)
+        logging.debug("Entering %s"%f.__name__)
+        try:
+            kargs["user"]=getuser(kargs["apikey"])
+            del kargs["apikey"]
+        except KeyError:
+            kargs["user"]=getuser(args[1])
+            del args[1]
+        ret=f(*args,**kargs)
+        logging.debug("Exited %s"%f.__name__)
+        return ret
+    new_f.__name__ = f.__name__
+    return new_f
+
+
+class InterfaceException(Exception):
+    def __init__(self, code=999, msg="Unbekannter Fehler."):
+        self.code=code
+        self.msg=msg
+
+    def dict(self):
+        return {"code":self.code,
+                "msg":self.msg,
+                }
+    def __str__(self):
+        return "%i:%s"%(self.code,self.msg)
+
+class UserNotFound(InterfaceException):
+    def __init__(self):
+        InterfaceException.__init__(self, 901, "Der API-Key ist ungültig.")
+
+class ExternalException(InterfaceException):
+    def __init__(self):
+        InterfaceException.__init__(self, 950, "Fehler in externer API.")
+
+
+class Interface(object):
+    '''class for a xmlrpc user
+    '''
+    
+    @with_user
+    def status(self, user, id=None, detailed=False):
+        '''Gibt den aktuellen Status eines Auftrages oder Mehreren 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
+
+
+        '''
+        #return user.status(id,detailed)
+        return ""
+
+    @with_user
+    def stop(self, user, id):
+        '''Stoppt den angegeben Auftrag.
+
+        Keywords:
+        apikey[string]: Der API Key
+        id[hash]: Eine Auftragsnummer
+
+        Return:
+
+        '''
+        return ""
+    
+    @with_user
+    def sms(self, user, message, recipients, route="default"):
+        '''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
+
+        '''
+        return ""
+   
+    @with_user
+    def fax(self, user, subject, fax, recipients, route="default"):
+        '''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
+
+        '''
+        return ""
+
+    @with_user
+    def mail(self, user, subject,  body, recipients, frm, route="default"):
+        '''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
+
+        '''
+        return ""
+       
+    @with_user
+    def routes(self, user, typ):
+        '''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
+
+        '''
+        return ""
+        
+    @with_user
+    def defaultRoute(self, user, typ):
+        '''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
+
+
+        '''
+        return ""
+
+    @with_user
+    def statistic(self, user):
+        '''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
+        '''
+        return ""
+
+    def listMethods(self):
+        """Since we override lookupProcedure, its suggested to override
+        listProcedures too.
+        """
+        return self.listProcedures()
+  
+
+    def listProcedures(self):
+        """Since we override lookupProcedure, its suggested to override
+        listProcedures too.
+        """
+        return ['listMethods','status','stop','sms','fax','mail','routes','defaultRoute','statistic']
+
+
+class XMLRPCInterface(Interface,xmlrpc.XMLRPC): 
+    def __init__(self):
+        xmlrpc.XMLRPC.__init__(self) 
+        Interface.__init__(self)
+    
+    def lookupProcedure(self, procedurePath):
+        logging.debug("lookupProcedure('%s')"%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)
+
+class SOAPInterface(Interface,soap.SOAPPublisher): 
+    def __init__(self):
+        soap.SOAPPublisher.__init__(self) 
+        Interface.__init__(self)
+        
+    def lookupFunction(self, functionName):
+        """Lookup published SOAP function.
+
+        Override in subclasses. Default behaviour - publish methods
+        starting with soap_, if they have true attribute useKeywords
+        they are expected to accept keywords.
+        
+        @return: tuple (callable, useKeywords), or (None, None) if not found.
+        """
+        if functionName in self.listProcedures():
+            function = getattr(self, functionName, None)
+            if function:
+                return function, getattr(function, "useKeywords", False)
+            return None
+        else:
+            return None
+
+
+def main():
+    from twisted.internet import reactor
+    root = resource.Resource()
+    root.putChild('RPC2', XMLRPCInterface())
+    root.putChild('SOAP', SOAPInterface())
+    reactor.listenTCP(7080, server.Site(root))
+    reactor.run()
+
+if __name__ == '__main__':
+    main()
--- a/iro/newuser.py	Wed Dec 21 21:48:27 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/schema.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*- 
+
+from sqlalchemy import Column, Integer, String, Sequence, Boolean, DateTime, Numeric, Enum
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+
+#relationship
+from sqlalchemy import ForeignKey
+from sqlalchemy.orm import relationship, backref
+
+engine = create_engine('sqlite:///:memory:', echo=True)
+Base = declarative_base()
+
+__tables__=["User", "Job", "Message", "Offer", "Userright"]
+
+class Userright(Base):
+    """Über welche Routen darf ein Benutzer Daten verschicken und welches sind die Standardrouten (<em>isDefault=1</em>) für den Benuter."""
+    __tablename__ = 'userright'
+    user_name = Column('user', String, ForeignKey('apiuser.name'), primary_key=True)
+    offer_name = Column('offer', String, ForeignKey('offer.name'), primary_key=True)
+    isDefault = Column(Boolean)
+    offer = relationship("Offer")
+
+class Offer(Base):
+    """Alle Routen über die SMS, Faxe und Mails verschickt werden könnnen. <em>provider</em>, <em>typ</em> und <em>route</em> werden verwendet, um die entsprechenden Zugangsdaten laden zu können."""
+    __tablename__ = "offer"
+    name = Column(String, primary_key=True)
+    provider = Column(String)
+    route = Column(String)
+    typ = Column(String)
+    
+
+class Message(Base):
+    """Wenn ein Vorgang von Iro Kosten erzeugt hat wird eine neue Zeile eingefügt. Solange nicht bezahlt wurde ist <em>isBilled=0</em>."""
+    __tablename__ = "message"
+    id = Column(Integer, Sequence('message_id_seq'), primary_key=True)
+    recipient = Column(String)
+    isBilled = Column(Boolean)
+    date = Column(DateTime)
+    price = Column(Numeric(8,2))
+    job_hash = Column("job",Integer, ForeignKey('job.hash'))
+    job = relationship("Job", backref=backref('messages'))
+    offer_id = Column("offer",String, ForeignKey('offer.name'))
+    offer = relationship("Offer", backref=backref('messages'))
+
+
+class Job(Base):
+    """Ein kompletter Auftrag, der an Iro zum verschicken übergeben wird. Status zeigt den generellen Status des Auftrages an (<em>init</em>, <em>started</em>, <em>sending</em>, <em>sended</em> oder <em>error</em>). <em>info</em> wird verwendet um dem Benutzer eine Möglickeit zu geben verschiede Auftragsgruppen zu erstellen."""
+    __tablename__ = "job"
+    hash = Column(String, primary_key=True)
+    info = Column(String)
+    status = Column(Enum("init","started","sending","sended","error"))
+    user_id = Column("user", String, ForeignKey('apiuser.name'))
+    user = relationship("User", backref=backref('jobs'))
+
+class User(Base):
+    """Die Benutzerdatenbank von Iro. <em>ng_kunde</em> ist der verknüpfte netzguerilla.net Benutzer, der die Rechnung zahlt."""
+    __tablename__ = "apiuser"
+    name = Column(String, primary_key=True)
+    ng_kunde = Column(Integer)
+    apikey = Column(String,unique=True)
+    rights = relationship('Userright')
+    def __init__(self, name, apikey):
+        self.name=name
+        self.apikey=apikey
+
+    def __repr__(self):
+        return "<User('%s','%s')>"%(self.name,self.apikey)
+
+
+#Base.metadata.create_all(engine)
--- a/iro/tests/stopableServer.py	Wed Dec 21 21:48:27 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
-
Binary file iro/tests/test.pdf has changed
--- a/iro/tests/testJob.py	Wed Dec 21 21:48:27 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 21:48:27 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 21:48:27 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 21:48:27 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
-
--- a/iro/user.py	Wed Dec 21 21:48:27 2011 +0100
+++ b/iro/user.py	Wed Dec 21 21:51:47 2011 +0100
@@ -28,8 +28,7 @@
 
 
 class User: 
-    '''
-    class for a xmlrpc user
+    '''class for a xmlrpc user
     '''
     def __init__(self, name, jobqueue):
         self.jobqueue=jobqueue
@@ -38,7 +37,7 @@
         self.features=["mail", "sms", "fax", ]
 
     def status(self,id=None,detailed=False):
-        u'''Gibt den aktuellen Status eines Auftrages zurück.
+        '''Gibt den aktuellen Status eines Auftrages zurück.
 
         Keywords:
         id[hash]: Eine Auftragsnummer
@@ -75,7 +74,7 @@
             return {}
 
     def stop(self,id):
-        u'''Stoppt den angegeben Auftrag.
+        '''Stoppt den angegeben Auftrag.
 
         Keywords:
         id[hash]: Eine Auftragsnummer
@@ -92,7 +91,7 @@
 
     
     def startSMS(self, message, recipients, provider="default"):
-        u'''Versendet eine SMS.
+        '''Versendet eine SMS.
 
         Keywords:
         message[string]: Nachricht
@@ -111,7 +110,7 @@
     
     
     def startFAX(self, subject, fax, recipients, provider="default"):
-        u'''Versendet ein FAX.
+        '''Versendet ein FAX.
 
         Keywords:
         subject[string]: der Betreff
@@ -136,7 +135,7 @@
         return id
 
     def startMail(self, subject,  body, recipients, frm, provider="default"):
-        u'''Versendet eine Email.
+        '''Versendet eine Email.
 
         Keywords:
         subject[string]: der Betreff
@@ -157,7 +156,7 @@
         return id    
        
     def getProvider(self, typ):
-        u'''Gibt eine Liste aller verfügbaren Provider zurück.
+        '''Gibt eine Liste aller verfügbaren Provider zurück.
 
         Keywords:
         typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
@@ -173,7 +172,7 @@
         return self.jobqueue.providerlist.getProviderlist(typ)
         
     def getDefaultProvider(self, typ):
-        u'''Gibt den Standardprovider zurück.
+        '''Gibt den Standardprovider zurück.
  
         Keywords:
         typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
--- a/setup.py	Wed Dec 21 21:48:27 2011 +0100
+++ b/setup.py	Wed Dec 21 21:51:47 2011 +0100
@@ -1,2 +1,16 @@
+# -*- coding: utf-8 -*-
+
 from setuptools import setup
-setup(name="iro",version='0.1',install_requires=["xmlrpclib","ConfigParser","base64","multiprocessing"])
+from iro import __version__
+
+setup(name='iro',
+        version=__version__,
+        packages=['iro'],
+        setup_requires = ['nose>=0.11'],
+        install_requires=['twisted>=11.1.0',"xmlrpclib","ConfigParser","base64","multiprocessing"],
+        test_suite="nose.collector",
+        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/admin/hg/iro',
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sqlalchemy_schemadisplay3.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,173 @@
+# 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/tests/dump_test_log.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,42 @@
+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/tests/stopableServer.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,118 @@
+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
+
Binary file tests/test.pdf has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testJob.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,82 @@
+# -*- 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()  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testWorker.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,147 @@
+# -*- 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()  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testXMLRPCServer.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,71 @@
+# -*- 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()  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testloglock.py	Wed Dec 21 21:51:47 2011 +0100
@@ -0,0 +1,98 @@
+
+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
+