vines 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/README +5 -9
  2. data/Rakefile +11 -9
  3. data/conf/config.rb +30 -4
  4. data/lib/vines/cluster/connection.rb +26 -0
  5. data/lib/vines/cluster/publisher.rb +55 -0
  6. data/lib/vines/cluster/pubsub.rb +92 -0
  7. data/lib/vines/cluster/sessions.rb +125 -0
  8. data/lib/vines/cluster/subscriber.rb +108 -0
  9. data/lib/vines/cluster.rb +246 -0
  10. data/lib/vines/command/init.rb +21 -24
  11. data/lib/vines/config/host.rb +48 -8
  12. data/lib/vines/config/port.rb +5 -0
  13. data/lib/vines/config/pubsub.rb +108 -0
  14. data/lib/vines/config.rb +74 -20
  15. data/lib/vines/jid.rb +14 -0
  16. data/lib/vines/router.rb +69 -55
  17. data/lib/vines/stanza/iq/disco_info.rb +22 -9
  18. data/lib/vines/stanza/iq/disco_items.rb +6 -3
  19. data/lib/vines/stanza/iq/ping.rb +1 -1
  20. data/lib/vines/stanza/iq/private_storage.rb +4 -8
  21. data/lib/vines/stanza/iq/roster.rb +6 -14
  22. data/lib/vines/stanza/iq/session.rb +2 -7
  23. data/lib/vines/stanza/iq/vcard.rb +4 -6
  24. data/lib/vines/stanza/iq/version.rb +1 -1
  25. data/lib/vines/stanza/iq.rb +8 -10
  26. data/lib/vines/stanza/presence/subscribe.rb +3 -11
  27. data/lib/vines/stanza/presence/subscribed.rb +16 -29
  28. data/lib/vines/stanza/presence/unsubscribe.rb +3 -15
  29. data/lib/vines/stanza/presence/unsubscribed.rb +3 -16
  30. data/lib/vines/stanza/presence.rb +30 -0
  31. data/lib/vines/stanza/pubsub/create.rb +39 -0
  32. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  33. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  34. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  35. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  36. data/lib/vines/stanza/pubsub.rb +22 -0
  37. data/lib/vines/stanza.rb +72 -22
  38. data/lib/vines/storage/couchdb.rb +46 -65
  39. data/lib/vines/storage/local.rb +20 -14
  40. data/lib/vines/storage/mongodb.rb +132 -0
  41. data/lib/vines/storage/null.rb +39 -0
  42. data/lib/vines/storage/redis.rb +61 -68
  43. data/lib/vines/storage/sql.rb +73 -69
  44. data/lib/vines/storage.rb +1 -1
  45. data/lib/vines/stream/client/bind.rb +2 -2
  46. data/lib/vines/stream/client/session.rb +71 -16
  47. data/lib/vines/stream/component/handshake.rb +1 -0
  48. data/lib/vines/stream/component/ready.rb +2 -2
  49. data/lib/vines/stream/http/session.rb +2 -0
  50. data/lib/vines/stream/http.rb +0 -6
  51. data/lib/vines/stream/server/final_restart.rb +1 -0
  52. data/lib/vines/stream/server/outbound/final_features.rb +1 -0
  53. data/lib/vines/stream/server/ready.rb +6 -2
  54. data/lib/vines/stream/server.rb +4 -3
  55. data/lib/vines/stream.rb +10 -6
  56. data/lib/vines/version.rb +1 -1
  57. data/lib/vines.rb +48 -22
  58. data/test/cluster/publisher_test.rb +45 -0
  59. data/test/cluster/sessions_test.rb +54 -0
  60. data/test/cluster/subscriber_test.rb +94 -0
  61. data/test/config/host_test.rb +100 -21
  62. data/test/config/pubsub_test.rb +181 -0
  63. data/test/config_test.rb +225 -43
  64. data/test/jid_test.rb +7 -0
  65. data/test/router_test.rb +181 -9
  66. data/test/stanza/iq/disco_info_test.rb +8 -6
  67. data/test/stanza/iq/disco_items_test.rb +3 -3
  68. data/test/stanza/iq/private_storage_test.rb +8 -19
  69. data/test/stanza/iq/roster_test.rb +1 -1
  70. data/test/stanza/iq/session_test.rb +3 -6
  71. data/test/stanza/iq/vcard_test.rb +6 -2
  72. data/test/stanza/iq/version_test.rb +3 -2
  73. data/test/stanza/iq_test.rb +5 -5
  74. data/test/stanza/message_test.rb +3 -2
  75. data/test/stanza/presence/probe_test.rb +2 -1
  76. data/test/stanza/pubsub/create_test.rb +138 -0
  77. data/test/stanza/pubsub/delete_test.rb +142 -0
  78. data/test/stanza/pubsub/publish_test.rb +373 -0
  79. data/test/stanza/pubsub/subscribe_test.rb +186 -0
  80. data/test/stanza/pubsub/unsubscribe_test.rb +179 -0
  81. data/test/stanza_test.rb +2 -1
  82. data/test/storage/local_test.rb +26 -25
  83. data/test/storage/mock_mongo.rb +40 -0
  84. data/test/storage/mock_redis.rb +98 -0
  85. data/test/storage/mongodb_test.rb +81 -0
  86. data/test/storage/null_test.rb +30 -0
  87. data/test/storage/redis_test.rb +3 -36
  88. data/test/stream/component/handshake_test.rb +4 -0
  89. data/test/stream/component/ready_test.rb +2 -1
  90. data/test/stream/server/ready_test.rb +7 -1
  91. data/web/404.html +5 -3
  92. data/web/chat/coffeescripts/chat.coffee +9 -5
  93. data/web/chat/javascripts/app.js +1 -1
  94. data/web/chat/javascripts/chat.js +14 -8
  95. data/web/chat/stylesheets/chat.css +4 -1
  96. data/web/lib/coffeescripts/button.coffee +9 -5
  97. data/web/lib/coffeescripts/filter.coffee +1 -1
  98. data/web/lib/coffeescripts/login.coffee +14 -1
  99. data/web/lib/coffeescripts/session.coffee +8 -11
  100. data/web/lib/images/dark-gray.png +0 -0
  101. data/web/lib/images/light-gray.png +0 -0
  102. data/web/lib/images/logo-large.png +0 -0
  103. data/web/lib/images/logo-small.png +0 -0
  104. data/web/lib/images/white.png +0 -0
  105. data/web/lib/javascripts/base.js +9 -8
  106. data/web/lib/javascripts/button.js +20 -12
  107. data/web/lib/javascripts/filter.js +1 -1
  108. data/web/lib/javascripts/icons.js +7 -1
  109. data/web/lib/javascripts/jquery.js +4 -4
  110. data/web/lib/javascripts/login.js +16 -2
  111. data/web/lib/javascripts/raphael.js +5 -7
  112. data/web/lib/javascripts/session.js +10 -14
  113. data/web/lib/stylesheets/base.css +7 -11
  114. data/web/lib/stylesheets/login.css +31 -27
  115. metadata +100 -34
