class Vines::Storage::Sql

Public Class Methods

new(&block) click to toggle source
# File lib/vines/storage/sql.rb, line 51
def initialize(&block)
  @config = {}
  instance_eval(&block)
  required = [:adapter, :database]
  required << [:host, :port] unless @config[:adapter] == 'sqlite3'
  required.flatten.each {|key| raise "Must provide #{key}" unless @config[key] }
  [:username, :password].each {|key| @config.delete(key) if empty?(@config[key]) }
  establish_connection
end
with_connection(method, args={}) click to toggle source

Wrap the method with ActiveRecord connection pool logic, so we properly return connections to the pool when we're finished with them. This also defers the original method by pushing it onto the EM thread pool because ActiveRecord uses blocking IO.

# File lib/vines/storage/sql.rb, line 30
def self.with_connection(method, args={})
  deferrable = args.key?(:defer) ? args[:defer] : true
  old = instance_method(method)
  define_method method do |*args|
    ActiveRecord::Base.connection_pool.with_connection do
      old.bind(self).call(*args)
    end
  end
  defer(method) if deferrable
end

Public Instance Methods

create_schema(args={}) click to toggle source

Create the tables and indexes used by this storage engine.

# File lib/vines/storage/sql.rb, line 177
def create_schema(args={})
  args[:force] ||= false

  ActiveRecord::Schema.define do
    create_table :users, force: args[:force] do |t|
      t.string :jid,      limit: 512, null: false
      t.string :name,     limit: 256, null: true
      t.string :password, limit: 256, null: true
      t.text   :vcard,    null: true
    end
    add_index :users, :jid, unique: true

    create_table :contacts, force: args[:force] do |t|
      t.integer :user_id,      null: false
      t.string  :jid,          limit: 512, null: false
      t.string  :name,         limit: 256, null: true
      t.string  :ask,          limit: 128, null: true
      t.string  :subscription, limit: 128, null: false
    end
    add_index :contacts, [:user_id, :jid], unique: true

    create_table :groups, force: args[:force] do |t|
      t.string :name, limit: 256, null: false
    end
    add_index :groups, :name, unique: true

    create_table :contacts_groups, id: false, force: args[:force] do |t|
      t.integer :contact_id, null: false
      t.integer :group_id,   null: false
    end
    add_index :contacts_groups, [:contact_id, :group_id], unique: true

    create_table :fragments, force: args[:force] do |t|
      t.integer :user_id,   null: false
      t.string  :root,      limit: 256, null: false
      t.string  :namespace, limit: 256, null: false
      t.text    :xml,       null: false
    end
    add_index :fragments, [:user_id, :root, :namespace], unique: true

    create_table :offline_messages do |t|
      t.string  :from,      null: false
      t.string  :to,        limit: 512,null: false
      t.text    :body,      null: false
    end
    add_index :offline_messages,[:from,:to]
  end
end
delete_offline_messages(jid) click to toggle source
# File lib/vines/storage/sql.rb, line 122
def delete_offline_messages(jid)
  jid = JID.new(jid).bare.to_s
  return if jid.empty?
  Sql::OfflineMessage.destroy_all(:to=>jid)
end
fetch_offline_messages(jid) click to toggle source
# File lib/vines/storage/sql.rb, line 128
def fetch_offline_messages(jid)
  jid = JID.new(jid).bare.to_s
  offline_msgs = offline_messages_to_jid(jid) || {}
end
find_fragment(jid, node) click to toggle source
# File lib/vines/storage/sql.rb, line 155
def find_fragment(jid, node)
  jid = JID.new(jid).bare.to_s
  return if jid.empty?
  if fragment = fragment_by_jid(jid, node)
    Nokogiri::XML(fragment.xml).root rescue nil
  end
end
find_user(jid) click to toggle source
# File lib/vines/storage/sql.rb, line 61
def find_user(jid)
  jid = JID.new(jid).bare.to_s
  return if jid.empty?
  xuser = user_by_jid(jid)
  return Vines::User.new(jid: jid).tap do |user|
    user.name, user.password = xuser.name, xuser.password
    xuser.contacts.each do |contact|
      groups = contact.groups.map {|group| group.name }
      user.roster << Vines::Contact.new(
        jid: contact.jid,
        name: contact.name,
        subscription: contact.subscription,
        ask: contact.ask,
        groups: groups)
    end
  end if xuser
