2 # -*- coding: utf-8 -*-
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
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):
37 Systematiki low-level server protocol.
39 States: (->: sleeping; *: action)
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 |
55 * dataReceived() with beginning of request |
57 \|/ [W] Wait for the client to finish request |
59 * reply = self.factory.cb_request(request) |
61 * self.transport.write("%u\r\n%s\r\n" % (len(reply), |
65 class BadClientVersion(Exception): pass
66 class BadRequest(Exception): pass
69 super(SkServerProtocol
, self
).__init
__()
70 self
.__client
_version
= "UNKNOWN"
71 self
.__received
_data
= ""
72 self
.__request
_length
= None
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"
84 def dataReceived(self
, data
):
85 print "dataReceived %r" % data
86 self
.__received
_data
+= data
89 while result
and self
.__received
_data
:
90 result
= getattr(self
, "dataReceived_" + self
.__state
)()
92 self
.transport
.loseConnection()
95 def dataReceived_V(self
):
96 lines
= self
.__received
_data
.split("\r\n")
98 return False # not there yet
99 self
.__received
_data
= ""
100 BadVersion
= self
.BadClientVersion
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
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]
119 def dataReceived_W(self
):
120 rlen
= self
.__request
_length
121 rd
= self
.__received
_data
122 if len(rd
) < rlen
+ 2:
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
))
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
):
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
}
157 table
.attach_cell(LeftLabel("Database:"), **xo
)
158 self
.__db
_entry
= table
.attach_cell(Entry())
161 table
.attach_cell(LeftLabel("Port:"), **xo
)
162 self
.__port
_entry
= table
.attach_cell(Entry())
163 self
.__port
_entry
.set_text("2357")
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")
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")
199 option_parser
= optparse
.OptionParser(prog
= "Systematiki Server")
200 option_parser
= SetupWindow
._modify
_option
_parser
(option_parser
)
206 if __name__
== "__main__":
208 sys
.exit(main(sys
.argv
))