@@ -24,89 +24,68 @@ module Vines
24
24
 
25
25
  def find_user(jid)
26
26
  jid = JID.new(jid).bare.to_s
27
- if jid.empty? then yield; return end
28
- get("user:#{jid}") do |doc|
29
- user = if doc && doc['type'] == 'User'
30
- User.new(:jid => jid).tap do |user|
31
- user.name, user.password = doc.values_at('name', 'password')
32
- (doc['roster'] || {}).each_pair do |jid, props|
33
- user.roster << Contact.new(
34
- :jid => jid,
35
- :name => props['name'],
36
- :subscription => props['subscription'],
37
- :ask => props['ask'],
38
- :groups => props['groups'] || [])
39
- end
40
- end
27
+ return if jid.empty?
28
+ doc = get("user:#{jid}")
29
+ return unless doc && doc['type'] == 'User'
30
+ User.new(jid: jid).tap do |user|
31
+ user.name, user.password = doc.values_at('name', 'password')
32
+ (doc['roster'] || {}).each_pair do |jid, props|
33
+ user.roster << Contact.new(
34
+ jid: jid,
35
+ name: props['name'],
36
+ subscription: props['subscription'],
37
+ ask: props['ask'],
38
+ groups: props['groups'] || [])
41
39
  end
42
- yield user
43
40
  end
