class FDB::DirectoryLayer

def check_version(tr, write_access)

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

def contents_of_node(node, path, layer='')

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

def convert_path_element(name)

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

def create(db_or_tr, path, options={})

def create(db_or_tr, path, options={})
  create_or_open_internal(db_or_tr, path, true, false, options)
end

def create_directory(tr, path, options)

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

def create_or_open(db_or_tr, path, options={})

def create_or_open(db_or_tr, path, options={})
  create_or_open_internal(db_or_tr, path, true, true, options)
end

def create_or_open_internal(db_or_tr, path, allow_create, allow_open, options={})

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

def exists?(db_or_tr, path=[])

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

def find(tr, path)

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

def initialize(options={})

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

def initialize_directory(tr)

def initialize_directory(tr)
  tr[@root_node['version']] = @@VERSION.pack('III<')
end

def is_prefix_free?(tr, prefix)

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

def layer

def layer
  return @layer.dup
end

def list(db_or_tr, path=[])

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

def move(db_or_tr, old_path, new_path)

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

def move_to(db_or_tr, new_absolute_path)

def move_to(db_or_tr, new_absolute_path)
  raise 'The root directory cannot be moved.'
end

def node_containing_key(tr, key)

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

def node_with_prefix(prefix)

def node_with_prefix(prefix)
  @node_subspace[prefix] if !prefix.nil?
end

def open(db_or_tr, path, options={})

def open(db_or_tr, path, options={})
  create_or_open_internal(db_or_tr, path, false, true, options)
end

def open_directory(path, options, existing_node)

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

def path

def path
  return @path.dup
end

def remove(db_or_tr, path=[])

def remove(db_or_tr, path=[])
  remove_internal(db_or_tr, path, true)
end

def remove_from_parent(tr, path)

def remove_from_parent(tr, path)
  parent = find(tr, path[0...-1])
  tr.clear(parent.subspace[@@SUBDIRS][path[-1]])
end

def remove_if_exists(db_or_tr, path=[])

def remove_if_exists(db_or_tr, path=[])
  remove_internal(db_or_tr, path, false)
end

def remove_internal(db_or_tr, path, fail_on_nonexistent)

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

def remove_recursive(tr, node)

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

def subdir_names_and_nodes(tr, node)

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

def to_unicode_path(path)

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