Software /
code /
verse
Comparison
plugins/disco.lua @ 266:ad8a918fa6e6
plugins.disco: A little cleanup and a bit of rewrite with a touch of premature optimization.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Fri, 16 Dec 2011 22:00:41 +0100 |
parent | 250:a5ac643a7fd6 |
child | 267:d30897e4f5c2 |
comparison
equal
deleted
inserted
replaced
265:2a2326a8f9e8 | 266:ad8a918fa6e6 |
---|---|
15 local xmlns_disco_info = xmlns_disco.."#info"; | 15 local xmlns_disco_info = xmlns_disco.."#info"; |
16 local xmlns_disco_items = xmlns_disco.."#items"; | 16 local xmlns_disco_items = xmlns_disco.."#items"; |
17 | 17 |
18 function verse.plugins.disco(stream) | 18 function verse.plugins.disco(stream) |
19 stream:add_plugin("presence"); | 19 stream:add_plugin("presence"); |
20 stream.disco = { cache = {}, info = {} } | 20 local disco_info_mt = { |
21 stream.disco.info.identities = { | 21 __index = function(t, k) |
22 {category = 'client', type='pc', name='Verse'}, | 22 local node = { identities = {}, features = {} }; |
23 } | 23 if k == "identities" or k == "features" then |
24 stream.disco.info.features = { | 24 return t[false][k] |
25 {var = xmlns_caps}, | 25 end |
26 {var = xmlns_disco_info}, | 26 t[k] = node; |
27 {var = xmlns_disco_items}, | 27 return node; |
28 } | 28 end, |
29 stream.disco.items = {} | 29 }; |
30 stream.disco.nodes = {} | 30 local disco_items_mt = { |
31 __index = function(t, k) | |
32 local node = { }; | |
33 t[k] = node; | |
34 return node; | |
35 end, | |
36 }; | |
37 stream.disco = { | |
38 cache = {}, | |
39 info = setmetatable({ | |
40 [false] = { | |
41 identities = { | |
42 {category = 'client', type='pc', name='Verse'}, | |
43 }, | |
44 features = { | |
45 [xmlns_caps] = true, | |
46 [xmlns_disco_info] = true, | |
47 [xmlns_disco_items] = true, | |
48 }, | |
49 }, | |
50 }, disco_info_mt); | |
51 items = setmetatable({[false]={}}, disco_items_mt); | |
52 }; | |
31 | 53 |
32 stream.caps = {} | 54 stream.caps = {} |
33 stream.caps.node = 'http://code.matthewwild.co.uk/verse/' | 55 stream.caps.node = 'http://code.matthewwild.co.uk/verse/' |
34 | 56 |
35 local function cmp_identity(item1, item2) | 57 local function cmp_identity(item1, item2) |
52 | 74 |
53 local function cmp_feature(item1, item2) | 75 local function cmp_feature(item1, item2) |
54 return item1.var < item2.var | 76 return item1.var < item2.var |
55 end | 77 end |
56 | 78 |
57 local function calculate_hash() | 79 local function calculate_hash(node) |
58 table.sort(stream.disco.info.identities, cmp_identity) | 80 local identities = stream.disco.info[node or false].identities; |
59 table.sort(stream.disco.info.features, cmp_feature) | 81 table.sort(identities, cmp_identity) |
60 local S = '' | 82 local features = {}; |
61 for key,identity in pairs(stream.disco.info.identities) do | 83 for var in pairs(stream.disco.info[node or false].features) do |
62 S = S .. string.format( | 84 features[#features+1] = { var = var }; |
63 '%s/%s/%s/%s', identity.category, identity.type, | 85 end |
86 table.sort(features, cmp_feature) | |
87 local S = {}; | |
88 for key,identity in pairs(identities) do | |
89 S[#S+1] = table.concat({ | |
90 identity.category, identity.type or '', | |
64 identity['xml:lang'] or '', identity.name or '' | 91 identity['xml:lang'] or '', identity.name or '' |
65 ) .. '<' | 92 }, '/'); |
66 end | 93 end |
67 for key,feature in pairs(stream.disco.info.features) do | 94 for key,feature in pairs(features) do |
68 S = S .. feature.var .. '<' | 95 S[#S+1] = feature.var |
69 end | 96 end |
97 S[#S+1] = ''; | |
98 S = table.concat(S,'<'); | |
70 -- FIXME: make sure S is utf8-encoded | 99 -- FIXME: make sure S is utf8-encoded |
71 --stream:debug("Computed hash string: "..S); | 100 --stream:debug("Computed hash string: "..S); |
72 --stream:debug("Computed hash string (sha1): "..sha1(S, true)); | 101 --stream:debug("Computed hash string (sha1): "..sha1(S, true)); |
73 --stream:debug("Computed hash string (sha1+b64): "..b64(sha1(S))); | 102 --stream:debug("Computed hash string (sha1+b64): "..b64(sha1(S))); |
74 return (b64(sha1(S))) | 103 return (b64(sha1(S))) |
77 setmetatable(stream.caps, { | 106 setmetatable(stream.caps, { |
78 __call = function (...) -- vararg: allow calling as function or member | 107 __call = function (...) -- vararg: allow calling as function or member |
79 -- retrieve the c stanza to insert into the | 108 -- retrieve the c stanza to insert into the |
80 -- presence stanza | 109 -- presence stanza |
81 local hash = calculate_hash() | 110 local hash = calculate_hash() |
111 stream.caps.hash = hash; | |
112 -- TODO proper caching.... some day | |
82 return verse.stanza('c', { | 113 return verse.stanza('c', { |
83 xmlns = xmlns_caps, | 114 xmlns = xmlns_caps, |
84 hash = 'sha-1', | 115 hash = 'sha-1', |
85 node = stream.caps.node, | 116 node = stream.caps.node, |
86 ver = hash | 117 ver = hash |
87 }) | 118 }) |
88 end | 119 end |
89 }) | 120 }) |
90 | 121 |
91 function stream:add_disco_feature(feature) | 122 function stream:add_disco_feature(feature, node) |
92 table.insert(self.disco.info.features, {var=feature}); | 123 local feature = feature.var or feature; |
124 self.disco.info[node or false].features[feature] = true; | |
93 stream:resend_presence(); | 125 stream:resend_presence(); |
94 end | 126 end |
95 | 127 |
96 function stream:remove_disco_feature(feature) | 128 function stream:remove_disco_feature(feature, node) |
97 for idx, disco_feature in ipairs(self.disco.info.features) do | 129 local feature = feature.var or feature; |
98 if disco_feature.var == feature then | 130 self.disco.info[node or false].features[feature] = nil; |
99 table.remove(self.disco.info.features, idx); | 131 stream:resend_presence(); |
100 stream:resend_presence(); | |
101 return true; | |
102 end | |
103 end | |
104 end | 132 end |
105 | 133 |
106 function stream:add_disco_item(item, node) | 134 function stream:add_disco_item(item, node) |
107 local disco_items = self.disco.items; | 135 local items = self.disco.items[node or false]; |
108 if node then | 136 items[#items +1] = item; |
109 disco_items = self.disco.nodes[node]; | 137 end |
110 if not disco_items then | 138 |
111 disco_items = { features = {}, items = {} }; | 139 function stream:remove_disco_item(item, node) |
112 self.disco.nodes[node] = disco_items; | 140 local items = self.disco.items[node or false]; |
113 disco_items = disco_items.items; | 141 for i=#items,1,-1 do |
114 else | 142 if items[i] == item then |
115 disco_items = disco_items.items; | 143 table.remove(items, i); |
116 end | 144 end |
117 end | 145 end |
118 table.insert(disco_items, item); | 146 end |
119 end | 147 |
120 | 148 -- TODO Node? |
121 function stream:jid_has_identity(jid, category, type) | 149 function stream:jid_has_identity(jid, category, type) |
122 local cached_disco = self.disco.cache[jid]; | 150 local cached_disco = self.disco.cache[jid]; |
123 if not cached_disco then | 151 if not cached_disco then |
124 return nil, "no-cache"; | 152 return nil, "no-cache"; |
125 end | 153 end |
253 return callback(disco_items); | 281 return callback(disco_items); |
254 end); | 282 end); |
255 end | 283 end |
256 | 284 |
257 stream:hook("iq/"..xmlns_disco_info, function (stanza) | 285 stream:hook("iq/"..xmlns_disco_info, function (stanza) |
258 if stanza.attr.type == 'get' then | 286 local query = stanza.tags[1]; |
259 local query = stanza:child_with_name('query') | 287 if stanza.attr.type == 'get' and query.name == "query" then |
260 if not query then return; end | 288 local query_node = query.attr.node; |
261 -- figure out what identities/features to send | 289 local node = stream.disco.info[query_node or false]; |
262 local identities | 290 if query_node and query_node == stream.caps.node .. "#" .. stream.caps.hash then |
263 local features | 291 node = stream.disco.info[false]; |
264 if query.attr.node then | 292 end |
265 local hash = calculate_hash() | 293 local identities, features = node.identities, node.features |
266 local node = stream.disco.nodes[query.attr.node] | 294 |
267 if node and node.info then | |
268 identities = node.info.identities or {} | |
269 features = node.info.identities or {} | |
270 elseif query.attr.node == stream.caps.node..'#'..hash then | |
271 -- matches caps hash, so use the main info | |
272 identities = stream.disco.info.identities | |
273 features = stream.disco.info.features | |
274 else | |
275 -- unknown node: give an error | |
276 local response = verse.stanza('iq',{ | |
277 to = stanza.attr.from, | |
278 from = stanza.attr.to, | |
279 id = stanza.attr.id, | |
280 type = 'error' | |
281 }) | |
282 response:tag('query',{xmlns = xmlns_disco_info}):reset() | |
283 response:tag('error',{type = 'cancel'}):tag( | |
284 'item-not-found',{xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas'} | |
285 ) | |
286 stream:send(response) | |
287 return true | |
288 end | |
289 else | |
290 identities = stream.disco.info.identities | |
291 features = stream.disco.info.features | |
292 end | |
293 -- construct the response | 295 -- construct the response |
294 local result = verse.stanza('query',{ | 296 local result = verse.reply(stanza):tag("query", { |
295 xmlns = xmlns_disco_info, | 297 xmlns = xmlns_disco_info, |
296 node = query.attr.node | 298 node = query_node, |
297 }) | 299 }); |
298 for key,identity in pairs(identities) do | 300 for _,identity in pairs(identities) do |
299 result:tag('identity', identity):reset() | 301 result:tag('identity', identity):up() |
300 end | 302 end |
301 for key,feature in pairs(features) do | 303 for feature in pairs(features) do |
302 result:tag('feature', feature):reset() | 304 result:tag('feature', { var = feature }):up() |
303 end | 305 end |
304 stream:send(verse.stanza('iq',{ | 306 stream:send(result); |
305 to = stanza.attr.from, | |
306 from = stanza.attr.to, | |
307 id = stanza.attr.id, | |
308 type = 'result' | |
309 }):add_child(result)) | |
310 return true | 307 return true |
311 end | 308 end |
312 end); | 309 end); |
313 | 310 |
314 stream:hook("iq/"..xmlns_disco_items, function (stanza) | 311 stream:hook("iq/"..xmlns_disco_items, function (stanza) |
315 if stanza.attr.type == 'get' then | 312 local query = stanza.tags[1]; |
316 local query = stanza:child_with_name('query') | 313 if stanza.attr.type == 'get' and query.name == "query" then |
317 if not query then return; end | |
318 -- figure out what items to send | 314 -- figure out what items to send |
319 local items | 315 local items = stream.disco.items[query.attr.node or false]; |
320 if query.attr.node then | 316 |
321 local node = stream.disco.nodes[query.attr.node] | |
322 if node then | |
323 items = node.items or {} | |
324 else | |
325 -- unknown node: give an error | |
326 local response = verse.stanza('iq',{ | |
327 to = stanza.attr.from, | |
328 from = stanza.attr.to, | |
329 id = stanza.attr.id, | |
330 type = 'error' | |
331 }) | |
332 response:tag('query',{xmlns = xmlns_disco_items}):reset() | |
333 response:tag('error',{type = 'cancel'}):tag( | |
334 'item-not-found',{xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas'} | |
335 ) | |
336 stream:send(response) | |
337 return true | |
338 end | |
339 else | |
340 items = stream.disco.items | |
341 end | |
342 -- construct the response | 317 -- construct the response |
343 local result = verse.stanza('query',{ | 318 local result = verse.reply(stanza):tag('query',{ |
344 xmlns = xmlns_disco_items, | 319 xmlns = xmlns_disco_items, |
345 node = query.attr.node | 320 node = query.attr.node |
346 }) | 321 }) |
347 for key,item in pairs(items) do | 322 for i=1,#items do |
348 result:tag('item', item):reset() | 323 result:tag('item', items[i]):up() |
349 end | 324 end |
350 stream:send(verse.stanza('iq',{ | 325 stream:send(result); |
351 to = stanza.attr.from, | |
352 from = stanza.attr.to, | |
353 id = stanza.attr.id, | |
354 type = 'result' | |
355 }):add_child(result)) | |
356 return true | 326 return true |
357 end | 327 end |
358 end); | 328 end); |
359 | 329 |
360 local initial_disco_started; | 330 local initial_disco_started; |