44
41
  end
45
- fiber :find_user
46
42
 
47
- def save_user(user, &callback)
43
+ def save_user(user)
48
44
  id = "user:#{user.jid.bare}"
49
- get(id) do |doc|
50
- doc ||= {'_id' => id}
51
- doc['type'] = 'User'
52
- doc['name'] = user.name
53
- doc['password'] = user.password
54
- doc['roster'] = {}
55
- user.roster.each do |contact|
56
- doc['roster'][contact.jid.bare.to_s] = contact.to_h
57
- end
58
- save_doc(doc, &callback)
45
+ doc = get(id) || {'_id' => id}
46
+ doc['type'] = 'User'
47
+ doc['name'] = user.name
48
+ doc['password'] = user.password
49
+ doc['roster'] = {}
50
+ user.roster.each do |contact|
51
+ doc['roster'][contact.jid.bare.to_s] = contact.to_h
59
52
  end
53
+ save_doc(doc)
60
54
  end
61
- fiber :save_user
62
55
 
63
56
  def find_vcard(jid)
64
57
  jid = JID.new(jid).bare.to_s
65
- if jid.empty? then yield; return end
66
- get("vcard:#{jid}") do |doc|
67
- card = if doc && doc['type'] == 'Vcard'
68
- Nokogiri::XML(doc['card']).root rescue nil
69
- end
70
- yield card
71
- end
58
+ return if jid.empty?
59
+ doc = get("vcard:#{jid}")
60
+ return unless doc && doc['type'] == 'Vcard'
61
+ Nokogiri::XML(doc['card']).root rescue nil
72
62
  end
73
- fiber :find_vcard
74
63
 
75
- def save_vcard(jid, card, &callback)
64
+ def save_vcard(jid, card)
76
65
  jid = JID.new(jid).bare.to_s
77
66
  id = "vcard:#{jid}"
78
- get(id) do |doc|
79
- doc ||= {'_id' => id}
80
- doc['type'] = 'Vcard'
81
- doc['card'] = card.to_xml
82
- save_doc(doc, &callback)
83
- end
67
+ doc = get(id) || {'_id' => id}
68
+ doc['type'] = 'Vcard'
69
+ doc['card'] = card.to_xml
70
+ save_doc(doc)
84
71
  end
85
- fiber :save_vcard
86
72
 
87
73
  def find_fragment(jid, node)
88
74
  jid = JID.new(jid).bare.to_s
89
- if jid.empty? then yield; return end
90
- get(fragment_id(jid, node)) do |doc|
91
- fragment = if doc && doc['type'] == 'Fragment'
92
- Nokogiri::XML(doc['xml']).root rescue nil
93
- end
94
- yield fragment
95
- end
75
+ return if jid.empty?
76
+ doc = get(fragment_id(jid, node))
77
+ return unless doc && doc['type'] == 'Fragment'
78
+ Nokogiri::XML(doc['xml']).root rescue nil
96
79
  end
97
- fiber :find_fragment
98
80
 
99
- def save_fragment(jid, node, &callback)
81
+ def save_fragment(jid, node)
100
82
  jid = JID.new(jid).bare.to_s
101
83
  id = fragment_id(jid, node)
102
- get(id) do |doc|
103
- doc ||= {'_id' => id}
104
- doc['type'] = 'Fragment'
105
- doc['xml'] = node.to_xml
106
- save_doc(doc, &callback)
107
- end
84
+ doc = get(id) || {'_id' => id}
85
+ doc['type'] = 'Fragment'
86
+ doc['xml'] = node.to_xml
87
+ save_doc(doc)
108
88
  end
109
- fiber :save_fragment
110
89
 
111
90
  private
112
91
 
@@ -132,13 +111,15 @@ module Vines
132
111
  yield doc
133
112
  end
134
113
  end
114
+ fiber :get
135
115
 
136
- def save_doc(doc, &callback)
137
- http = EM::HttpRequest.new(@url).post(
138
- :head => {'Content-Type' => 'application/json'}, :body => doc.to_json)
139
- http.callback(&callback) if callback
140
- http.errback(&callback) if callback
116
+ def save_doc(doc)
117
+ head = {'Content-Type' => 'application/json'}
118
+ http = EM::HttpRequest.new(@url).post(head: head, body: doc.to_json)
119
+ http.callback { yield }
120
+ http.errback { yield }
141
121
  end
