class FDB::DirectoryLayer

Attributes

path[W]

Public Class Methods

new(options={}) click to toggle source
# File lib/fdbdirectory.rb, line 146
def initialize(options={})
  defaults = { :node_subspace => Subspace.new([], "\xfe"), 
               :content_subspace =>Subspace.new, 
               :allow_manual_prefixes => false }

  options = defaults.merge(options)

  @content_subspace = options[:content_subspace]
  @node_subspace = options[:node_subspace]
  @allow_manual_prefixes = options[:allow_manual_prefixes]

  @root_node = @node_subspace[@node_subspace.key]
  @allocator = HighContentionAllocator.new(@root_node['hca'])

  @path = []
  @layer = ''
end

Public Instance Methods

create(db_or_tr, path, options={}) click to toggle source
# File lib/fdbdirectory.rb, line 183
def create(db_or_tr, path, options={})
  create_or_open_internal(db_or_tr, path, true, false, options)
end
create_or_open(db_or_tr, path, options={}) click to toggle source
# File lib/fdbdirectory.rb, line 175
def create_or_open(db_or_tr, path, options={})
  create_or_open_internal(db_or_tr, path, true, true, options)
end
exists?(db_or_tr, path=[]) click to toggle source
# File lib/fdbdirectory.rb, line 264
def exists?(db_or_tr, path=[])
  db_or_tr.transact do |tr|
    check_version(tr, false)

    path = to_unicode_path(path)
    node = find(tr, path).prefetch_metadata(tr)

    next false if !node.exists?

    if node.is_in_partition?
      next node.get_contents(self).exists?(tr, node.get_partition_subpath)
    end

    true
  end
end
layer() click to toggle source
# File lib/fdbdirectory.rb, line 168
def layer
  return @layer.dup
end
list(db_or_tr, path=[]) click to toggle source
# File lib/fdbdirectory.rb, line 247
def list(db_or_tr, path=[])
  db_or_tr.transact do |tr|
    check_version(tr, false)

    path = to_unicode_path(path)
    node = find(tr, path).prefetch_metadata(tr)

    raise ArgumentError, 'The directory does not exist.' unless node.exists?

    if node.is_in_partition?(nil, true)
      next node.get_contents(self).list(tr, node.get_partition_subpath)
    end

    subdir_names_and_nodes(tr, node.subspace).map { |name, node| name }
  end
end
move(db_or_tr, old_path, new_path) click to toggle source
# File lib/fdbdirectory.rb, line 191
def move(db_or_tr, old_path, new_path)
  db_or_tr.transact do |tr|
    check_version(tr, true)

    old_path = to_unicode_path(old_path)
    new_path = to_unicode_path(new_path)

    if old_path == new_path[0...old_path.length]
      raise ArgumentError, 
        'The desination directory cannot be a subdirectory of the source directory.'
    end

    old_node = find(tr, old_path).prefetch_metadata(tr)
    new_node = find(tr, new_path).prefetch_metadata(tr)

    raise ArgumentError, 'The source directory does not exist.' unless old_node.exists?

    if old_node.is_in_partition? || new_node.is_in_partition?
      if !old_node.is_in_partition? || 
        !new_node.is_in_partition? || 
        old_node.path != new_node.path
      then
        raise ArgumentError, 'Cannot move between partitions'
      end

      next new_node
        .get_contents(self)
        .move(tr, old_node.get_partition_subpath, new_node.get_partition_subpath)
    end

    if new_node.exists?
      raise ArgumentError, 'The destination directory already exists. Remove it first.'
    end

    parent_node = find(tr, new_path[0...-1])
    if !parent_node.exists?
      raise ArgumentError, 
        'The parent directory of the destination directory does not exist. Create it first.'
    end
    
    tr[parent_node.subspace[@@SUBDIRS][new_path[-1]]] = 
      @node_subspace.unpack(old_node.subspace.key)[0]
    remove_from_parent(tr, old_path)

    contents_of_node(old_node.subspace, new_path, old_node.layer)
  end
end
move_to(db_or_tr, new_absolute_path) click to toggle source
# File lib/fdbdirectory.rb, line 187
def move_to(db_or_tr, new_absolute_path)
  raise 'The root directory cannot be moved.'
end
open(db_or_tr, path, options={}) click to toggle source
# File lib/fdbdirectory.rb, line 179
def open(db_or_tr, path, options={})
  create_or_open_internal(db_or_tr, path, false, true, options)
end
path() click to toggle source
# File lib/fdbdirectory.rb, line 164
def path
  return @path.dup
