vinesmod 0.4.5
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/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
|