122
+ fiber :save_doc
142
123
 
143
124
  def escape(jid)
144
125
  URI.escape(jid, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
@@ -13,6 +13,11 @@ module Vines
13
13
  unless @dir && File.directory?(@dir) && File.writable?(@dir)
14
14
  raise 'Must provide a writable storage directory'
15
15
  end
16
+
17
+ %w[user vcard fragment].each do |sub|
18
+ sub = File.expand_path(sub, @dir)
19
+ Dir.mkdir(sub, 0700) unless File.exists?(sub)
20
+ end
16
21
  end
17
22
 
18
23
  def dir(dir=nil)
@@ -21,17 +26,17 @@ module Vines
21
26
 
22
27
  def find_user(jid)
23
28
  jid = JID.new(jid).bare.to_s
24
- file = absolute_path("#{jid}.user") unless jid.empty?
29
+ file = absolute_path("user/#{jid}") unless jid.empty?
25
30
  record = YAML.load_file(file) rescue nil
26
- return User.new(:jid => jid).tap do |user|
31
+ return User.new(jid: jid).tap do |user|
27
32
  user.name, user.password = record.values_at('name', 'password')
28
33
  (record['roster'] || {}).each_pair do |jid, props|
29
34
  user.roster << Contact.new(
30
- :jid => jid,
31
- :name => props['name'],
32
- :subscription => props['subscription'],
33
- :ask => props['ask'],
34
- :groups => props['groups'] || [])
35
+ jid: jid,
36
+ name: props['name'],
37
+ subscription: props['subscription'],
38
+ ask: props['ask'],
39
+ groups: props['groups'] || [])
35
40
  end
36
41
  end if record
37
42
  end
@@ -41,7 +46,7 @@ module Vines
41
46
  user.roster.each do |contact|
42
47
  record['roster'][contact.jid.bare.to_s] = contact.to_h
43
48
  end
44
- save("#{user.jid.bare.to_s}.user") do |f|
49
+ save("user/#{user.jid.bare}") do |f|
45
50
  YAML.dump(record, f)
46
51
  end
47
52
  end
@@ -49,14 +54,14 @@ module Vines
49
54
  def find_vcard(jid)
50
55
  jid = JID.new(jid).bare.to_s
51
56
  return if jid.empty?
52
- file = absolute_path("#{jid}.vcard")
57
+ file = absolute_path("vcard/#{jid}")
53
58
  Nokogiri::XML(File.read(file)).root rescue nil
54
59
  end
55
60
 
56
61
  def save_vcard(jid, card)
57
62
  jid = JID.new(jid).bare.to_s
58
63
  return if jid.empty?
59
- save("#{jid}.vcard") do |f|
64
+ save("vcard/#{jid}") do |f|
60
65
  f.write(card.to_xml)
61
66
  end
62
67
  end
@@ -64,14 +69,14 @@ module Vines
64
69
  def find_fragment(jid, node)
65
70
  jid = JID.new(jid).bare.to_s
66
71
  return if jid.empty?
67
- file = absolute_path(fragment_id(jid, node))
72
+ file = absolute_path("fragment/#{fragment_id(jid, node)}")
68
73
  Nokogiri::XML(File.read(file)).root rescue nil
69
74
  end
70
75
 
71
76
  def save_fragment(jid, node)
72
77
  jid = JID.new(jid).bare.to_s
73
78
  return if jid.empty?
74
- save(fragment_id(jid, node)) do |f|
79
+ save("fragment/#{fragment_id(jid, node)}") do |f|
75
80
  f.write(node.to_xml)
76
81
  end
77
82
  end
@@ -80,7 +85,8 @@ module Vines
80
85
 
81
86
  def absolute_path(file)
82
87
  File.expand_path(file, @dir).tap do |absolute|
83
- raise 'path traversal' unless File.dirname(absolute) == @dir
88
+ parent = File.dirname(File.dirname(absolute))
89
+ raise 'path traversal' unless parent == @dir
84
90
  end
