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.
- data/README +5 -9
- data/Rakefile +11 -9
- data/conf/config.rb +30 -4
- data/lib/vines/cluster/connection.rb +26 -0
- data/lib/vines/cluster/publisher.rb +55 -0
- data/lib/vines/cluster/pubsub.rb +92 -0
- data/lib/vines/cluster/sessions.rb +125 -0
- data/lib/vines/cluster/subscriber.rb +108 -0
- data/lib/vines/cluster.rb +246 -0
- data/lib/vines/command/init.rb +21 -24
- data/lib/vines/config/host.rb +48 -8
- data/lib/vines/config/port.rb +5 -0
- data/lib/vines/config/pubsub.rb +108 -0
- data/lib/vines/config.rb +74 -20
- data/lib/vines/jid.rb +14 -0
- data/lib/vines/router.rb +69 -55
- data/lib/vines/stanza/iq/disco_info.rb +22 -9
- data/lib/vines/stanza/iq/disco_items.rb +6 -3
- data/lib/vines/stanza/iq/ping.rb +1 -1
- data/lib/vines/stanza/iq/private_storage.rb +4 -8
- data/lib/vines/stanza/iq/roster.rb +6 -14
- data/lib/vines/stanza/iq/session.rb +2 -7
- data/lib/vines/stanza/iq/vcard.rb +4 -6
- data/lib/vines/stanza/iq/version.rb +1 -1
- data/lib/vines/stanza/iq.rb +8 -10
- data/lib/vines/stanza/presence/subscribe.rb +3 -11
- data/lib/vines/stanza/presence/subscribed.rb +16 -29
- data/lib/vines/stanza/presence/unsubscribe.rb +3 -15
- data/lib/vines/stanza/presence/unsubscribed.rb +3 -16
- data/lib/vines/stanza/presence.rb +30 -0
- data/lib/vines/stanza/pubsub/create.rb +39 -0
- data/lib/vines/stanza/pubsub/delete.rb +41 -0
- data/lib/vines/stanza/pubsub/publish.rb +66 -0
- data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
- data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
- data/lib/vines/stanza/pubsub.rb +22 -0
- data/lib/vines/stanza.rb +72 -22
- data/lib/vines/storage/couchdb.rb +46 -65
- data/lib/vines/storage/local.rb +20 -14
- data/lib/vines/storage/mongodb.rb +132 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/redis.rb +61 -68
- data/lib/vines/storage/sql.rb +73 -69
- data/lib/vines/storage.rb +1 -1
- data/lib/vines/stream/client/bind.rb +2 -2
- data/lib/vines/stream/client/session.rb +71 -16
- data/lib/vines/stream/component/handshake.rb +1 -0
- data/lib/vines/stream/component/ready.rb +2 -2
- data/lib/vines/stream/http/session.rb +2 -0
- data/lib/vines/stream/http.rb +0 -6
- data/lib/vines/stream/server/final_restart.rb +1 -0
- data/lib/vines/stream/server/outbound/final_features.rb +1 -0
- data/lib/vines/stream/server/ready.rb +6 -2
- data/lib/vines/stream/server.rb +4 -3
- data/lib/vines/stream.rb +10 -6
- data/lib/vines/version.rb +1 -1
- data/lib/vines.rb +48 -22
- data/test/cluster/publisher_test.rb +45 -0
- data/test/cluster/sessions_test.rb +54 -0
- data/test/cluster/subscriber_test.rb +94 -0
- data/test/config/host_test.rb +100 -21
- data/test/config/pubsub_test.rb +181 -0
- data/test/config_test.rb +225 -43
- data/test/jid_test.rb +7 -0
- data/test/router_test.rb +181 -9
- data/test/stanza/iq/disco_info_test.rb +8 -6
- data/test/stanza/iq/disco_items_test.rb +3 -3
- data/test/stanza/iq/private_storage_test.rb +8 -19
- data/test/stanza/iq/roster_test.rb +1 -1
- data/test/stanza/iq/session_test.rb +3 -6
- data/test/stanza/iq/vcard_test.rb +6 -2
- data/test/stanza/iq/version_test.rb +3 -2
- data/test/stanza/iq_test.rb +5 -5
- data/test/stanza/message_test.rb +3 -2
- data/test/stanza/presence/probe_test.rb +2 -1
- data/test/stanza/pubsub/create_test.rb +138 -0
- data/test/stanza/pubsub/delete_test.rb +142 -0
- data/test/stanza/pubsub/publish_test.rb +373 -0
- data/test/stanza/pubsub/subscribe_test.rb +186 -0
- data/test/stanza/pubsub/unsubscribe_test.rb +179 -0
- data/test/stanza_test.rb +2 -1
- data/test/storage/local_test.rb +26 -25
- data/test/storage/mock_mongo.rb +40 -0
- data/test/storage/mock_redis.rb +98 -0
- data/test/storage/mongodb_test.rb +81 -0
- data/test/storage/null_test.rb +30 -0
- data/test/storage/redis_test.rb +3 -36
- data/test/stream/component/handshake_test.rb +4 -0
- data/test/stream/component/ready_test.rb +2 -1
- data/test/stream/server/ready_test.rb +7 -1
- data/web/404.html +5 -3
- data/web/chat/coffeescripts/chat.coffee +9 -5
- data/web/chat/javascripts/app.js +1 -1
- data/web/chat/javascripts/chat.js +14 -8
- data/web/chat/stylesheets/chat.css +4 -1
- data/web/lib/coffeescripts/button.coffee +9 -5
- data/web/lib/coffeescripts/filter.coffee +1 -1
- data/web/lib/coffeescripts/login.coffee +14 -1
- data/web/lib/coffeescripts/session.coffee +8 -11
- data/web/lib/images/dark-gray.png +0 -0
- data/web/lib/images/light-gray.png +0 -0
- data/web/lib/images/logo-large.png +0 -0
- data/web/lib/images/logo-small.png +0 -0
- data/web/lib/images/white.png +0 -0
- data/web/lib/javascripts/base.js +9 -8
- data/web/lib/javascripts/button.js +20 -12
- data/web/lib/javascripts/filter.js +1 -1
- data/web/lib/javascripts/icons.js +7 -1
- data/web/lib/javascripts/jquery.js +4 -4
- data/web/lib/javascripts/login.js +16 -2
- data/web/lib/javascripts/raphael.js +5 -7
- data/web/lib/javascripts/session.js +10 -14
- data/web/lib/stylesheets/base.css +7 -11
- data/web/lib/stylesheets/login.css +31 -27
- 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?
|
28
|
-
get("user:#{jid}")
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
43
|
+
def save_user(user)
|
48
44
|
id = "user:#{user.jid.bare}"
|
49
|
-
get(id)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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?
|
66
|
-
get("vcard:#{jid}")
|
67
|
-
|
68
|
-
|
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
|
64
|
+
def save_vcard(jid, card)
|
76
65
|
jid = JID.new(jid).bare.to_s
|
77
66
|
id = "vcard:#{jid}"
|
78
|
-
get(id)
|
79
|
-
|
80
|
-
|
81
|
-
|
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?
|
90
|
-
get(fragment_id(jid, node))
|
91
|
-
|
92
|
-
|
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
|
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)
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
137
|
-
|
138
|
-
|
139
|
-
http.callback
|
140
|
-
http.errback
|
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}]"))
|
data/lib/vines/storage/local.rb
CHANGED
@@ -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("
|
29
|
+
file = absolute_path("user/#{jid}") unless jid.empty?
|
25
30
|
record = YAML.load_file(file) rescue nil
|
26
|
-
return User.new(:
|
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
|
-
:
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
34
|
-
:
|
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("
|
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("
|
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("
|
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
|
-
|
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}
|
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
|
data/lib/vines/storage/redis.rb
CHANGED
@@ -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?
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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 = {:
|
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.
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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?
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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 = {:
|
71
|
-
|
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?
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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 = {:
|
93
|
-
|
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
|
106
|
-
# +Vines::Contact+ objects
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
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 ||=
|
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
|