vinesmod 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +43 -0
- data/Rakefile +57 -0
- data/bin/vines +93 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3366 -0
- data/conf/config.rb +149 -0
- data/lib/vines.rb +197 -0
- data/lib/vines/cluster.rb +246 -0
- 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/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +50 -0
- data/lib/vines/command/init.rb +68 -0
- data/lib/vines/command/register.rb +27 -0
- data/lib/vines/command/restart.rb +12 -0
- data/lib/vines/command/schema.rb +24 -0
- data/lib/vines/command/start.rb +28 -0
- data/lib/vines/command/stop.rb +18 -0
- data/lib/vines/command/unregister.rb +27 -0
- data/lib/vines/config.rb +213 -0
- data/lib/vines/config/host.rb +119 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -0
- data/lib/vines/contact.rb +111 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +95 -0
- data/lib/vines/kit.rb +35 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/stanza/iq.rb +48 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +45 -0
- data/lib/vines/stanza/iq/disco_items.rb +29 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/register.rb +42 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +140 -0
- data/lib/vines/stanza/iq/session.rb +17 -0
- data/lib/vines/stanza/iq/vcard.rb +56 -0
- data/lib/vines/stanza/iq/version.rb +25 -0
- data/lib/vines/stanza/message.rb +43 -0
- data/lib/vines/stanza/presence.rb +156 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +37 -0
- data/lib/vines/stanza/presence/subscribe.rb +42 -0
- data/lib/vines/stanza/presence/subscribed.rb +51 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
- data/lib/vines/stanza/pubsub.rb +22 -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/storage.rb +188 -0
- data/lib/vines/storage/local.rb +165 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +260 -0
- data/lib/vines/store.rb +94 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/stream/client.rb +84 -0
- data/lib/vines/stream/client/auth.rb +74 -0
- data/lib/vines/stream/client/auth_restart.rb +29 -0
- data/lib/vines/stream/client/bind.rb +72 -0
- data/lib/vines/stream/client/bind_restart.rb +24 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +17 -0
- data/lib/vines/stream/client/session.rb +210 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +38 -0
- data/lib/vines/stream/component.rb +58 -0
- data/lib/vines/stream/component/handshake.rb +26 -0
- data/lib/vines/stream/component/ready.rb +23 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/http.rb +157 -0
- data/lib/vines/stream/http/auth.rb +22 -0
- data/lib/vines/stream/http/bind.rb +32 -0
- data/lib/vines/stream/http/bind_restart.rb +37 -0
- data/lib/vines/stream/http/ready.rb +29 -0
- data/lib/vines/stream/http/request.rb +172 -0
- data/lib/vines/stream/http/session.rb +120 -0
- data/lib/vines/stream/http/sessions.rb +65 -0
- data/lib/vines/stream/http/start.rb +23 -0
- data/lib/vines/stream/parser.rb +78 -0
- data/lib/vines/stream/sasl.rb +92 -0
- data/lib/vines/stream/server.rb +150 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +13 -0
- data/lib/vines/stream/server/final_restart.rb +21 -0
- data/lib/vines/stream/server/outbound/auth.rb +31 -0
- data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
- data/lib/vines/stream/server/outbound/final_features.rb +28 -0
- data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/start.rb +20 -0
- data/lib/vines/stream/server/outbound/tls.rb +30 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
- data/lib/vines/stream/server/ready.rb +24 -0
- data/lib/vines/stream/server/start.rb +13 -0
- data/lib/vines/stream/server/tls.rb +13 -0
- data/lib/vines/stream/state.rb +60 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +5 -0
- data/lib/vines/xmpp_server.rb +43 -0
- data/vines.gemspec +36 -0
- data/web/404.html +51 -0
- data/web/apple-touch-icon.png +0 -0
- data/web/chat/coffeescripts/chat.coffee +362 -0
- data/web/chat/coffeescripts/init.coffee +15 -0
- data/web/chat/index.html +16 -0
- data/web/chat/javascripts/app.js +1 -0
- data/web/chat/stylesheets/chat.css +144 -0
- data/web/favicon.png +0 -0
- data/web/lib/coffeescripts/button.coffee +25 -0
- data/web/lib/coffeescripts/contact.coffee +32 -0
- data/web/lib/coffeescripts/filter.coffee +49 -0
- data/web/lib/coffeescripts/layout.coffee +30 -0
- data/web/lib/coffeescripts/login.coffee +68 -0
- data/web/lib/coffeescripts/logout.coffee +5 -0
- data/web/lib/coffeescripts/navbar.coffee +84 -0
- data/web/lib/coffeescripts/notification.coffee +14 -0
- data/web/lib/coffeescripts/router.coffee +40 -0
- data/web/lib/coffeescripts/session.coffee +229 -0
- data/web/lib/coffeescripts/transfer.coffee +106 -0
- data/web/lib/images/dark-gray.png +0 -0
- data/web/lib/images/default-user.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 +12 -0
- data/web/lib/javascripts/icons.js +110 -0
- data/web/lib/javascripts/jquery.cookie.js +91 -0
- data/web/lib/javascripts/jquery.js +4 -0
- data/web/lib/javascripts/raphael.js +6 -0
- data/web/lib/javascripts/strophe.js +1 -0
- data/web/lib/stylesheets/base.css +385 -0
- data/web/lib/stylesheets/login.css +68 -0
- metadata +423 -0
@@ -0,0 +1,165 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Storage
|
5
|
+
|
6
|
+
# A storage implementation that persists data to YAML files on the
|
7
|
+
# local file system.
|
8
|
+
class Local < Storage
|
9
|
+
register :fs
|
10
|
+
|
11
|
+
def initialize(&block)
|
12
|
+
@dir = nil
|
13
|
+
instance_eval(&block)
|
14
|
+
unless @dir && File.directory?(@dir) && File.writable?(@dir)
|
15
|
+
raise 'Must provide a writable storage directory'
|
16
|
+
end
|
17
|
+
|
18
|
+
%w[user vcard fragment room message].each do |sub|
|
19
|
+
sub = File.expand_path(sub, @dir)
|
20
|
+
Dir.mkdir(sub, 0700) unless File.exists?(sub)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def dir(dir=nil)
|
25
|
+
dir ? @dir = File.expand_path(dir) : @dir
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_user(jid)
|
29
|
+
jid = JID.new(jid).bare.to_s
|
30
|
+
file = "user/#{jid}" unless jid.empty?
|
31
|
+
record = YAML.load(read(file)) rescue nil
|
32
|
+
return User.new(jid: jid).tap do |user|
|
33
|
+
user.name, user.password = record.values_at('name', 'password')
|
34
|
+
(record['roster'] || {}).each_pair do |jid, props|
|
35
|
+
user.roster << Contact.new(
|
36
|
+
jid: jid,
|
37
|
+
name: props['name'],
|
38
|
+
subscription: props['subscription'],
|
39
|
+
ask: props['ask'],
|
40
|
+
groups: props['groups'] || [])
|
41
|
+
end
|
42
|
+
end if record
|
43
|
+
end
|
44
|
+
|
45
|
+
def save_user(user)
|
46
|
+
record = {'name' => user.name, 'password' => user.password, 'roster' => {}}
|
47
|
+
user.roster.each do |contact|
|
48
|
+
record['roster'][contact.jid.bare.to_s] = contact.to_h
|
49
|
+
end
|
50
|
+
save("user/#{user.jid.bare}", YAML.dump(record))
|
51
|
+
end
|
52
|
+
|
53
|
+
def offline_messages_present?(jid)
|
54
|
+
File.exist?(absolute_path("message/#{jid.bare.to_s}"))
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete_offline_messages(jid)
|
58
|
+
if offline_messages_present?(jid)
|
59
|
+
File.delete(absolute_path("message/#{jid.bare.to_s}"))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def fetch_offline_messages(jid)
|
64
|
+
jid = JID.new(jid).bare.to_s
|
65
|
+
file = absolute_path("message/#{jid}") unless jid.empty?
|
66
|
+
offline_msgs = YAML.load_file(file) rescue {}
|
67
|
+
end
|
68
|
+
|
69
|
+
def save_offline_message(msg)
|
70
|
+
file = "message/#{msg[:to]}"
|
71
|
+
offline_msgs = YAML.load_file(absolute_path(file)) rescue []
|
72
|
+
msg.delete('to')
|
73
|
+
offline_msgs << msg
|
74
|
+
save(file) do |f|
|
75
|
+
YAML.dump(offline_msgs,f)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_vcard(jid)
|
80
|
+
jid = JID.new(jid).bare.to_s
|
81
|
+
return if jid.empty?
|
82
|
+
file = "vcard/#{jid}"
|
83
|
+
Nokogiri::XML(read(file)).root rescue nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def save_vcard(jid, card)
|
87
|
+
jid = JID.new(jid).bare.to_s
|
88
|
+
return if jid.empty?
|
89
|
+
save("vcard/#{jid}", card.to_xml)
|
90
|
+
end
|
91
|
+
|
92
|
+
def find_fragment(jid, node)
|
93
|
+
jid = JID.new(jid).bare.to_s
|
94
|
+
return if jid.empty?
|
95
|
+
file = 'fragment/%s' % fragment_id(jid, node)
|
96
|
+
Nokogiri::XML(read(file)).root rescue nil
|
97
|
+
end
|
98
|
+
|
99
|
+
def save_fragment(jid, node)
|
100
|
+
jid = JID.new(jid).bare.to_s
|
101
|
+
return if jid.empty?
|
102
|
+
file = 'fragment/%s' % fragment_id(jid, node)
|
103
|
+
save(file, node.to_xml)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Resolves a relative file name into an absolute path inside the
|
109
|
+
# storage directory.
|
110
|
+
#
|
111
|
+
# file - A fully-qualified or relative file name String.
|
112
|
+
#
|
113
|
+
# Returns the fully-qualified file path String.
|
114
|
+
#
|
115
|
+
# Raises RuntimeError if the resolved path is outside of the storage
|
116
|
+
# directory. This prevents directory path traversals with maliciously
|
117
|
+
# crafted JIDs.
|
118
|
+
def absolute_path(file)
|
119
|
+
File.expand_path(file, @dir).tap do |absolute|
|
120
|
+
parent = File.dirname(File.dirname(absolute))
|
121
|
+
raise "path traversal failed for #{file}" unless parent == @dir
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Read the file from the filesystem and return its contents as a String.
|
126
|
+
# All files are assumed to be encoded as UTF-8.
|
127
|
+
#
|
128
|
+
# file - A fully-qualified or relative file name String.
|
129
|
+
#
|
130
|
+
# Returns the file content as a UTF-8 encoded String.
|
131
|
+
def read(file)
|
132
|
+
file = absolute_path(file)
|
133
|
+
File.read(file, encoding: 'utf-8')
|
134
|
+
end
|
135
|
+
|
136
|
+
# Write the content to the file. Make sure to consistently encode files
|
137
|
+
# we read and write as UTF-8.
|
138
|
+
#
|
139
|
+
# file - A fully-qualified or relative file name String.
|
140
|
+
# content - The String to write.
|
141
|
+
#
|
142
|
+
# Returns nothing.
|
143
|
+
def save(file, content)
|
144
|
+
file = absolute_path(file)
|
145
|
+
File.open(file, 'w:utf-8') {|f| f.write(content) }
|
146
|
+
File.chmod(0600, file)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Generates a unique file id for the user's private XML fragment.
|
150
|
+
#
|
151
|
+
# Private XML fragment storage needs to uniquely identify fragment files
|
152
|
+
# on disk. We combine the user's JID with a SHA-1 hash of the element's
|
153
|
+
# name and namespace to avoid special characters in the file name.
|
154
|
+
#
|
155
|
+
# jid - A bare JID identifying the user who owns this fragment.
|
156
|
+
# node - A Nokogiri::XML::Node for the XML to be stored.
|
157
|
+
#
|
158
|
+
# Returns an id String suitable for use in a file name.
|
159
|
+
def fragment_id(jid, node)
|
160
|
+
id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}")
|
161
|
+
"#{jid}-#{id}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
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
|
@@ -0,0 +1,260 @@
|
|
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
|
+
|
16
|
+
class OfflineMessage < ActiveRecord::Base
|
17
|
+
belongs_to :sender,:foreign_key=>"from",:class_name=>"User"
|
18
|
+
belongs_to :recipient,:foreign_key=>"to",:class_name=>"User"
|
19
|
+
end
|
20
|
+
|
21
|
+
class User < ActiveRecord::Base
|
22
|
+
has_many :contacts, :dependent => :destroy
|
23
|
+
has_many :fragments, :dependent => :delete_all
|
24
|
+
end
|
25
|
+
|
26
|
+
# Wrap the method with ActiveRecord connection pool logic, so we properly
|
27
|
+
# return connections to the pool when we're finished with them. This also
|
28
|
+
# defers the original method by pushing it onto the EM thread pool because
|
29
|
+
# ActiveRecord uses blocking IO.
|
30
|
+
def self.with_connection(method, args={})
|
31
|
+
deferrable = args.key?(:defer) ? args[:defer] : true
|
32
|
+
old = instance_method(method)
|
33
|
+
define_method method do |*args|
|
34
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
35
|
+
old.bind(self).call(*args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
defer(method) if deferrable
|
39
|
+
end
|
40
|
+
|
41
|
+
%w[adapter host port database username password pool].each do |name|
|
42
|
+
define_method(name) do |*args|
|
43
|
+
if args.first
|
44
|
+
@config[name.to_sym] = args.first
|
45
|
+
else
|
46
|
+
@config[name.to_sym]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(&block)
|
52
|
+
@config = {}
|
53
|
+
instance_eval(&block)
|
54
|
+
required = [:adapter, :database]
|
55
|
+
required << [:host, :port] unless @config[:adapter] == 'sqlite3'
|
56
|
+
required.flatten.each {|key| raise "Must provide #{key}" unless @config[key] }
|
57
|
+
[:username, :password].each {|key| @config.delete(key) if empty?(@config[key]) }
|
58
|
+
establish_connection
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_user(jid)
|
62
|
+
jid = JID.new(jid).bare.to_s
|
63
|
+
return if jid.empty?
|
64
|
+
xuser = user_by_jid(jid)
|
65
|
+
return Vines::User.new(jid: jid).tap do |user|
|
66
|
+
user.name, user.password = xuser.name, xuser.password
|
67
|
+
xuser.contacts.each do |contact|
|
68
|
+
groups = contact.groups.map {|group| group.name }
|
69
|
+
user.roster << Vines::Contact.new(
|
70
|
+
jid: contact.jid,
|
71
|
+
name: contact.name,
|
72
|
+
subscription: contact.subscription,
|
73
|
+
ask: contact.ask,
|
74
|
+
groups: groups)
|
75
|
+
end
|
76
|
+
end if xuser
|
77
|
+
end
|
78
|
+
with_connection :find_user
|
79
|
+
|
80
|
+
def save_user(user)
|
81
|
+
xuser = user_by_jid(user.jid) || Sql::User.new(jid: user.jid.bare.to_s)
|
82
|
+
xuser.name = user.name
|
83
|
+
xuser.password = user.password
|
84
|
+
|
85
|
+
# remove deleted contacts from roster
|
86
|
+
xuser.contacts.delete(xuser.contacts.select do |contact|
|
87
|
+
!user.contact?(contact.jid)
|
88
|
+
end)
|
89
|
+
|
90
|
+
# update contacts
|
91
|
+
xuser.contacts.each do |contact|
|
92
|
+
fresh = user.contact(contact.jid)
|
93
|
+
contact.update_attributes(
|
94
|
+
name: fresh.name,
|
95
|
+
ask: fresh.ask,
|
96
|
+
subscription: fresh.subscription,
|
97
|
+
groups: groups(fresh))
|
98
|
+
end
|
99
|
+
|
100
|
+
# add new contacts to roster
|
101
|
+
jids = xuser.contacts.map {|c| c.jid }
|
102
|
+
user.roster.select {|contact| !jids.include?(contact.jid.bare.to_s) }
|
103
|
+
.each do |contact|
|
104
|
+
xuser.contacts.build(
|
105
|
+
user: xuser,
|
106
|
+
jid: contact.jid.bare.to_s,
|
107
|
+
name: contact.name,
|
108
|
+
ask: contact.ask,
|
109
|
+
subscription: contact.subscription,
|
110
|
+
groups: groups(contact))
|
111
|
+
end
|
112
|
+
xuser.save
|
113
|
+
end
|
114
|
+
with_connection :save_user
|
115
|
+
|
116
|
+
def offline_messages_present?(jid)
|
117
|
+
jid = JID.new(jid).bare.to_s
|
118
|
+
return if jid.empty?
|
119
|
+
offline_messages_to_jid(jid)
|
120
|
+
end
|
121
|
+
|
122
|
+
def delete_offline_messages(jid)
|
123
|
+
jid = JID.new(jid).bare.to_s
|
124
|
+
return if jid.empty?
|
125
|
+
Sql::OfflineMessage.destroy_all(:to=>jid)
|
126
|
+
end
|
127
|
+
|
128
|
+
def fetch_offline_messages(jid)
|
129
|
+
jid = JID.new(jid).bare.to_s
|
130
|
+
offline_msgs = offline_messages_to_jid(jid) || {}
|
131
|
+
end
|
132
|
+
|
133
|
+
def save_offline_message(msg)
|
134
|
+
Sql::OfflineMessage.create(msg)
|
135
|
+
end
|
136
|
+
|
137
|
+
def find_vcard(jid)
|
138
|
+
jid = JID.new(jid).bare.to_s
|
139
|
+
return if jid.empty?
|
140
|
+
if xuser = user_by_jid(jid)
|
141
|
+
Nokogiri::XML(xuser.vcard).root rescue nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
with_connection :find_vcard
|
145
|
+
|
146
|
+
def save_vcard(jid, card)
|
147
|
+
xuser = user_by_jid(jid)
|
148
|
+
if xuser
|
149
|
+
xuser.vcard = card.to_xml
|
150
|
+
xuser.save
|
151
|
+
end
|
152
|
+
end
|
153
|
+
with_connection :save_vcard
|
154
|
+
|
155
|
+
def find_fragment(jid, node)
|
156
|
+
jid = JID.new(jid).bare.to_s
|
157
|
+
return if jid.empty?
|
158
|
+
if fragment = fragment_by_jid(jid, node)
|
159
|
+
Nokogiri::XML(fragment.xml).root rescue nil
|
160
|
+
end
|
161
|
+
end
|
162
|
+
with_connection :find_fragment
|
163
|
+
|
164
|
+
def save_fragment(jid, node)
|
165
|
+
jid = JID.new(jid).bare.to_s
|
166
|
+
fragment = fragment_by_jid(jid, node) ||
|
167
|
+
Sql::Fragment.new(
|
168
|
+
user: user_by_jid(jid),
|
169
|
+
root: node.name,
|
170
|
+
namespace: node.namespace.href)
|
171
|
+
fragment.xml = node.to_xml
|
172
|
+
fragment.save
|
173
|
+
end
|
174
|
+
with_connection :save_fragment
|
175
|
+
|
176
|
+
# Create the tables and indexes used by this storage engine.
|
177
|
+
def create_schema(args={})
|
178
|
+
args[:force] ||= false
|
179
|
+
|
180
|
+
ActiveRecord::Schema.define do
|
181
|
+
create_table :users, force: args[:force] do |t|
|
182
|
+
t.string :jid, limit: 512, null: false
|
183
|
+
t.string :name, limit: 256, null: true
|
184
|
+
t.string :password, limit: 256, null: true
|
185
|
+
t.text :vcard, null: true
|
186
|
+
end
|
187
|
+
add_index :users, :jid, unique: true
|
188
|
+
|
189
|
+
create_table :contacts, force: args[:force] do |t|
|
190
|
+
t.integer :user_id, null: false
|
191
|
+
t.string :jid, limit: 512, null: false
|
192
|
+
t.string :name, limit: 256, null: true
|
193
|
+
t.string :ask, limit: 128, null: true
|
194
|
+
t.string :subscription, limit: 128, null: false
|
195
|
+
end
|
196
|
+
add_index :contacts, [:user_id, :jid], unique: true
|
197
|
+
|
198
|
+
create_table :groups, force: args[:force] do |t|
|
199
|
+
t.string :name, limit: 256, null: false
|
200
|
+
end
|
201
|
+
add_index :groups, :name, unique: true
|
202
|
+
|
203
|
+
create_table :contacts_groups, id: false, force: args[:force] do |t|
|
204
|
+
t.integer :contact_id, null: false
|
205
|
+
t.integer :group_id, null: false
|
206
|
+
end
|
207
|
+
add_index :contacts_groups, [:contact_id, :group_id], unique: true
|
208
|
+
|
209
|
+
create_table :fragments, force: args[:force] do |t|
|
210
|
+
t.integer :user_id, null: false
|
211
|
+
t.string :root, limit: 256, null: false
|
212
|
+
t.string :namespace, limit: 256, null: false
|
213
|
+
t.text :xml, null: false
|
214
|
+
end
|
215
|
+
add_index :fragments, [:user_id, :root, :namespace], unique: true
|
216
|
+
|
217
|
+
create_table :offline_messages do |t|
|
218
|
+
t.string :from, null: false
|
219
|
+
t.string :to, limit: 512,null: false
|
220
|
+
t.text :body, null: false
|
221
|
+
end
|
222
|
+
add_index :offline_messages,[:from,:to]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
with_connection :create_schema, defer: false
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
def establish_connection
|
230
|
+
ActiveRecord::Base.logger = Logger.new('/dev/null')
|
231
|
+
ActiveRecord::Base.establish_connection(@config)
|
232
|
+
# has_and_belongs_to_many requires a connection so configure the
|
233
|
+
# associations here rather than in the class definitions above.
|
234
|
+
Sql::Contact.has_and_belongs_to_many :groups
|
235
|
+
Sql::Group.has_and_belongs_to_many :contacts
|
236
|
+
end
|
237
|
+
|
238
|
+
def user_by_jid(jid)
|
239
|
+
jid = JID.new(jid).bare.to_s
|
240
|
+
Sql::User.find_by_jid(jid, :include => {:contacts => :groups})
|
241
|
+
end
|
242
|
+
|
243
|
+
def fragment_by_jid(jid, node)
|
244
|
+
jid = JID.new(jid).bare.to_s
|
245
|
+
clause = 'user_id=(select id from users where jid=?) and root=? and namespace=?'
|
246
|
+
Sql::Fragment.where(clause, jid, node.name, node.namespace.href).first
|
247
|
+
end
|
248
|
+
|
249
|
+
def groups(contact)
|
250
|
+
contact.groups.map {|name| Sql::Group.find_or_create_by_name(name.strip) }
|
251
|
+
end
|
252
|
+
|
253
|
+
def offline_messages_to_jid(jid)
|
254
|
+
jid = JID.new(jid).bare.to_s
|
255
|
+
Sql::OfflineMessage.find_all_by_to(jid)
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|