85
91
  end
86
92
 
@@ -92,7 +98,7 @@ module Vines
92
98
 
93
99
  def fragment_id(jid, node)
94
100
  id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
95
- "#{jid}-#{id}.fragment"
101
+ "#{jid}-#{id}"
96
102
  end
97
103
  end
98
104
  end
@@ -0,0 +1,132 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Storage
5
+ class MongoDB < Storage
6
+ register :mongodb
7
+
8
+ %w[database tls username password pool].each do |name|
9
+ define_method(name) do |*args|
10
+ if args.first
11
+ @config[name.to_sym] = args.first
12
+ else
13
+ @config[name.to_sym]
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(&block)
19
+ @config, @hosts = {}, []
20
+ instance_eval(&block)
21
+ raise "Must provide database" unless @config[:database]
22
+ raise "Must provide at least one host connection" if @hosts.empty?
23
+ end
24
+
25
+ def host(name, port)
26
+ pair = [name, port]
27
+ raise "duplicate hosts not allowed: #{name}:#{port}" if @hosts.include?(pair)
28
+ @hosts << pair
29
+ end
30
+
31
+ def find_user(jid)
32
+ jid = JID.new(jid).bare.to_s
33
+ return if jid.empty?
34
+ doc = get(:users, jid)
35
+ return unless doc
36
+ User.new(jid: jid).tap do |user|
37
+ user.name, user.password = doc.values_at('name', 'password')
38
+ (doc['roster'] || {}).each_pair do |jid, props|
39
+ user.roster << Contact.new(
40
+ jid: jid,
41
+ name: props['name'],
42
+ subscription: props['subscription'],
43
+ ask: props['ask'],
44
+ groups: props['groups'] || [])
45
+ end
46
+ end
47
+ end
48
+
49
+ def save_user(user)
50
+ id = user.jid.bare.to_s
51
+ doc = get(:users, id) || {'_id' => id}
52
+ doc['name'] = user.name
53
+ doc['password'] = user.password
54
+ doc['roster'] = {}
55
+ user.roster.each do |contact|
56
+ doc['roster'][contact.jid.bare.to_s] = contact.to_h
57
+ end
58
+ save_doc(:users, doc)
59
+ end
60
+
61
+ def find_vcard(jid)
62
+ jid = JID.new(jid).bare.to_s
63
+ return if jid.empty?
64
+ doc = get(:vcards, jid)
65
+ return unless doc
66
+ Nokogiri::XML(doc['card']).root rescue nil
67
+ end
68
+
69
+ def save_vcard(jid, card)
70
+ jid = JID.new(jid).bare.to_s
71
+ doc = get(:vcards, jid) || {'_id' => jid}
72
+ doc['card'] = card.to_xml
73
+ save_doc(:vcards, doc)
74
+ end
75
+
76
+ def find_fragment(jid, node)
77
+ jid = JID.new(jid).bare.to_s
78
+ return if jid.empty?
79
+ doc = get(:fragments, fragment_id(jid, node))
80
+ return unless doc
81
+ Nokogiri::XML(doc['xml']).root rescue nil
82
+ end
83
+
84
+ def save_fragment(jid, node)
85
+ jid = JID.new(jid).bare.to_s
86
+ id = fragment_id(jid, node)
87
+ doc = get(:fragments, id) || {'_id' => id}
88
+ doc['xml'] = node.to_xml
89
+ save_doc(:fragments, doc)
90
+ end
91
+
92
+ private
93
+
94
+ def fragment_id(jid, node)
95
+ id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
96
+ "#{jid}:#{id}"
97
+ end
98
+
99
+ def get(collection, id)
100
+ db.collection(collection).find_one({_id: id})
101
+ end
102
+ defer :get
103
+
104
+ def save_doc(collection, doc)
105
+ db.collection(collection).save(doc, safe: true)
106
+ end
107
+ defer :save_doc
108
+
109
+ def db
110
+ @db ||= connect
111
+ end
112
+
113
+ def connect
114
+ opts = {
115
+ pool_timeout: 5,
116
+ pool_size: @config[:pool] || 5,
117
+ ssl: @config[:tls]
118
+ }
119
+ conn = if @hosts.size == 1
120
+ Mongo::Connection.new(@hosts.first[0], @hosts.first[1], opts)
121
+ else
122
+ Mongo::ReplSetConnection.new(*@hosts, opts)
123
+ end
124
+ conn.db(@config[:database]).tap do |db|
125
+ user = @config[:username] || ''
126
+ pass = @config[:password] || ''
127
+ db.authenticate(user, pass) unless user.empty? || pass.empty?
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Storage
5
+ # A storage implementation that does not persist data to any form of storage.
6
+ # When looking up the storage object for a domain, it's easier to treat a
7
+ # missing domain with a Null storage than checking for nil.
8
+ #
9
+ # For example, presence subscription stanzas sent to a pubsub subdomain
10
+ # have no storage. Rather than checking for nil storage or pubsub addresses,
11
+ # it's easier to treat stanzas to pubsub domains as Null storage that never
12
+ # finds or saves users and their rosters.
13
+ class Null < Storage
14
+ def find_user(jid)
15
+ nil
16
+ end
17
+
18
+ def save_user(user)
19
+ # do nothing
20
+ end
21
+
22
+ def find_vcard(jid)
23
+ nil
24
+ end
25
+
26
+ def save_vcard(jid, card)
27
+ # do nothing
28
+ end
29
+
30
+ def find_fragment(jid, node)
31
+ nil
32
+ end
33
+
34
+ def save_fragment(jid, node)
35
+ # do nothing
36
+ end
37
+ end
38
+ end
39
+ end
@@ -18,83 +18,66 @@ module Vines
18
18
  def initialize(&block)
