Low-level client/server protocol implemented
[systematiki.git] / systematiki_server.py
blob865a087b3abb30774a2d4d64ba55ec8c581ed9dc
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 Systematiki Server.
5 """
7 # Copyright (C) 2007 Felix Rabe <public@felixrabe.textdriven.com>
9 # This library is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU Lesser General Public
11 # License as published by the Free Software Foundation; either
12 # version 2.1 of the License, or (at your option) any later version.
14 # This library is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 # Lesser General Public License for more details.
19 # You should have received a copy of the GNU Lesser General Public License
20 # along with this library; if not, write to the Free Software Foundation,
21 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 import optparse
25 from PyGTKShell.Config import _config
26 _config["main-loop-integrate-twisted"] = True
27 from PyGTKShell.RawConsole import *
29 from twisted.internet import reactor
30 from twisted.internet.protocol import Protocol, Factory
32 from Systematiki.Database import FSDatabase as SkDatabase
35 class SkServerProtocol(Protocol, object):
36 """
37 Systematiki low-level server protocol.
39 States: (->: sleeping; *: action)
42 \|/ [U] Unconnected
44 * connectionMade()
46 * self.transport.write() header
48 \|/ [V] Wait for the client's version
50 * dataReceived() with client version
52 | [R] Wait for the client to send a request <--------.
53 \|/ or lose the connection |
54 V |
55 * dataReceived() with beginning of request |
56 | |
57 \|/ [W] Wait for the client to finish request |
58 V |
59 * reply = self.factory.cb_request(request) |
60 | |
61 * self.transport.write("%u\r\n%s\r\n" % (len(reply), |
62 reply)) ----'
63 """
65 class BadClientVersion(Exception): pass
66 class BadRequest(Exception): pass
68 def __init__(self):
69 super(SkServerProtocol, self).__init__()
70 self.__client_version = "UNKNOWN"
71 self.__received_data = ""
72 self.__request_length = None
73 self.__state = "U"
75 def connectionMade(self):
76 from setup import info
77 self.transport.write("Systematiki Server\r\n")
78 self.transport.write("Protocol Version: 0\r\n")
79 self.transport.write("Server Version: %s\r\n" % info["version"])
80 self.transport.write("\r\n")
81 print "connection made"
82 self.__state = "V"
84 def dataReceived(self, data):
85 print "dataReceived %r" % data
86 self.__received_data += data
87 try:
88 result = True
89 while result and self.__received_data:
90 result = getattr(self, "dataReceived_" + self.__state)()
91 except:
92 self.transport.loseConnection()
93 raise
95 def dataReceived_V(self):
96 lines = self.__received_data.split("\r\n")
97 if len(lines) < 3:
98 return False # not there yet
99 self.__received_data = ""
100 BadVersion = self.BadClientVersion
101 if len(lines) > 3:
102 raise BadVersion, "too much data"
103 if not (lines[1] == lines[2] == ""):
104 raise BadVersion, "trailing lines not empty"
105 t, v = lines[0].split(": ", 1)
106 if t != "Client Version":
107 raise BadVersion, "client version not recognized"
108 self.__client_version = v
109 self.__state = "R"
110 return True
112 def dataReceived_R(self):
113 parts = self.__received_data.split("\r\n", 1)
114 self.__request_length = int(parts[0])
115 self.__received_data = parts[1]
116 self.__state = "W"
117 return True
119 def dataReceived_W(self):
120 rlen = self.__request_length
121 rd = self.__received_data
122 if len(rd) < rlen + 2:
123 return False
124 request = rd[:rlen]
125 if rd[rlen:rlen+2] != "\r\n":
126 raise self.BadRequest, "request does not end in a newline"
127 self.__received_data = rd[rlen + 2:]
128 reply = self.factory.cb_request(request)
129 self.transport.write("%u\r\n%s\r\n" % (len(reply), reply))
130 self.__state = "R"
131 return True
134 class SkServerFactory(Factory):
136 protocol = SkServerProtocol
138 def __init__(self, database):
139 self.database = database
141 def cb_request(self, request):
142 return request.upper()
145 class SetupWindow(WindowF12RawConsole):
147 def __init__(self):
148 super(SetupWindow, self).__init__()
149 self.set_title("Systematiki Server")
151 outer, inner = gnome_hig(self)
152 table = gnome_hig(inner(Table(), False, False))
154 xo = {"xoptions": gtk.FILL}
156 table.add_rows()
157 table.attach_cell(LeftLabel("Database:"), **xo)
158 self.__db_entry = table.attach_cell(Entry())
160 table.add_rows()
161 table.attach_cell(LeftLabel("Port:"), **xo)
162 self.__port_entry = table.attach_cell(Entry())
163 self.__port_entry.set_text("2357")
165 table.add_rows()
166 self.__run_button = table.attach_row(Button("Listen"))
167 self.__run_button.connect("clicked", self.__cb_run_button_clicked)
168 self.__run_button.set_property("can-default", True)
169 self.__run_button.grab_default()
170 self.__port_entry.grab_focus()
172 def __cb_run_button_clicked(self, button):
173 if button.get_label() == "Listen":
174 port = int(self.__port_entry.get_text())
175 db_root = self.__db_entry.get_text()
176 self.__database = SkDatabase(db_root)
177 factory = SkServerFactory(self.__database)
178 self.__listener = reactor.listenTCP(port, factory)
179 self.__db_entry.set_editable(False)
180 self.__port_entry.set_editable(False)
181 button.set_label("Shut Down")
182 else:
183 self.__listener.stopListening()
184 self.__database = None
185 self.__db_entry.set_editable(True)
186 self.__port_entry.set_editable(True)
187 button.set_label("Listen")
189 @classmethod # for potential inheritance
190 def _modify_option_parser(cls, option_parser):
191 option_parser.add_option("-d", "--database",
192 help = "Systematiki database path")
193 option_parser.add_option("-p", "--port", type = "int",
194 help = "port to listen on")
195 return option_parser
198 def main(argv):
199 option_parser = optparse.OptionParser(prog = "Systematiki Server")
200 option_parser = SetupWindow._modify_option_parser(option_parser)
201 SetupWindow()
202 main_loop_run()
203 return 0
206 if __name__ == "__main__":
207 import sys
208 sys.exit(main(sys.argv))