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.
Files changed (151) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +19 -0
  3. data/README.md +43 -0
  4. data/Rakefile +57 -0
  5. data/bin/vines +93 -0
  6. data/conf/certs/README +39 -0
  7. data/conf/certs/ca-bundle.crt +3366 -0
  8. data/conf/config.rb +149 -0
  9. data/lib/vines.rb +197 -0
  10. data/lib/vines/cluster.rb +246 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/command/bcrypt.rb +12 -0
  17. data/lib/vines/command/cert.rb +50 -0
  18. data/lib/vines/command/init.rb +68 -0
  19. data/lib/vines/command/register.rb +27 -0
  20. data/lib/vines/command/restart.rb +12 -0
  21. data/lib/vines/command/schema.rb +24 -0
  22. data/lib/vines/command/start.rb +28 -0
  23. data/lib/vines/command/stop.rb +18 -0
  24. data/lib/vines/command/unregister.rb +27 -0
  25. data/lib/vines/config.rb +213 -0
  26. data/lib/vines/config/host.rb +119 -0
  27. data/lib/vines/config/port.rb +132 -0
  28. data/lib/vines/config/pubsub.rb +108 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +35 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza.rb +175 -0
  37. data/lib/vines/stanza/iq.rb +48 -0
  38. data/lib/vines/stanza/iq/auth.rb +18 -0
  39. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  40. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  41. data/lib/vines/stanza/iq/error.rb +16 -0
  42. data/lib/vines/stanza/iq/ping.rb +16 -0
  43. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  44. data/lib/vines/stanza/iq/query.rb +10 -0
  45. data/lib/vines/stanza/iq/register.rb +42 -0
  46. data/lib/vines/stanza/iq/result.rb +16 -0
  47. data/lib/vines/stanza/iq/roster.rb +140 -0
  48. data/lib/vines/stanza/iq/session.rb +17 -0
  49. data/lib/vines/stanza/iq/vcard.rb +56 -0
  50. data/lib/vines/stanza/iq/version.rb +25 -0
  51. data/lib/vines/stanza/message.rb +43 -0
  52. data/lib/vines/stanza/presence.rb +156 -0
  53. data/lib/vines/stanza/presence/error.rb +23 -0
  54. data/lib/vines/stanza/presence/probe.rb +37 -0
  55. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  56. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  57. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  58. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  59. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  60. data/lib/vines/stanza/pubsub.rb +22 -0
  61. data/lib/vines/stanza/pubsub/create.rb +39 -0
  62. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  63. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  64. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  65. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  66. data/lib/vines/storage.rb +188 -0
  67. data/lib/vines/storage/local.rb +165 -0
  68. data/lib/vines/storage/null.rb +39 -0
  69. data/lib/vines/storage/sql.rb +260 -0
  70. data/lib/vines/store.rb +94 -0
  71. data/lib/vines/stream.rb +247 -0
  72. data/lib/vines/stream/client.rb +84 -0
  73. data/lib/vines/stream/client/auth.rb +74 -0
  74. data/lib/vines/stream/client/auth_restart.rb +29 -0
  75. data/lib/vines/stream/client/bind.rb +72 -0
  76. data/lib/vines/stream/client/bind_restart.rb +24 -0
  77. data/lib/vines/stream/client/closed.rb +13 -0
  78. data/lib/vines/stream/client/ready.rb +17 -0
  79. data/lib/vines/stream/client/session.rb +210 -0
  80. data/lib/vines/stream/client/start.rb +27 -0
  81. data/lib/vines/stream/client/tls.rb +38 -0
  82. data/lib/vines/stream/component.rb +58 -0
  83. data/lib/vines/stream/component/handshake.rb +26 -0
  84. data/lib/vines/stream/component/ready.rb +23 -0
  85. data/lib/vines/stream/component/start.rb +19 -0
  86. data/lib/vines/stream/http.rb +157 -0
  87. data/lib/vines/stream/http/auth.rb +22 -0
  88. data/lib/vines/stream/http/bind.rb +32 -0
  89. data/lib/vines/stream/http/bind_restart.rb +37 -0
  90. data/lib/vines/stream/http/ready.rb +29 -0
  91. data/lib/vines/stream/http/request.rb +172 -0
  92. data/lib/vines/stream/http/session.rb +120 -0
  93. data/lib/vines/stream/http/sessions.rb +65 -0
  94. data/lib/vines/stream/http/start.rb +23 -0
  95. data/lib/vines/stream/parser.rb +78 -0
  96. data/lib/vines/stream/sasl.rb +92 -0
  97. data/lib/vines/stream/server.rb +150 -0
  98. data/lib/vines/stream/server/auth.rb +13 -0
  99. data/lib/vines/stream/server/auth_restart.rb +13 -0
  100. data/lib/vines/stream/server/final_restart.rb +21 -0
  101. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  102. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  103. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  104. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  105. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  106. data/lib/vines/stream/server/outbound/start.rb +20 -0
  107. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  108. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  109. data/lib/vines/stream/server/ready.rb +24 -0
  110. data/lib/vines/stream/server/start.rb +13 -0
  111. data/lib/vines/stream/server/tls.rb +13 -0
  112. data/lib/vines/stream/state.rb +60 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +5 -0
  116. data/lib/vines/xmpp_server.rb +43 -0
  117. data/vines.gemspec +36 -0
  118. data/web/404.html +51 -0
  119. data/web/apple-touch-icon.png +0 -0
  120. data/web/chat/coffeescripts/chat.coffee +362 -0
  121. data/web/chat/coffeescripts/init.coffee +15 -0
  122. data/web/chat/index.html +16 -0
  123. data/web/chat/javascripts/app.js +1 -0
  124. data/web/chat/stylesheets/chat.css +144 -0
  125. data/web/favicon.png +0 -0
  126. data/web/lib/coffeescripts/button.coffee +25 -0
  127. data/web/lib/coffeescripts/contact.coffee +32 -0
  128. data/web/lib/coffeescripts/filter.coffee +49 -0
  129. data/web/lib/coffeescripts/layout.coffee +30 -0
  130. data/web/lib/coffeescripts/login.coffee +68 -0
  131. data/web/lib/coffeescripts/logout.coffee +5 -0
  132. data/web/lib/coffeescripts/navbar.coffee +84 -0
  133. data/web/lib/coffeescripts/notification.coffee +14 -0
  134. data/web/lib/coffeescripts/router.coffee +40 -0
  135. data/web/lib/coffeescripts/session.coffee +229 -0
  136. data/web/lib/coffeescripts/transfer.coffee +106 -0
  137. data/web/lib/images/dark-gray.png +0 -0
  138. data/web/lib/images/default-user.png +0 -0
  139. data/web/lib/images/light-gray.png +0 -0
  140. data/web/lib/images/logo-large.png +0 -0
  141. data/web/lib/images/logo-small.png +0 -0
  142. data/web/lib/images/white.png +0 -0
  143. data/web/lib/javascripts/base.js +12 -0
  144. data/web/lib/javascripts/icons.js +110 -0
  145. data/web/lib/javascripts/jquery.cookie.js +91 -0
  146. data/web/lib/javascripts/jquery.js +4 -0
  147. data/web/lib/javascripts/raphael.js +6 -0
  148. data/web/lib/javascripts/strophe.js +1 -0
  149. data/web/lib/stylesheets/base.css +385 -0
  150. data/web/lib/stylesheets/login.css +68 -0
  151. 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