19
19
  @config = {}
20
20
  instance_eval(&block)
21
- @config[:db] = @config.delete(:database) if @config.key?(:database)
22
21
  end
23
22
 
24
23
  def find_user(jid)
25
24
  jid = JID.new(jid).bare.to_s
26
- if jid.empty? then yield; return end
27
- find_roster(jid) do |contacts|
28
- redis.get("user:#{jid}") do |response|
29
- user = if response
30
- doc = JSON.parse(response) rescue nil
31
- User.new(:jid => jid).tap do |user|
32
- user.name, user.password = doc.values_at('name', 'password')
33
- user.roster = contacts
34
- end if doc
35
- end
36
- yield user
37
- end
25
+ return if jid.empty?
26
+ response = query(:get, "user:#{jid}")
27
+ return unless response
28
+ doc = JSON.parse(response) rescue nil
29
+ return unless doc
30
+ User.new(jid: jid).tap do |user|
31
+ user.name, user.password = doc.values_at('name', 'password')
32
+ user.roster = find_roster(jid)
38
33
  end
39
34
  end
40
- fiber :find_user
41
35
 
42
36
  def save_user(user)
43
- doc = {:name => user.name, :password => user.password}
37
+ doc = {name: user.name, password: user.password}
44
38
  contacts = user.roster.map {|c| [c.jid.to_s, c.to_h.to_json] }.flatten
45
39
  roster = "roster:#{user.jid.bare}"
46
40
 
47
- redis.set("user:#{user.jid.bare}", doc.to_json) do
48
- redis.del(roster) do
49
- contacts.empty? ? yield : redis.hmset(roster, *contacts) { yield }
50
- end
51
- end
41
+ redis.multi
42
+ redis.set("user:#{user.jid.bare}", doc.to_json)
43
+ redis.del(roster)
44
+ redis.hmset(roster, *contacts) unless contacts.empty?
45
+
46
+ req = redis.exec
47
+ req.callback { yield }
48
+ req.errback { yield }
52
49
  end
53
50
  fiber :save_user
54
51
 
55
52
  def find_vcard(jid)
56
53
  jid = JID.new(jid).bare.to_s
57
- if jid.empty? then yield; return end
58
- redis.get("vcard:#{jid}") do |response|
59
- card = if response
60
- doc = JSON.parse(response) rescue nil
61
- Nokogiri::XML(doc['card']).root rescue nil
62
- end
63
- yield card
64
- end
54
+ return if jid.empty?
55
+ response = query(:get, "vcard:#{jid}")
56
+ return unless response
57
+ doc = JSON.parse(response) rescue nil
58
+ Nokogiri::XML(doc['card']).root rescue nil
65
59
  end
