class Ole::Storage::Dirent

A class which wraps an ole directory entry. Can be either a directory (Dirent#dir?) or a file (Dirent#file?)

Most interaction with Ole::Storage is through this class. The 2 most important functions are Dirent#children, and Dirent#data.

was considering separate classes for dirs and files. some methods/attrs only applicable to one or the other.

As with the other classes, to_s performs the serialization.

Constants

COLOUR_MAP

something to do with the fact that the tree is supposed to be red-black

DEFAULT
EOT

used in the next / prev / child stuff to show that the tree ends here. also used for first_block for directory.

PACK
SIZE
TYPE_MAP

Attributes

children[R]

This returns all the children of this Dirent. It is filled in when the tree structure is recreated.

create_time[R]
idx[RW]

i think its just used by the tree building

modify_time[R]
name[R]
name_lookup[R]

these are for internal use and are used for faster lookup.

ole[R]
parent[RW]
type[R]

Public Class Methods

copy(src, dst) click to toggle source
# File lib/ole/storage/base.rb, line 925
def self.copy src, dst
  # copies the contents of src to dst. must be the same type. this will throw an
  # error on copying to root. maybe this will recurse too much for big documents??
  raise ArgumentError, 'differing types' if src.file? and !dst.file?
  dst.name = src.name
  if src.dir?
    src.children.each do |src_child|
      dst_child = Dirent.new dst.ole, :type => src_child.type
      dst << dst_child
      Dirent.copy src_child, dst_child
    end
  else
    src.open do |src_io|
      dst.open { |dst_io| IO.copy src_io, dst_io }
    end
  end
end
flatten_helper(children) click to toggle source

i think making the tree structure optimized is actually more complex than this, and requires some intelligent ordering of the children based on names, but as long as it is valid its ok. actually, i think its ok. gsf for example only outputs a singly-linked-list, where prev is always EOT.

# File lib/ole/storage/base.rb, line 857
def self.flatten_helper children
  return EOT if children.empty?
  i = children.length / 2
  this = children[i]
  this.prev, this.next = [(0...i), (i+1..-1)].map { |r| flatten_helper children[r] }
  this.idx
end
new(ole, values=DEFAULT, params={}) click to toggle source
Calls superclass method
# File lib/ole/storage/base.rb, line 740
def initialize ole, values=DEFAULT, params={}
  @ole = ole        
  values, params = DEFAULT, values if Hash === values
  values = values.unpack(PACK) if String === values
  super(*values)

  # extra parsing from the actual struct values
  @name = params[:name] || Types::Variant.load(Types::VT_LPWSTR, name_utf16[0...name_len])
  @type = if params[:type]
    unless TYPE_MAP.values.include?(params[:type])
      raise ArgumentError, "unknown type #{params[:type].inspect}"
    end
    params[:type]
  else
    TYPE_MAP[type_id] or raise FormatError, "unknown type_id #{type_id.inspect}"
  end

  # further extra type specific stuff
  if file?
    default_time = @ole.params[:update_timestamps] ? Types::FileTime.now : nil
    @create_time ||= default_time
    @modify_time ||= default_time
    @create_time = Types::Variant.load(Types::VT_FILETIME, create_time_str) if create_time_str
    @modify_time = Types::Variant.load(Types::VT_FILETIME, create_time_str) if modify_time_str
    @children = nil
    @name_lookup = nil
  else
    @create_time = nil
    @modify_time = nil
    self.size = 0 unless @type == :root
    @children = []
    @name_lookup = {}
  end

  @parent = nil
  
  # to silence warnings. used for tree building at load time
  # only.
  @idx = nil
end

Public Instance Methods

/(name) click to toggle source

maybe need some options regarding case sensitivity.

# File lib/ole/storage/base.rb, line 816
def / name
  @name_lookup[name]
end
<<(child) click to toggle source
# File lib/ole/storage/base.rb, line 906
def << child
  child.parent = self
  @name_lookup[child.name] = child
  @children << child
end
[](idx) click to toggle source
Calls superclass method
# File lib/ole/storage/base.rb, line 820
def [] idx
  if String === idx
    #warn 'String form of Dirent#[] is deprecated'
    self / idx
  else
    super
  end
end
delete(child, truncate=true) click to toggle source

remove the Dirent child from the children array, truncating the data by default.

# File lib/ole/storage/base.rb, line 914
def delete child, truncate=true
  # remove from our child array, so that on reflatten and re-creation of @dirents, it will be gone
  unless @children.delete(child)
    raise ArgumentError, "#{child.inspect} not a child of #{self.inspect}"
  end
  @name_lookup.delete(child.name)
  child.parent = nil
  # free our blocks
  child.open { |io| io.truncate 0 } if child.file?
end
dir?() click to toggle source
# File lib/ole/storage/base.rb, line 810
def dir?
  # to count root as a dir.
  !file?
end
each_child(&block) click to toggle source
# File lib/ole/storage/base.rb, line 835
def each_child(&block)
  @children.each(&block) if dir?
end
file?() click to toggle source
# File lib/ole/storage/base.rb, line 806
def file?
  type == :file
end
flatten(dirents=[]) click to toggle source

flattens the tree starting from here into dirents. note it modifies its argument.

# File lib/ole/storage/base.rb, line 840
def flatten dirents=[]
  @idx = dirents.length
  dirents << self
  if file?
    self.prev = self.next = self.child = EOT
  else
    children.each { |child| child.flatten dirents } 
    self.child = Dirent.flatten_helper children
  end
  dirents
end
inspect() click to toggle source
# File lib/ole/storage/base.rb, line 891
def inspect
  str = "#<Dirent:#{name.inspect}"
  # perhaps i should remove the data snippet. its not that useful anymore.
  # there is also some dir specific stuff. like clsid, flags, that i should
  # probably include
  if file?
    tmp = read 9
    data = tmp.length == 9 ? tmp[0, 5] + '...' : tmp
    str << " size=#{size}" +
      "#{modify_time ? ' modify_time=' + modify_time.to_s.inspect : nil}" +
      " data=#{data.inspect}"
  end
  str + '>'
end
name=(name) click to toggle source
# File lib/ole/storage/base.rb, line 781
def name= name
  if @parent
    map = @parent.instance_variable_get :@name_lookup
    map.delete @name
    map[name] = self
  end
  @name = name
end
open(mode='r') { |io| ... } click to toggle source
# File lib/ole/storage/base.rb, line 790
def open mode='r'
  raise Errno::EISDIR unless file?
  io = RangesIOMigrateable.new self, mode
  @modify_time = Types::FileTime.now if io.mode.writeable?
  if block_given?
    begin   yield io
    ensure; io.close
    end
  else io
  end
end
read(limit=nil) click to toggle source
# File lib/ole/storage/base.rb, line 802
def read limit=nil
  open { |io| io.read limit }
end
time() click to toggle source

move to ruby-msg. and remove from here

# File lib/ole/storage/base.rb, line 830
def time
  #warn 'Dirent#time is deprecated'
  create_time || modify_time
end
to_s() click to toggle source
# File lib/ole/storage/base.rb, line 865
def to_s
  tmp = Types::Variant.dump(Types::VT_LPWSTR, name)
  tmp = tmp[0, 62] if tmp.length > 62
  tmp += 0.chr * 2
  self.name_len = tmp.length
  self.name_utf16 = tmp + 0.chr * (64 - tmp.length)
  # type_id can perhaps be set in the initializer, as its read only now.
  self.type_id = TYPE_MAP.to_a.find { |id, name| @type == name }.first
  # for the case of files, it is assumed that that was handled already
  # note not dir?, so as not to override root's first_block
  self.first_block = Dirent::EOT if type == :dir
  if file?
    # this is messed up. it changes the time stamps regardless of whether the file
    # was actually touched. instead, any open call with a writeable mode, should update
    # the modify time. create time would be set in new.
    if @ole.params[:update_timestamps]
      self.create_time_str = Types::Variant.dump Types::VT_FILETIME, @create_time
      self.modify_time_str = Types::Variant.dump Types::VT_FILETIME, @modify_time
    end
  else
    self.create_time_str = 0.chr * 8
    self.modify_time_str = 0.chr * 8
  end
  to_a.pack PACK
end