end
remove(db_or_tr, path=[]) click to toggle source
# File lib/fdbdirectory.rb, line 239
def remove(db_or_tr, path=[])
  remove_internal(db_or_tr, path, true)
end
remove_if_exists(db_or_tr, path=[]) click to toggle source
# File lib/fdbdirectory.rb, line 243
def remove_if_exists(db_or_tr, path=[])
  remove_internal(db_or_tr, path, false)
end

Protected Instance Methods

create_directory(tr, path, options) click to toggle source
# File lib/fdbdirectory.rb, line 328
def create_directory(tr, path, options)
  check_version(tr, true)

  prefix = options[:prefix]
  if prefix.nil?
    prefix = @content_subspace.key + @allocator.allocate(tr)
    if !tr.get_range_start_with(prefix, { :limit => 1 }).to_a.empty?
      raise "The database has keys stored at the prefix chosen by the automatic prefix allocator: #{prefix.dump}."
    end

    if !is_prefix_free?(tr.snapshot, prefix)
      raise 'The directory layer has manually allocated prefixes that conflict with the automatic prefix allocator.'
    end
  elsif !is_prefix_free?(tr, prefix)
    raise ArgumentError, 'The given prefix is already in use.'
  end

  parent_node = if path[0...-1].length > 0
                  node_with_prefix(create_or_open(tr, path[0...-1]).key)
                else
                  @root_node
                end

  raise 'The parent directory does not exist.' unless parent_node

  node = node_with_prefix(prefix)
  tr[parent_node[@@SUBDIRS][path[-1]]] = prefix
  tr[node['layer']] = options[:layer]

  contents_of_node(node, path, options[:layer])
end
create_or_open_internal(db_or_tr, path, allow_create, allow_open, options={}) click to toggle source
# File lib/fdbdirectory.rb, line 283
def create_or_open_internal(db_or_tr, path, allow_create, allow_open, options={})
  defaults = { :layer => '', :prefix => nil }
  options = defaults.merge(options)

  if !options[:prefix].nil? and allow_open and allow_create
    raise ArgumentError, 'Cannot specify a prefix when calling create_or_open.'
  end

  if !options[:prefix].nil? and !@allow_manual_prefixes
    if @path.length == 0
      raise ArgumentError, 'Cannot specify a prefix unless manual prefixes are enabled.'
    else
      raise ArgumentError, 'Cannot specify a prefix in a partition.'
    end
  end

  db_or_tr.transact do |tr|
    check_version(tr, false)
    path = to_unicode_path(path)

    raise ArgumentError, 'The root directory cannot be opened.' if path.length == 0

    existing_node = find(tr, path).prefetch_metadata(tr)
    
    if existing_node.exists?
      if existing_node.is_in_partition?
        subpath = existing_node.get_partition_subpath
        existing_node.get_contents(self).directory_layer.create_or_open_internal(tr, subpath, allow_create, allow_open, options)
      else
        raise ArgumentError, 'The directory already exists.' unless allow_open
        open_directory(path, options, existing_node)
      end
    else
      raise ArgumentError, 'The directory does not exist.' unless allow_create
      create_directory(tr, path, options)
    end
  end
end
open_directory(path, options, existing_node) click to toggle source
# File lib/fdbdirectory.rb, line 321
def open_directory(path, options, existing_node)
  if options[:layer] and !options[:layer].empty? and options[:layer] != existing_node.layer
    raise 'The directory was created with an incompatible layer.'
  end
  existing_node.get_contents(self)
end
remove_internal(db_or_tr, path, fail_on_nonexistent) click to toggle source
# File lib/fdbdirectory.rb, line 360
def remove_internal(db_or_tr, path, fail_on_nonexistent)
  db_or_tr.transact do |tr|
    check_version(tr, true)

    path = to_unicode_path(path)

    if path.empty?
      raise ArgumentError, 'The root directory cannot be removed.'
    end

    node = find(tr, path).prefetch_metadata(tr)

    if !node.exists?
      raise ArgumentError, 'The directory does not exist.' if fail_on_nonexistent
      next false
    end

    if node.is_in_partition?
      next node.get_contents(self).directory_layer
                  .remove_internal(tr, node.get_partition_subpath, fail_on_nonexistent)
    end

    remove_recursive(tr, node.subspace)
    remove_from_parent(tr, path)
    true
  end
end

Private Instance Methods