end
find_vcard(jid) click to toggle source
# File lib/vines/storage/sql.rb, line 137
def find_vcard(jid)
  jid = JID.new(jid).bare.to_s
  return if jid.empty?
  if xuser = user_by_jid(jid)
    Nokogiri::XML(xuser.vcard).root rescue nil
  end
end
offline_messages_present?(jid) click to toggle source
# File lib/vines/storage/sql.rb, line 116
def offline_messages_present?(jid)
  jid = JID.new(jid).bare.to_s
  return if jid.empty?
  offline_messages_to_jid(jid)
end
save_fragment(jid, node) click to toggle source
# File lib/vines/storage/sql.rb, line 164
def save_fragment(jid, node)
  jid = JID.new(jid).bare.to_s
  fragment = fragment_by_jid(jid, node) ||
    Sql::Fragment.new(
    user: user_by_jid(jid),
    root: node.name,
    namespace: node.namespace.href)
  fragment.xml = node.to_xml
  fragment.save
end
save_offline_message(msg) click to toggle source
# File lib/vines/storage/sql.rb, line 133
def save_offline_message(msg)        
  Sql::OfflineMessage.create(msg)
end
save_user(user) click to toggle source
# File lib/vines/storage/sql.rb, line 80
def save_user(user)
  xuser = user_by_jid(user.jid) || Sql::User.new(jid: user.jid.bare.to_s)
  xuser.name = user.name
  xuser.password = user.password

  # remove deleted contacts from roster
  xuser.contacts.delete(xuser.contacts.select do |contact|
      !user.contact?(contact.jid)
    end)

  # update contacts
  xuser.contacts.each do |contact|
    fresh = user.contact(contact.jid)
    contact.update_attributes(
      name: fresh.name,
      ask: fresh.ask,
      subscription: fresh.subscription,
      groups: groups(fresh))
  end

  # add new contacts to roster
  jids = xuser.contacts.map {|c| c.jid }
  user.roster.select {|contact| !jids.include?(contact.jid.bare.to_s) }
  .each do |contact|
    xuser.contacts.build(
      user: xuser,
      jid: contact.jid.bare.to_s,
      name: contact.name,
      ask: contact.ask,
      subscription: contact.subscription,
      groups: groups(contact))
  end
  xuser.save
end
save_vcard(jid, card) click to toggle source
# File lib/vines/storage/sql.rb, line 146
def save_vcard(jid, card)
  xuser = user_by_jid(jid)
  if xuser
    xuser.vcard = card.to_xml
    xuser.save
  end
end

Private Instance Methods

establish_connection() click to toggle source
# File lib/vines/storage/sql.rb, line 229
def establish_connection
  ActiveRecord::Base.logger = Logger.new('/dev/null')
  ActiveRecord::Base.establish_connection(@config)
  # has_and_belongs_to_many requires a connection so configure the
  # associations here rather than in the class definitions above.
  Sql::Contact.has_and_belongs_to_many :groups
  Sql::Group.has_and_belongs_to_many :contacts
end
fragment_by_jid(jid, node) click to toggle source
# File lib/vines/storage/sql.rb, line 243
def fragment_by_jid(jid, node)
  jid = JID.new(jid).bare.to_s
  clause = 'user_id=(select id from users where jid=?) and root=? and namespace=?'
  Sql::Fragment.where(clause, jid, node.name, node.namespace.href).first
end
groups(contact) click to toggle source
# File lib/vines/storage/sql.rb, line 249
def groups(contact)
  contact.groups.map {|name| Sql::Group.find_or_create_by_name(name.strip) }
end
offline_messages_to_jid(jid) click to toggle source
# File lib/vines/storage/sql.rb, line 253
def offline_messages_to_jid(jid)
  jid = JID.new(jid).bare.to_s
  Sql::OfflineMessage.find_all_by_to(jid)
end
user_by_jid(jid) click to toggle source
# File lib/vines/storage/sql.rb, line 238
def user_by_jid(jid)
  jid = JID.new(jid).bare.to_s
  Sql::User.find_by_jid(jid, :include => {:contacts => :groups})
end