66
- fiber :find_vcard
67
60
 
68
61
  def save_vcard(jid, card)
69
62
  jid = JID.new(jid).bare.to_s
70
- doc = {:card => card.to_xml}
71
- redis.set("vcard:#{jid}", doc.to_json) do
72
- yield
73
- end
63
+ doc = {card: card.to_xml}
64
+ query(:set, "vcard:#{jid}", doc.to_json)
74
65
  end
75
- fiber :save_vcard
76
66
 
77
67
  def find_fragment(jid, node)
78
68
  jid = JID.new(jid).bare.to_s
79
- if jid.empty? then yield; return end
80
- redis.hget("fragments:#{jid}", fragment_id(node)) do |response|
81
- fragment = if response
82
- doc = JSON.parse(response) rescue nil
83
- Nokogiri::XML(doc['xml']).root rescue nil
84
- end
85
- yield fragment
86
- end
69
+ return if jid.empty?
70
+ response = query(:hget, "fragments:#{jid}", fragment_id(node))
71
+ return unless response
72
+ doc = JSON.parse(response) rescue nil
73
+ Nokogiri::XML(doc['xml']).root rescue nil
87
74
  end
88
- fiber :find_fragment
89
75
 
90
76
  def save_fragment(jid, node)
91
77
  jid = JID.new(jid).bare.to_s
92
- doc = {:xml => node.to_xml}
93
- redis.hset("fragments:#{jid}", fragment_id(node), doc.to_json) do
94
- yield
95
- end
78
+ doc = {xml: node.to_xml}
79
+ query(:hset, "fragments:#{jid}", fragment_id(node), doc.to_json)
96
80
  end
97
- fiber :save_fragment
98
81
 
99
82
  private
100
83
 
@@ -102,32 +85,42 @@ module Vines
102
85
  Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
103
86
  end
104
87
 
105
- # Retrieve the hash stored at roster:jid and yield an array of
106
- # +Vines::Contact+ objects to the provided block.
107
- #
108
- # find_roster('alice@wonderland.lit') do |contacts|
109
- # puts contacts.size
110
- # end
88
+ # Retrieve the hash stored at roster:jid and return an array of
89
+ # +Vines::Contact+ objects.
111
90
  def find_roster(jid)
112
91
  jid = JID.new(jid).bare
113
- redis.hgetall("roster:#{jid}") do |roster|
114
- contacts = roster.map do |jid, json|
115
- contact = JSON.parse(json) rescue nil
116
- Contact.new(
117
- :jid => jid,
118
- :name => contact['name'],
119
- :subscription => contact['subscription'],
120
- :ask => contact['ask'],
121
- :groups => contact['groups'] || []) if contact
122
- end.compact
123
- yield contacts
124
- end
92
+ roster = query(:hgetall, "roster:#{jid}")
93
+ Hash[*roster].map do |jid, json|
94
+ contact = JSON.parse(json) rescue nil
95
+ Contact.new(
96
+ jid: jid,
97
+ name: contact['name'],
98
+ subscription: contact['subscription'],
99
+ ask: contact['ask'],
100
+ groups: contact['groups'] || []) if contact
101
+ end.compact
125
102
  end
126
103
 
127
- # Cache and return a redis connection object. The em-redis gem reconnects
104
+ def query(name, *args)
105
+ req = redis.send(name, *args)
106
+ req.callback {|response| yield response }
107
+ req.errback { yield }
108
+ end
109
+ fiber :query
110
+
111
+ # Cache and return a redis connection object. The em-hiredis gem reconnects
128
112
  # in unbind so only create one connection.
129
113
  def redis
130
- @redis ||= EM::Protocols::Redis.connect(@config)
114
+ @redis ||= connect
115
+ end
116
+
117
+ # Return a new redis connection using the configuration attributes from the
118
+ # conf/config.rb file.
119
+ def connect
120
+ args = @config.values_at(:host, :port, :password, :database)
121
+ conn = EM::Hiredis::Client.new(*args)
122
+ conn.connect
123
+ conn
131
124
  end
132
125
  end
133
126
  end