vines 0.4.5 → 0.4.6
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/Gemfile +3 -0
- data/README.md +48 -0
- data/Rakefile +6 -58
- data/bin/vines +12 -2
- data/conf/certs/ca-bundle.crt +568 -39
- data/conf/config.rb +9 -42
- data/lib/vines.rb +1 -8
- data/lib/vines/command/cert.rb +4 -4
- data/lib/vines/command/init.rb +3 -3
- data/lib/vines/config.rb +9 -0
- data/lib/vines/kit.rb +2 -7
- data/lib/vines/storage/local.rb +50 -17
- data/lib/vines/store.rb +6 -4
- data/lib/vines/stream.rb +1 -1
- data/lib/vines/stream/http/session.rb +0 -0
- data/lib/vines/stream/http/sessions.rb +0 -0
- data/lib/vines/stream/parser.rb +3 -2
- data/lib/vines/token_bucket.rb +19 -10
- data/lib/vines/version.rb +1 -1
- data/test/cluster/publisher_test.rb +45 -33
- data/test/cluster/sessions_test.rb +32 -39
- data/test/cluster/subscriber_test.rb +93 -78
- data/test/config/host_test.rb +2 -4
- data/test/config/pubsub_test.rb +132 -126
- data/test/config_test.rb +2 -4
- data/test/contact_test.rb +80 -66
- data/test/error_test.rb +54 -55
- data/test/jid_test.rb +1 -2
- data/test/kit_test.rb +22 -17
- data/test/router_test.rb +187 -146
- data/test/stanza/iq/disco_info_test.rb +59 -59
- data/test/stanza/iq/disco_items_test.rb +36 -34
- data/test/stanza/iq/private_storage_test.rb +138 -143
- data/test/stanza/iq/roster_test.rb +198 -175
- data/test/stanza/iq/session_test.rb +17 -18
- data/test/stanza/iq/vcard_test.rb +117 -116
- data/test/stanza/iq/version_test.rb +47 -46
- data/test/stanza/iq_test.rb +53 -49
- data/test/stanza/message_test.rb +92 -89
- data/test/stanza/presence/probe_test.rb +2 -5
- data/test/stanza/presence/subscribe_test.rb +67 -54
- data/test/stanza/pubsub/create_test.rb +86 -108
- data/test/stanza/pubsub/delete_test.rb +141 -114
- data/test/stanza/pubsub/publish_test.rb +256 -320
- data/test/stanza/pubsub/subscribe_test.rb +169 -150
- data/test/stanza/pubsub/unsubscribe_test.rb +111 -142
- data/test/stanza_test.rb +61 -54
- data/test/storage/ldap_test.rb +1 -2
- data/test/storage/local_test.rb +3 -5
- data/test/storage/null_test.rb +3 -4
- data/test/storage/storage_tests.rb +1 -3
- data/test/storage_test.rb +1 -2
- data/test/store_test.rb +1 -2
- data/test/stream/client/auth_test.rb +61 -63
- data/test/stream/client/ready_test.rb +7 -8
- data/test/stream/client/session_test.rb +19 -18
- data/test/stream/component/handshake_test.rb +40 -37
- data/test/stream/component/ready_test.rb +76 -61
- data/test/stream/component/start_test.rb +7 -8
- data/test/stream/http/auth_test.rb +3 -4
- data/test/stream/http/ready_test.rb +52 -60
- data/test/stream/http/request_test.rb +1 -3
- data/test/stream/http/sessions_test.rb +2 -3
- data/test/stream/http/start_test.rb +3 -4
- data/test/stream/parser_test.rb +3 -4
- data/test/stream/sasl_test.rb +105 -86
- data/test/stream/server/auth_test.rb +40 -36
- data/test/stream/server/outbound/auth_test.rb +3 -4
- data/test/stream/server/ready_test.rb +51 -51
- data/test/test_helper.rb +42 -0
- data/test/token_bucket_test.rb +38 -18
- data/test/user_test.rb +79 -49
- data/vines.gemspec +33 -0
- data/web/chat/javascripts/app.js +1 -1
- data/web/lib/coffeescripts/layout.coffee +1 -1
- data/web/lib/javascripts/base.js +10 -10
- data/web/lib/javascripts/jquery.js +4 -4
- metadata +31 -128
- data/README +0 -35
- data/lib/vines/storage/couchdb.rb +0 -129
- data/lib/vines/storage/mongodb.rb +0 -132
- data/lib/vines/storage/redis.rb +0 -127
- data/lib/vines/storage/sql.rb +0 -220
- data/test/rake_test_loader.rb +0 -17
- data/test/storage/couchdb_test.rb +0 -107
- data/test/storage/mock_mongo.rb +0 -40
- data/test/storage/mongodb_test.rb +0 -81
- data/test/storage/redis_test.rb +0 -51
- data/test/storage/sql_test.rb +0 -62
data/README
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
== Welcome to Vines
|
2
|
-
|
3
|
-
Vines is an XMPP chat server that supports thousands of simultaneous connections
|
4
|
-
by using EventMachine for asynchronous IO. User data is stored in a SQL database,
|
5
|
-
CouchDB, MongoDB, Redis, the file system, or a custom storage implementation
|
6
|
-
that you provide. LDAP authentication can be used so user names and passwords
|
7
|
-
aren't stored in the chat database. SSL encryption is mandatory on all client
|
8
|
-
and server connections.
|
9
|
-
|
10
|
-
The Vines XMPP server includes a web chat client. The web application is available
|
11
|
-
immediately after starting the chat server at http://localhost:5280/chat/.
|
12
|
-
|
13
|
-
Additional documentation can be found at www.getvines.org.
|
14
|
-
|
15
|
-
== Usage
|
16
|
-
|
17
|
-
1. gem install vines
|
18
|
-
2. vines init wonderland.lit
|
19
|
-
3. cd wonderland.lit && vines start
|
20
|
-
4. Login with your favorite chat program (iChat, Adium, Pidgin, etc.) to
|
21
|
-
start chatting!
|
22
|
-
|
23
|
-
== Dependencies
|
24
|
-
|
25
|
-
Vines requires Ruby 1.9.2 or better. Instructions for installing the
|
26
|
-
needed OS packages, as well as Ruby itself, are available at
|
27
|
-
http://www.getvines.org/ruby.
|
28
|
-
|
29
|
-
== Contact
|
30
|
-
|
31
|
-
* David Graham <david@negativecode.com>
|
32
|
-
|
33
|
-
== License
|
34
|
-
|
35
|
-
Vines is released under the MIT license. Check the LICENSE file for details.
|
@@ -1,129 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
module Vines
|
4
|
-
class Storage
|
5
|
-
class CouchDB < Storage
|
6
|
-
register :couchdb
|
7
|
-
|
8
|
-
%w[host port database tls username password].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 = {}
|
20
|
-
instance_eval(&block)
|
21
|
-
[:host, :port, :database].each {|key| raise "Must provide #{key}" unless @config[key] }
|
22
|
-
@url = url(@config)
|
23
|
-
end
|
24
|
-
|
25
|
-
def find_user(jid)
|
26
|
-
jid = JID.new(jid).bare.to_s
|
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'] || [])
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def save_user(user)
|
44
|
-
id = "user:#{user.jid.bare}"
|
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
|
52
|
-
end
|
53
|
-
save_doc(doc)
|
54
|
-
end
|
55
|
-
|
56
|
-
def find_vcard(jid)
|
57
|
-
jid = JID.new(jid).bare.to_s
|
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
|
62
|
-
end
|
63
|
-
|
64
|
-
def save_vcard(jid, card)
|
65
|
-
jid = JID.new(jid).bare.to_s
|
66
|
-
id = "vcard:#{jid}"
|
67
|
-
doc = get(id) || {'_id' => id}
|
68
|
-
doc['type'] = 'Vcard'
|
69
|
-
doc['card'] = card.to_xml
|
70
|
-
save_doc(doc)
|
71
|
-
end
|
72
|
-
|
73
|
-
def find_fragment(jid, node)
|
74
|
-
jid = JID.new(jid).bare.to_s
|
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
|
79
|
-
end
|
80
|
-
|
81
|
-
def save_fragment(jid, node)
|
82
|
-
jid = JID.new(jid).bare.to_s
|
83
|
-
id = fragment_id(jid, node)
|
84
|
-
doc = get(id) || {'_id' => id}
|
85
|
-
doc['type'] = 'Fragment'
|
86
|
-
doc['xml'] = node.to_xml
|
87
|
-
save_doc(doc)
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
def fragment_id(jid, node)
|
93
|
-
id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
|
94
|
-
"fragment:#{jid}:#{id}"
|
95
|
-
end
|
96
|
-
|
97
|
-
def url(config)
|
98
|
-
scheme = config[:tls] ? 'https' : 'http'
|
99
|
-
user, password = config.values_at(:username, :password)
|
100
|
-
credentials = empty?(user, password) ? '' : "%s:%s@" % [user, password]
|
101
|
-
"%s://%s%s:%s/%s" % [scheme, credentials, *config.values_at(:host, :port, :database)]
|
102
|
-
end
|
103
|
-
|
104
|
-
def get(jid)
|
105
|
-
http = EM::HttpRequest.new("#{@url}/#{escape(jid)}").get
|
106
|
-
http.errback { yield }
|
107
|
-
http.callback do
|
108
|
-
doc = if http.response_header.status == 200
|
109
|
-
JSON.parse(http.response) rescue nil
|
110
|
-
end
|
111
|
-
yield doc
|
112
|
-
end
|
113
|
-
end
|
114
|
-
fiber :get
|
115
|
-
|
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 }
|
121
|
-
end
|
122
|
-
fiber :save_doc
|
123
|
-
|
124
|
-
def escape(jid)
|
125
|
-
URI.escape(jid, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
@@ -1,132 +0,0 @@
|
|
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
|
data/lib/vines/storage/redis.rb
DELETED
@@ -1,127 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
module Vines
|
4
|
-
class Storage
|
5
|
-
class Redis < Storage
|
6
|
-
register :redis
|
7
|
-
|
8
|
-
%w[host port database password].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 = {}
|
20
|
-
instance_eval(&block)
|
21
|
-
end
|
22
|
-
|
23
|
-
def find_user(jid)
|
24
|
-
jid = JID.new(jid).bare.to_s
|
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)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def save_user(user)
|
37
|
-
doc = {name: user.name, password: user.password}
|
38
|
-
contacts = user.roster.map {|c| [c.jid.to_s, c.to_h.to_json] }.flatten
|
39
|
-
roster = "roster:#{user.jid.bare}"
|
40
|
-
|
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 }
|
49
|
-
end
|
50
|
-
fiber :save_user
|
51
|
-
|
52
|
-
def find_vcard(jid)
|
53
|
-
jid = JID.new(jid).bare.to_s
|
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
|
59
|
-
end
|
60
|
-
|
61
|
-
def save_vcard(jid, card)
|
62
|
-
jid = JID.new(jid).bare.to_s
|
63
|
-
doc = {card: card.to_xml}
|
64
|
-
query(:set, "vcard:#{jid}", doc.to_json)
|
65
|
-
end
|
66
|
-
|
67
|
-
def find_fragment(jid, node)
|
68
|
-
jid = JID.new(jid).bare.to_s
|
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
|
74
|
-
end
|
75
|
-
|
76
|
-
def save_fragment(jid, node)
|
77
|
-
jid = JID.new(jid).bare.to_s
|
78
|
-
doc = {xml: node.to_xml}
|
79
|
-
query(:hset, "fragments:#{jid}", fragment_id(node), doc.to_json)
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
def fragment_id(node)
|
85
|
-
Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
|
86
|
-
end
|
87
|
-
|
88
|
-
# Retrieve the hash stored at roster:jid and return an array of
|
89
|
-
# +Vines::Contact+ objects.
|
90
|
-
def find_roster(jid)
|
91
|
-
jid = JID.new(jid).bare
|
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
|
102
|
-
end
|
103
|
-
|
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
|
112
|
-
# in unbind so only create one connection.
|
113
|
-
def 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
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
data/lib/vines/storage/sql.rb
DELETED
@@ -1,220 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
module Vines
|
4
|
-
class Storage
|
5
|
-
class Sql < Storage
|
6
|
-
register :sql
|
7
|
-
|
8
|
-
class Contact < ActiveRecord::Base
|
9
|
-
belongs_to :user
|
10
|
-
end
|
11
|
-
class Fragment < ActiveRecord::Base
|
12
|
-
belongs_to :user
|
13
|
-
end
|
14
|
-
class Group < ActiveRecord::Base; end
|
15
|
-
class User < ActiveRecord::Base
|
16
|
-
has_many :contacts, :dependent => :destroy
|
17
|
-
has_many :fragments, :dependent => :delete_all
|
18
|
-
end
|
19
|
-
|
20
|
-
# Wrap the method with ActiveRecord connection pool logic, so we properly
|
21
|
-
# return connections to the pool when we're finished with them. This also
|
22
|
-
# defers the original method by pushing it onto the EM thread pool because
|
23
|
-
# ActiveRecord uses blocking IO.
|
24
|
-
def self.with_connection(method, args={})
|
25
|
-
deferrable = args.key?(:defer) ? args[:defer] : true
|
26
|
-
old = instance_method(method)
|
27
|
-
define_method method do |*args|
|
28
|
-
ActiveRecord::Base.connection_pool.with_connection do
|
29
|
-
old.bind(self).call(*args)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
defer(method) if deferrable
|
33
|
-
end
|
34
|
-
|
35
|
-
%w[adapter host port database username password pool].each do |name|
|
36
|
-
define_method(name) do |*args|
|
37
|
-
if args.first
|
38
|
-
@config[name.to_sym] = args.first
|
39
|
-
else
|
40
|
-
@config[name.to_sym]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def initialize(&block)
|
46
|
-
@config = {}
|
47
|
-
instance_eval(&block)
|
48
|
-
required = [:adapter, :database]
|
49
|
-
required << [:host, :port] unless @config[:adapter] == 'sqlite3'
|
50
|
-
required.flatten.each {|key| raise "Must provide #{key}" unless @config[key] }
|
51
|
-
[:username, :password].each {|key| @config.delete(key) if empty?(@config[key]) }
|
52
|
-
establish_connection
|
53
|
-
end
|
54
|
-
|
55
|
-
def find_user(jid)
|
56
|
-
jid = JID.new(jid).bare.to_s
|
57
|
-
return if jid.empty?
|
58
|
-
xuser = user_by_jid(jid)
|
59
|
-
return Vines::User.new(jid: jid).tap do |user|
|
60
|
-
user.name, user.password = xuser.name, xuser.password
|
61
|
-
xuser.contacts.each do |contact|
|
62
|
-
groups = contact.groups.map {|group| group.name }
|
63
|
-
user.roster << Vines::Contact.new(
|
64
|
-
jid: contact.jid,
|
65
|
-
name: contact.name,
|
66
|
-
subscription: contact.subscription,
|
67
|
-
ask: contact.ask,
|
68
|
-
groups: groups)
|
69
|
-
end
|
70
|
-
end if xuser
|
71
|
-
end
|
72
|
-
with_connection :find_user
|
73
|
-
|
74
|
-
def save_user(user)
|
75
|
-
xuser = user_by_jid(user.jid) || Sql::User.new(jid: user.jid.bare.to_s)
|
76
|
-
xuser.name = user.name
|
77
|
-
xuser.password = user.password
|
78
|
-
|
79
|
-
# remove deleted contacts from roster
|
80
|
-
xuser.contacts.delete(xuser.contacts.select do |contact|
|
81
|
-
!user.contact?(contact.jid)
|
82
|
-
end)
|
83
|
-
|
84
|
-
# update contacts
|
85
|
-
xuser.contacts.each do |contact|
|
86
|
-
fresh = user.contact(contact.jid)
|
87
|
-
contact.update_attributes(
|
88
|
-
name: fresh.name,
|
89
|
-
ask: fresh.ask,
|
90
|
-
subscription: fresh.subscription,
|
91
|
-
groups: groups(fresh))
|
92
|
-
end
|
93
|
-
|
94
|
-
# add new contacts to roster
|
95
|
-
jids = xuser.contacts.map {|c| c.jid }
|
96
|
-
user.roster.select {|contact| !jids.include?(contact.jid.bare.to_s) }
|
97
|
-
.each do |contact|
|
98
|
-
xuser.contacts.build(
|
99
|
-
user: xuser,
|
100
|
-
jid: contact.jid.bare.to_s,
|
101
|
-
name: contact.name,
|
102
|
-
ask: contact.ask,
|
103
|
-
subscription: contact.subscription,
|
104
|
-
groups: groups(contact))
|
105
|
-
end
|
106
|
-
xuser.save
|
107
|
-
end
|
108
|
-
with_connection :save_user
|
109
|
-
|
110
|
-
def find_vcard(jid)
|
111
|
-
jid = JID.new(jid).bare.to_s
|
112
|
-
return if jid.empty?
|
113
|
-
if xuser = user_by_jid(jid)
|
114
|
-
Nokogiri::XML(xuser.vcard).root rescue nil
|
115
|
-
end
|
116
|
-
end
|
117
|
-
with_connection :find_vcard
|
118
|
-
|
119
|
-
def save_vcard(jid, card)
|
120
|
-
xuser = user_by_jid(jid)
|
121
|
-
if xuser
|
122
|
-
xuser.vcard = card.to_xml
|
123
|
-
xuser.save
|
124
|
-
end
|
125
|
-
end
|
126
|
-
with_connection :save_vcard
|
127
|
-
|
128
|
-
def find_fragment(jid, node)
|
129
|
-
jid = JID.new(jid).bare.to_s
|
130
|
-
return if jid.empty?
|
131
|
-
if fragment = fragment_by_jid(jid, node)
|
132
|
-
Nokogiri::XML(fragment.xml).root rescue nil
|
133
|
-
end
|
134
|
-
end
|
135
|
-
with_connection :find_fragment
|
136
|
-
|
137
|
-
def save_fragment(jid, node)
|
138
|
-
jid = JID.new(jid).bare.to_s
|
139
|
-
fragment = fragment_by_jid(jid, node) ||
|
140
|
-
Sql::Fragment.new(
|
141
|
-
user: user_by_jid(jid),
|
142
|
-
root: node.name,
|
143
|
-
namespace: node.namespace.href)
|
144
|
-
fragment.xml = node.to_xml
|
145
|
-
fragment.save
|
146
|
-
end
|
147
|
-
with_connection :save_fragment
|
148
|
-
|
149
|
-
# Create the tables and indexes used by this storage engine.
|
150
|
-
def create_schema(args={})
|
151
|
-
args[:force] ||= false
|
152
|
-
|
153
|
-
ActiveRecord::Schema.define do
|
154
|
-
create_table :users, force: args[:force] do |t|
|
155
|
-
t.string :jid, limit: 512, null: false
|
156
|
-
t.string :name, limit: 256, null: true
|
157
|
-
t.string :password, limit: 256, null: true
|
158
|
-
t.text :vcard, null: true
|
159
|
-
end
|
160
|
-
add_index :users, :jid, unique: true
|
161
|
-
|
162
|
-
create_table :contacts, force: args[:force] do |t|
|
163
|
-
t.integer :user_id, null: false
|
164
|
-
t.string :jid, limit: 512, null: false
|
165
|
-
t.string :name, limit: 256, null: true
|
166
|
-
t.string :ask, limit: 128, null: true
|
167
|
-
t.string :subscription, limit: 128, null: false
|
168
|
-
end
|
169
|
-
add_index :contacts, [:user_id, :jid], unique: true
|
170
|
-
|
171
|
-
create_table :groups, force: args[:force] do |t|
|
172
|
-
t.string :name, limit: 256, null: false
|
173
|
-
end
|
174
|
-
add_index :groups, :name, unique: true
|
175
|
-
|
176
|
-
create_table :contacts_groups, id: false, force: args[:force] do |t|
|
177
|
-
t.integer :contact_id, null: false
|
178
|
-
t.integer :group_id, null: false
|
179
|
-
end
|
180
|
-
add_index :contacts_groups, [:contact_id, :group_id], unique: true
|
181
|
-
|
182
|
-
create_table :fragments, force: args[:force] do |t|
|
183
|
-
t.integer :user_id, null: false
|
184
|
-
t.string :root, limit: 256, null: false
|
185
|
-
t.string :namespace, limit: 256, null: false
|
186
|
-
t.text :xml, null: false
|
187
|
-
end
|
188
|
-
add_index :fragments, [:user_id, :root, :namespace], unique: true
|
189
|
-
end
|
190
|
-
end
|
191
|
-
with_connection :create_schema, defer: false
|
192
|
-
|
193
|
-
private
|
194
|
-
|
195
|
-
def establish_connection
|
196
|
-
ActiveRecord::Base.logger = Logger.new('/dev/null')
|
197
|
-
ActiveRecord::Base.establish_connection(@config)
|
198
|
-
# has_and_belongs_to_many requires a connection so configure the
|
199
|
-
# associations here rather than in the class definitions above.
|
200
|
-
Sql::Contact.has_and_belongs_to_many :groups
|
201
|
-
Sql::Group.has_and_belongs_to_many :contacts
|
202
|
-
end
|
203
|
-
|
204
|
-
def user_by_jid(jid)
|
205
|
-
jid = JID.new(jid).bare.to_s
|
206
|
-
Sql::User.find_by_jid(jid, :include => {:contacts => :groups})
|
207
|
-
end
|
208
|
-
|
209
|
-
def fragment_by_jid(jid, node)
|
210
|
-
jid = JID.new(jid).bare.to_s
|
211
|
-
clause = 'user_id=(select id from users where jid=?) and root=? and namespace=?'
|
212
|
-
Sql::Fragment.where(clause, jid, node.name, node.namespace.href).first
|
213
|
-
end
|
214
|
-
|
215
|
-
def groups(contact)
|
216
|
-
contact.groups.map {|name| Sql::Group.find_or_create_by_name(name.strip) }
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|