Parent

Class/Module Index [+]

Quicksearch

Rufus::Cloche

A cloche is a local JSON hashes store.

Warning : cloches are process-safe but not thread-safe.

Constants

VERSION
WIN

Attributes

dir[R]

Public Class Methods

new(opts={}) click to toggle source

Creates a new 'cloche'.

There are 2 options :

  • :dir : to specify the directory into which the cloche data is store

  • :nolock : when set to true, no flock is used

On the Windows platform, :nolock is set to true automatically.

With JRuby 1.4.0, :nolock=true seems necessary on Ubuntu. While flock seems to work on MacOSX Snow Leopard.

# File lib/rufus/cloche.rb, line 64
def initialize (opts={})

  @dir = File.expand_path(opts[:dir] || 'cloche')
  @mutex = Mutex.new

  @nolock = WIN || opts[:nolock]
end

Protected Class Methods

neutralize(s) click to toggle source
# File lib/rufus/cloche.rb, line 248
def self.neutralize (s)

  s.to_s.strip.gsub(/[ \/:;\*\\\+\?]/, '_')
end

Public Instance Methods

delete(doc) click to toggle source

Attempts at deleting a document. You have to pass the current version or at least the { '_id' => i, 'type' => t, '_rev' => r }.

Will return nil if the deletion is successful.

If the deletion failed because the given doc has an older revision number that the one currently stored, the doc in its freshest version will be returned.

Returns true if the deletion failed.

# File lib/rufus/cloche.rb, line 137
def delete (doc)

  drev = doc['_rev']

  raise ArgumentError.new('cannot delete doc without _rev') unless drev

  type, key = doc['type'], doc['_id']

  r = lock(false, type, key) do |f|

    cur = do_get(f)

    return nil unless cur
    return cur if cur['_rev'] != drev

    begin
      f.close
      File.delete(f.path)
      nil
    rescue Exception => e
      #p e
      false
    end
  end

  r == false ? true : nil
end
get(type, key) click to toggle source

Gets a document (or nil if not found (or corrupted)).

# File lib/rufus/cloche.rb, line 119
def get (type, key)

  r = lock(false, type, key) { |f| do_get(f) }

  r == false ? nil : r
end
get_many(type, key_match=nil, opts={}) click to toggle source

Given a type, this method will return an array of all the documents for that type.

A optional second parameter may be used to select, based on a regular expression, which documents to include (match on the key '_id').

Will return an empty Hash if there is no documents for a given type.

opts

The only option know for now is :limit, which limits the number of documents returned.

# File lib/rufus/cloche.rb, line 178
def get_many (type, key_match=nil, opts={})

  opts = opts.inject({}) { |h, (k, v)| h[k.to_s] = v; h }

  d = dir_for(type)

  return [] unless File.exist?(d)

  docs = []
  limit = opts['limit']

  files = Dir[File.join(d, '**', '*.json')].sort { |p0, p1|
    File.basename(p0) <=> File.basename(p1)
  }

  files.each do |fn|

    key = File.basename(fn, '.json')

    if (not key_match) || key.match(key_match)

      doc = get(type, key)
      docs << doc if doc

      break if limit && (docs.size >= limit)
    end
  end

  # WARNING : there is a twist here, the filenames may have a different
  #           sort order from actual _ids...

  #docs.sort { |doc0, doc1| doc0['_id'] <=> doc1['_id'] }
    # let's trust filename order

  docs
end
ids(type) click to toggle source

Returns a sorted list of all the ids for a given type of documents.

Warning : trusts the ids to be identical to the filenames

# File lib/rufus/cloche.rb, line 226
def ids (type)

  Dir[File.join(dir_for(type), '**', '*.json')].collect { |p|
    File.basename(p, '.json')
  }.sort
end
purge_type!(type) click to toggle source

Removes entirely documents of a given type.

# File lib/rufus/cloche.rb, line 217
def purge_type! (type)

  FileUtils.rm_rf(dir_for(type))
end
put(doc, opts={}) click to toggle source

Puts a document (Hash) under the cloche.

If the document is brand new, it will be given a revision number '_rev' of 0.

If the document already exists in the cloche and the version to put has an older (different) revision number than the one currently stored, put will fail and return the current version of the doc.

If the put is successful, nil is returned.

# File lib/rufus/cloche.rb, line 83
def put (doc, opts={})

  opts = opts.inject({}) { |h, (k, v)| h[k.to_s] = v; h }

  doc = Rufus::Json.dup(doc) unless opts['update_rev']
    # work with a copy, don't touch original

  type, key = doc['type'], doc['_id']

  raise(
    ArgumentError.new("missing values for keys 'type' and/or '_id'")
  ) if type.nil? || key.nil?

  rev = (doc['_rev'] ||= -1)

  raise(
    ArgumentError.new("values for '_rev' must be positive integers")
  ) if rev.class != Fixnum && rev.class != Bignum

  lock(true, type, key) do |file|

    cur = do_get(file)

    return cur if cur && cur['_rev'] != doc['_rev']
    return true if cur.nil? && doc['_rev'] != -1

    doc['_rev'] = doc['_rev'] + 1

    File.open(file.path, 'wb') { |io| io.write(Rufus::Json.encode(doc)) }
  end

  nil
end
real_ids(type) click to toggle source

Returns a sorted list of all the ids for a given type of documents.

Actually reads each file and returns the real _id list

# File lib/rufus/cloche.rb, line 237
def real_ids (type)

  Dir[File.join(dir_for(type), '**', '*.json')].inject([]) { |a, p|
    doc = do_get(p)
    a << doc['_id'] if doc
    a
  }.sort
end

Protected Instance Methods

dir_for(type) click to toggle source
# File lib/rufus/cloche.rb, line 259
def dir_for (type)

  File.join(@dir, Cloche.neutralize(type || 'no_type'))
end
do_get(file) click to toggle source
# File lib/rufus/cloche.rb, line 253
def do_get (file)

  s = file.is_a?(File) ? file.read : File.read(file)
  Rufus::Json.decode(s) rescue nil
end
lock(create, type, key, &block) click to toggle source
# File lib/rufus/cloche.rb, line 273
def lock (create, type, key, &block)

  @mutex.synchronize do
    begin

      d, f = path_for(type, key)
      fn = File.join(d, f)

      if create && ( ! File.exist?(fn))
        FileUtils.mkdir_p(d) unless File.exist?(d)
        FileUtils.touch(fn) unless File.exist?(fn)
      end

      file = File.new(fn) rescue nil

      return false if file.nil?

      file.flock(File::LOCK_EX) unless @nolock
      block.call(file)

    ensure
      begin
        file.flock(File::LOCK_UN) unless @nolock
      rescue Exception => e
        #p [ :lock, @fpath, e ]
        #e.backtrace.each { |l| puts l }
      end
      begin
        file.close if file
      rescue Exception => e
        #p [ :close_fail, e ]
      end
    end
  end
end
path_for(type, key) click to toggle source
# File lib/rufus/cloche.rb, line 264
def path_for (type, key)

  nkey = Cloche.neutralize(key)

  subdir = (nkey[-2, 2] || nkey).gsub(/\./, 'Z')

  [ File.join(dir_for(type), subdir), "#{nkey}.json" ]
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.