vines 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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