annotate sat_pubsub/tap_http.py @ 331:e93a9fd329d9

affiliations XMPP handling: /!\ PostGreSQL minimal version raised to 9.5 (for upsert support) - affiliations can now be handler after node creation using XMPP: affiliationsGet, affiliationsSet (both for node owner) and affiliations (for an entity to know with which nodes it is affiliated on the service) are implemented - pgsql: getOrCreateEntities helping method has been implemented, it get entity_id and create missing entities when needed.
author Goffi <goffi@goffi.org>
date Sun, 26 Mar 2017 20:58:48 +0200
parents 5d7c3787672e
children 9fbb31ce495b
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
1 #!/usr/bin/python
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
2 #-*- coding: utf-8 -*-
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
3
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
4 # Copyright (c) 2003-2011 Ralph Meijer
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
5 # Copyright (c) 2012-2016 Jérôme Poisson
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
6
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
7
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
8 # This program is free software: you can redistribute it and/or modify
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
9 # it under the terms of the GNU Affero General Public License as published by
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
10 # the Free Software Foundation, either version 3 of the License, or
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
11 # (at your option) any later version.
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
12
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
13 # This program is distributed in the hope that it will be useful,
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
16 # GNU Affero General Public License for more details.
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
17
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
18 # You should have received a copy of the GNU Affero General Public License
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
20 # --
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
21
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
22 # This program is based on Idavoll (http://idavoll.ik.nu/),
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
23 # originaly written by Ralph Meijer (http://ralphm.net/blog/)
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
24 # It is sublicensed under AGPL v3 (or any later version) as allowed by the original
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
25 # license.
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
26
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
27 # --
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
28
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
29 # Here is a copy of the original license:
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
30
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
31 # Copyright (c) 2003-2011 Ralph Meijer
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
32
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
33 # Permission is hereby granted, free of charge, to any person obtaining
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
34 # a copy of this software and associated documentation files (the
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
35 # "Software"), to deal in the Software without restriction, including
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
36 # without limitation the rights to use, copy, modify, merge, publish,
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
37 # distribute, sublicense, and/or sell copies of the Software, and to
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
38 # permit persons to whom the Software is furnished to do so, subject to
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
39 # the following conditions:
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
40
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
41 # The above copyright notice and this permission notice shall be
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
42 # included in all copies or substantial portions of the Software.
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
43
312
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
44 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
45 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
46 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
47 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
48 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
49 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
5d7c3787672e fixed copyright put in docstring instead of comments
Goffi <goffi@goffi.org>
parents: 311
diff changeset
50 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
233
564ae55219e1 sublicensed under AGPL V3
Goffi <goffi@goffi.org>
parents: 232
diff changeset
51
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
52
273
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
53 from twisted.application import internet, strports
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
54 from twisted.conch import manhole, manhole_ssh
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
55 from twisted.cred import portal, checkers
273
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
56 from twisted.web import resource, server
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
57
234
51af2ed8bd50 replaced idavoll imports by sat_pubsub imports
Goffi <goffi@goffi.org>
parents: 233
diff changeset
58 from sat_pubsub import gateway, tap
51af2ed8bd50 replaced idavoll imports by sat_pubsub imports
Goffi <goffi@goffi.org>
parents: 233
diff changeset
59 from sat_pubsub.gateway import RemoteSubscriptionService
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
60
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
61 class Options(tap.Options):
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
62 optParameters = [
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
63 ('webport', None, '8086', 'Web port'),
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
64 ]
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
65
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
66
206
274a45d2a5ab Implement root collection that includes all leaf nodes.
Ralph Meijer <ralphm@ik.nu>
parents: 204
diff changeset
67
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
68 def getManholeFactory(namespace, **passwords):
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
69 def getManHole(_):
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
70 return manhole.Manhole(namespace)
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
71
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
72 realm = manhole_ssh.TerminalRealm()
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
73 realm.chainedProtocolFactory.protocolFactory = getManHole
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
74 p = portal.Portal(realm)
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
75 p.registerChecker(
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
76 checkers.InMemoryUsernamePasswordDatabaseDontUse(**passwords))
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
77 f = manhole_ssh.ConchFactory(p)
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
78 return f
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
79
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
80
206
274a45d2a5ab Implement root collection that includes all leaf nodes.
Ralph Meijer <ralphm@ik.nu>
parents: 204
diff changeset
81
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
82 def makeService(config):
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
83 s = tap.makeService(config)
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
84
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
85 bs = s.getServiceNamed('backend')
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
86 cs = s.getServiceNamed('component')
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
87
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
88 # Set up XMPP service for subscribing to remote nodes
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
89
204
b4bf0a5ce50d Implement storage facilities for the HTTP gateway.
Ralph Meijer <ralphm@ik.nu>
parents: 185
diff changeset
90 if config['backend'] == 'pgsql':
234
51af2ed8bd50 replaced idavoll imports by sat_pubsub imports
Goffi <goffi@goffi.org>
parents: 233
diff changeset
91 from sat_pubsub.pgsql_storage import GatewayStorage
204
b4bf0a5ce50d Implement storage facilities for the HTTP gateway.
Ralph Meijer <ralphm@ik.nu>
parents: 185
diff changeset
92 gst = GatewayStorage(bs.storage.dbpool)
b4bf0a5ce50d Implement storage facilities for the HTTP gateway.
Ralph Meijer <ralphm@ik.nu>
parents: 185
diff changeset
93 elif config['backend'] == 'memory':
234
51af2ed8bd50 replaced idavoll imports by sat_pubsub imports
Goffi <goffi@goffi.org>
parents: 233
diff changeset
94 from sat_pubsub.memory_storage import GatewayStorage
204
b4bf0a5ce50d Implement storage facilities for the HTTP gateway.
Ralph Meijer <ralphm@ik.nu>
parents: 185
diff changeset
95 gst = GatewayStorage()
b4bf0a5ce50d Implement storage facilities for the HTTP gateway.
Ralph Meijer <ralphm@ik.nu>
parents: 185
diff changeset
96
b4bf0a5ce50d Implement storage facilities for the HTTP gateway.
Ralph Meijer <ralphm@ik.nu>
parents: 185
diff changeset
97 ss = RemoteSubscriptionService(config['jid'], gst)
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
98 ss.setHandlerParent(cs)
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
99 ss.startService()
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
100
185
9038908dc2f5 Add gateway support for retrieving items from a node. Reorder gateway module.
Ralph Meijer <ralphm@ik.nu>
parents: 183
diff changeset
101 # Set up web service
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
102
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
103 root = resource.Resource()
185
9038908dc2f5 Add gateway support for retrieving items from a node. Reorder gateway module.
Ralph Meijer <ralphm@ik.nu>
parents: 183
diff changeset
104
9038908dc2f5 Add gateway support for retrieving items from a node. Reorder gateway module.
Ralph Meijer <ralphm@ik.nu>
parents: 183
diff changeset
105 # Set up resources that exposes the backend
273
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
106 root.putChild('create', gateway.CreateResource(bs, config['jid'],
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
107 config['jid']))
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
108 root.putChild('delete', gateway.DeleteResource(bs, config['jid'],
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
109 config['jid']))
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
110 root.putChild('publish', gateway.PublishResource(bs, config['jid'],
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
111 config['jid']))
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
112 root.putChild('list', gateway.ListResource(bs))
185
9038908dc2f5 Add gateway support for retrieving items from a node. Reorder gateway module.
Ralph Meijer <ralphm@ik.nu>
parents: 183
diff changeset
113
9038908dc2f5 Add gateway support for retrieving items from a node. Reorder gateway module.
Ralph Meijer <ralphm@ik.nu>
parents: 183
diff changeset
114 # Set up resources for accessing remote pubsub nodes.
273
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
115 root.putChild('subscribe', gateway.RemoteSubscribeResource(ss))
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
116 root.putChild('unsubscribe', gateway.RemoteUnsubscribeResource(ss))
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
117 root.putChild('items', gateway.RemoteItemsResource(ss))
213
a321f9300054 Actually log requests if verbose.
Ralph Meijer <ralphm@ik.nu>
parents: 206
diff changeset
118
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
119 site = server.Site(root)
273
6ba0d6def7f5 Use twisted.web instead of web2, initial tests.
Ralph Meijer <ralphm@ik.nu>
parents: 255
diff changeset
120 w = internet.TCPServer(int(config['webport']), site)
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
121 w.setServiceParent(s)
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
122
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
123 # Set up a manhole
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
124
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
125 namespace = {'service': s,
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
126 'component': cs,
206
274a45d2a5ab Implement root collection that includes all leaf nodes.
Ralph Meijer <ralphm@ik.nu>
parents: 204
diff changeset
127 'backend': bs,
274a45d2a5ab Implement root collection that includes all leaf nodes.
Ralph Meijer <ralphm@ik.nu>
parents: 204
diff changeset
128 'root': root}
274a45d2a5ab Implement root collection that includes all leaf nodes.
Ralph Meijer <ralphm@ik.nu>
parents: 204
diff changeset
129
177
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
130 f = getManholeFactory(namespace, admin='admin')
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
131 manholeService = strports.service('2222', f)
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
132 manholeService.setServiceParent(s)
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
133
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
134 return s
faf1c9bc2612 Add HTTP gateway in a separate plugin.
Ralph Meijer <ralphm@ik.nu>
parents:
diff changeset
135