25
25
# Instead of using a complicated XMPP/AMPQ/JMS/super messaging service,
26
26
# we have simple HTTP GETs and PUTs to get data in and out.
28
# Currently supports both XML and JSON serialization.
28
# Currently supports JSON serialization.
30
30
# Example Sub clients:
31
# curl -sN http://127.0.0.1:2069/commits
32
# curl -sN http://127.0.0.1:2069/commits/svn/*
33
# curl -sN http://127.0.0.1:2069/commits/svn
34
# curl -sN http://127.0.0.1:2069/commits/*/13f79535-47bb-0310-9956-ffa450edef68
35
# curl -sN http://127.0.0.1:2069/commits/svn/13f79535-47bb-0310-9956-ffa450edef68
37
# URL is built into 2 parts:
38
# /commits/${optional_type}/${optional_repository}
40
# If the type is included in the URL, you will only get commits of that type.
41
# The type can be * and then you will receive commits of any type.
31
# curl -sN http://127.0.0.1:2069/commits
32
# curl -sN 'http://127.0.0.1:2069/commits/svn/*'
33
# curl -sN http://127.0.0.1:2069/commits/svn
34
# curl -sN 'http://127.0.0.1:2069/commits/*/13f79535-47bb-0310-9956-ffa450edef68'
35
# curl -sN http://127.0.0.1:2069/commits/svn/13f79535-47bb-0310-9956-ffa450edef68
37
# curl -sN http://127.0.0.1:2069/metadata
38
# curl -sN 'http://127.0.0.1:2069/metadata/svn/*'
39
# curl -sN http://127.0.0.1:2069/metadata/svn
40
# curl -sN 'http://127.0.0.1:2069/metadata/*/13f79535-47bb-0310-9956-ffa450edef68'
41
# curl -sN http://127.0.0.1:2069/metadata/svn/13f79535-47bb-0310-9956-ffa450edef68
43
# URLs are constructed from 3 parts:
44
# /${notification}/${optional_type}/${optional_repository}
46
# Notifications can be sent for commits or metadata (e.g., revprop) changes.
47
# If the type is included in the URL, you will only get notifications of that type.
48
# The type can be * and then you will receive notifications of any type.
43
50
# If the repository is included in the URL, you will only receive
44
51
# messages about that repository. The repository can be * and then you
86
93
def check_value(self, k):
87
94
return hasattr(self, k) and self.__dict__[k]
89
def render_commit(self):
97
raise NotImplementedError
100
raise NotImplementedError
102
class Commit(Notification):
90
106
obj = {'commit': {}}
91
107
obj['commit'].update(self.__dict__)
92
108
return json.dumps(obj)
96
112
paths_changed = " %d paths changed" % len(self.changed)
98
114
paths_changed = ""
99
return "%s:%s repo '%s' id '%s'%s" % (self.type,
115
return "commit %s:%s repo '%s' id '%s'%s" % (
116
self.type, self.format, self.repository, self.id,
119
class Metadata(Notification):
123
obj = {'metadata': {}}
124
obj['metadata'].update(self.__dict__)
125
return json.dumps(obj)
127
def render_log(self):
128
return "metadata %s:%s repo '%s' id '%s' revprop '%s'" % (
129
self.type, self.format, self.repository, self.id,
130
self.revprop['name'])
106
133
HEARTBEAT_TIME = 15
108
135
class Client(object):
109
def __init__(self, pubsub, r, type, repository):
136
def __init__(self, pubsub, r, kind, type, repository):
110
137
self.pubsub = pubsub
111
138
r.notifyFinish().addErrback(self.finished)
114
142
self.repository = repository
115
143
self.alive = True
123
151
except ValueError:
126
def interested_in(self, commit):
127
if self.type and self.type != commit.type:
130
if self.repository and self.repository != commit.repository:
154
def interested_in(self, notification):
155
if self.kind != notification.KIND:
158
if self.type and self.type != notification.type:
161
if self.repository and self.repository != notification.repository:
198
__notification_uri_map = {'commits': Commit.KIND,
199
'metadata': Metadata.KIND}
201
def __init__(self, notification_class):
202
resource.Resource.__init__(self)
203
self.__notification_class = notification_class
168
206
return len(self.clients)
195
238
if repository == '*':
196
239
repository = None
198
c = Client(self, request, type, repository)
241
c = Client(self, request, kind, type, repository)
199
242
self.clients.append(c)
201
244
return twisted.web.server.NOT_DONE_YET
203
def notifyAll(self, commit):
204
data = commit.render_commit()
246
def notifyAll(self, notification):
247
data = notification.render()
206
log.msg("COMMIT: %s (%d clients)" % (commit.render_log(), self.cc()))
249
log.msg("%s: %s (%d clients)"
250
% (notification.KIND, notification.render_log(), self.cc()))
207
251
for client in self.clients:
208
if client.interested_in(commit):
252
if client.interested_in(notification):
209
253
client.write_data(data)
211
255
def render_PUT(self, request):
218
262
#import pdb;pdb.set_trace()
219
263
#print "input: %s" % (input)
221
c = json.loads(input)
265
data = json.loads(input)
266
notification = self.__notification_class(data)
223
267
except ValueError as e:
224
268
request.setResponseCode(400)
225
log.msg("COMMIT: failed due to: %s" % str(e))
227
self.notifyAll(commit)
270
log.msg("%s: failed due to: %s" % (notification.KIND, errstr))
272
self.notifyAll(notification)
230
276
def svnpubsub_server():
231
277
root = resource.Resource()
233
root.putChild("commits", s)
278
c = SvnPubSub(Commit)
279
m = SvnPubSub(Metadata)
280
root.putChild('commits', c)
281
root.putChild('metadata', m)
234
282
return server.Site(root)
236
284
if __name__ == "__main__":