Class: Ole::Storage::Dirent

Inherits:
Struct
  • Object
show all
Includes:
RecursivelyEnumerable
Defined in:
lib/ole/storage/base.rb

Overview

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.

Constant Summary collapse

PACK =
'a64 v C C V3 a16 V a8 a8 V2 a4'
SIZE =
128
TYPE_MAP =
{
	# this is temporary
	0 => :empty,
	1 => :dir,
	2 => :file,
	5 => :root
}
COLOUR_MAP =

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

{
	0 => :red,
	1 => :black
}
EOT =

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

0xffffffff
DEFAULT =
[
	0.chr * 2, 2, 0, # will get overwritten
	1, EOT, EOT, EOT,
	0.chr * 16, 0, nil, nil,
	AllocationTable::EOC, 0, 0.chr * 4
]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from RecursivelyEnumerable

#each_recursive, #each_recursive_breadth_first, #each_recursive_depth_first, #recursive, #to_tree

Constructor Details

#initialize(ole, values = DEFAULT, params = {}) ⇒ Dirent

Returns a new instance of Dirent.



738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
# File 'lib/ole/storage/base.rb', line 738

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

Instance Attribute Details

#childObject

Returns the value of attribute child

Returns:

  • (Object)

    the current value of child



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def child
  @child
end

#childrenObject

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



725
726
727
# File 'lib/ole/storage/base.rb', line 725

def children
  @children
end

#clsidObject

Returns the value of attribute clsid

Returns:

  • (Object)

    the current value of clsid



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def clsid
  @clsid
end

#colourObject

Returns the value of attribute colour

Returns:

  • (Object)

    the current value of colour



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def colour
  @colour
end

#create_timeObject (readonly)

Returns the value of attribute create_time.



727
728
729
# File 'lib/ole/storage/base.rb', line 727

def create_time
  @create_time
end

#create_time_strObject

Returns the value of attribute create_time_str

Returns:

  • (Object)

    the current value of create_time_str



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def create_time_str
  @create_time_str
end

#first_blockObject

Returns the value of attribute first_block

Returns:

  • (Object)

    the current value of first_block



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def first_block
  @first_block
end

#flagsObject

Returns the value of attribute flags

Returns:

  • (Object)

    the current value of flags



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def flags
  @flags
end

#idxObject

i think its just used by the tree building



731
732
733
# File 'lib/ole/storage/base.rb', line 731

def idx
  @idx
end

#modify_timeObject (readonly)

Returns the value of attribute modify_time.



727
728
729
# File 'lib/ole/storage/base.rb', line 727

def modify_time
  @modify_time
end

#modify_time_strObject

Returns the value of attribute modify_time_str

Returns:

  • (Object)

    the current value of modify_time_str



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def modify_time_str
  @modify_time_str
end

#nameObject

Returns the value of attribute name.



726
727
728
# File 'lib/ole/storage/base.rb', line 726

def name
  @name
end

#name_lenObject

Returns the value of attribute name_len

Returns:

  • (Object)

    the current value of name_len



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def name_len
  @name_len
end

#name_utf16Object

Returns the value of attribute name_utf16

Returns:

  • (Object)

    the current value of name_utf16



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def name_utf16
  @name_utf16
end

#nextObject

Returns the value of attribute next

Returns:

  • (Object)

    the current value of next



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def next
  @next
end

#oleObject (readonly)

Returns the value of attribute ole.



727
728
729
# File 'lib/ole/storage/base.rb', line 727

def ole
  @ole
end

#parentObject

Returns the value of attribute parent.



728
729
730
# File 'lib/ole/storage/base.rb', line 728

def parent
  @parent
end

#prevObject

Returns the value of attribute prev

Returns:

  • (Object)

    the current value of prev



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def prev
  @prev
end

#reservedObject

Returns the value of attribute reserved

Returns:

  • (Object)

    the current value of reserved



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def reserved
  @reserved
end

#sizeObject

Returns the value of attribute size

Returns:

  • (Object)

    the current value of size



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def size
  @size
end

#typeObject (readonly)

Returns the value of attribute type.



727
728
729
# File 'lib/ole/storage/base.rb', line 727

def type
  @type
end

#type_idObject

Returns the value of attribute type_id

Returns:

  • (Object)

    the current value of type_id



691
692
693
# File 'lib/ole/storage/base.rb', line 691

def type_id
  @type_id
end

Class Method Details

.copy(src, dst) ⇒ Object

Raises:

  • (ArgumentError)


928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
# File 'lib/ole/storage/base.rb', line 928

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) ⇒ Object

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.



860
861
862
863
864
865
866
# File 'lib/ole/storage/base.rb', line 860

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

Instance Method Details

#/(name) ⇒ Object

maybe need some options regarding case sensitivity.



819
820
821
# File 'lib/ole/storage/base.rb', line 819

def / name
	@name_lookup[name]
end

#<<(child) ⇒ Object



909
910
911
912
913
# File 'lib/ole/storage/base.rb', line 909

def << child
	child.parent = self
	@name_lookup[child.name] = child
	@children << child
end

#[](idx) ⇒ Object



823
824
825
826
827
828
829
830
# File 'lib/ole/storage/base.rb', line 823

def [] idx
	if String === idx
		#warn 'String form of Dirent#[] is deprecated'
		self / idx
	else
		super
	end
end

#delete(child, truncate = true) ⇒ Object

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



917
918
919
920
921
922
923
924
925
926
# File 'lib/ole/storage/base.rb', line 917

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?Boolean

Returns:

  • (Boolean)


813
814
815
816
# File 'lib/ole/storage/base.rb', line 813

def dir?
	# to count root as a dir.
	!file?
end

#each_child(&block) ⇒ Object



838
839
840
# File 'lib/ole/storage/base.rb', line 838

def each_child(&block)
	@children.each(&block)
end

#file?Boolean

Returns:

  • (Boolean)


809
810
811
# File 'lib/ole/storage/base.rb', line 809

def file?
	type == :file
end

#flatten(dirents = []) ⇒ Object

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



843
844
845
846
847
848
849
850
851
852
853
# File 'lib/ole/storage/base.rb', line 843

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

#inspectObject



894
895
896
897
898
899
900
901
902
903
904
905
906
907
# File 'lib/ole/storage/base.rb', line 894

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

#open(mode = 'r') ⇒ Object

Raises:

  • (Errno::EISDIR)


793
794
795
796
797
798
799
800
801
802
803
# File 'lib/ole/storage/base.rb', line 793

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) ⇒ Object



805
806
807
# File 'lib/ole/storage/base.rb', line 805

def read limit=nil
	open { |io| io.read limit }
end

#timeObject

move to ruby-msg. and remove from here



833
834
835
836
# File 'lib/ole/storage/base.rb', line 833

def time
	#warn 'Dirent#time is deprecated'
	create_time || modify_time
end

#to_sObject



868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
# File 'lib/ole/storage/base.rb', line 868

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