check_version(tr, write_access) click to toggle source
# File lib/fdbdirectory.rb, line 390
def check_version(tr, write_access)
  version = tr[@root_node['version']]

  initialize_directory(tr) if !version && write_access
  return if !version

  version = version.to_s.unpack('III<')
  
  dir_ver = "#{version[0]}.#{version[1]}.#{version[2]}"
  layer_ver = "#{@@VERSION[0]}.#{@@VERSION[1]}.#{@@VERSION[2]}"

  if version[0] != @@VERSION[0]
    raise "Cannot load directory with version #{dir_ver} using directory layer #{layer_ver}"
  elsif version[1] != @@VERSION[1] && write_access
    raise "Directory with version #{dir_ver} is read-only 
          when opened using directory layer #{layer_ver}"
  end
end
contents_of_node(node, path, layer='') click to toggle source
# File lib/fdbdirectory.rb, line 429
def contents_of_node(node, path, layer='')
  prefix = @node_subspace.unpack(node.key)[0]
  if layer == 'partition'
    DirectoryPartition.new(@path + path, prefix, self)
  else
    DirectorySubspace.new(@path + path, prefix, self, layer)
  end
end
convert_path_element(name) click to toggle source
# File lib/fdbdirectory.rb, line 479
def convert_path_element(name)
  if !name.kind_of? String
    raise TypeError, 'Invalid path: must be a unicode string or an array of unicode strings'
  end
  name.dup.force_encoding('UTF-8')
end
find(tr, path) click to toggle source
# File lib/fdbdirectory.rb, line 438
def find(tr, path)
  node = Internal::Node.new(@root_node, [], path)
  path.each_with_index do |name, index|
    node = Internal::Node.new(node_with_prefix(tr[node.subspace[@@SUBDIRS][name]]), 
                              path[0..index], path)

    return node unless node.exists? and node.layer(tr) != 'partition'
  end

  node
end
initialize_directory(tr) click to toggle source
# File lib/fdbdirectory.rb, line 409
def initialize_directory(tr)
  tr[@root_node['version']] = @@VERSION.pack('III<')
end
is_prefix_free?(tr, prefix) click to toggle source
# File lib/fdbdirectory.rb, line 471
def is_prefix_free?(tr, prefix)
  prefix && 
    prefix.length > 0 &&
    !node_containing_key(tr, prefix) && 
    tr.get_range(@node_subspace.pack([prefix]), @node_subspace.pack([FDB.strinc(prefix)]), 
                 { :limit => 1 }).to_a.empty?
end
node_containing_key(tr, key) click to toggle source
# File lib/fdbdirectory.rb, line 413
def node_containing_key(tr, key)
  return @root_node if key.start_with?(@node_subspace.key)

  tr.get_range(@node_subspace.range[0], 
               @node_subspace.pack([key]) + "\x00", 
               { :reverse => true, :limit => 1})
  .map { |kv|
    prev_prefix = @node_subspace.unpack(kv.key)[0]
    node_with_prefix(prev_prefix) if key.start_with?(prev_prefix)
  }[0]
end
node_with_prefix(prefix) click to toggle source
# File lib/fdbdirectory.rb, line 425
def node_with_prefix(prefix)
  @node_subspace[prefix] if !prefix.nil?
end
remove_from_parent(tr, path) click to toggle source
# File lib/fdbdirectory.rb, line 457
def remove_from_parent(tr, path)
  parent = find(tr, path[0...-1])
  tr.clear(parent.subspace[@@SUBDIRS][path[-1]])
end
remove_recursive(tr, node) click to toggle source
# File lib/fdbdirectory.rb, line 462
def remove_recursive(tr, node)
  subdir_names_and_nodes(tr, node).each do |name, subnode|
    remove_recursive(tr, subnode)
  end

  tr.clear_range_start_with(@node_subspace.unpack(node.key)[0])
  tr.clear_range(node.range[0], node.range[1])
end
subdir_names_and_nodes(tr, node) click to toggle source
# File lib/fdbdirectory.rb, line 450
def subdir_names_and_nodes(tr, node)
  subdir = node[@@SUBDIRS]
  tr.get_range(subdir.range[0], subdir.range[1]).map { |kv|
    [subdir.unpack(kv.key)[0], node_with_prefix(kv.value)]
  }
end
to_unicode_path(path) click to toggle source
# File lib/fdbdirectory.rb, line 486
def to_unicode_path(path)
  if path.respond_to? 'each_with_index'
    path.each_with_index { |name, index| path[index] = convert_path_element(name) }
  else
    [convert_path_element(path)]
  end
end