Class: Ole::Storage::Dirent

Inherits:
Struct
  • Object
show all
Includes:
RecursivelyEnumerable
Defined in:
lib/ole/storage.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.



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
778
779
780
781
782
783
784
785
786
787
788
# File 'lib/ole/storage.rb', line 753

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] ? Time.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
	else
		@create_time = nil
		@modify_time = nil
		self.size = 0 unless @type == :root
		@children = []
	end
	
	# 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



714
715
716
# File 'lib/ole/storage.rb', line 714

def child
  @child
end

#childrenObject

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



750
751
752
# File 'lib/ole/storage.rb', line 750

def children
  @children
end

#clsidObject

Returns the value of attribute clsid

Returns:

  • (Object)

    the current value of clsid



714
715
716
# File 'lib/ole/storage.rb', line 714

def clsid
  @clsid
end

#colourObject

Returns the value of attribute colour

Returns:

  • (Object)

    the current value of colour



714
715
716
# File 'lib/ole/storage.rb', line 714

def colour
  @colour
end

#create_timeObject (readonly)

Returns the value of attribute create_time.



752
753
754
# File 'lib/ole/storage.rb', line 752

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



714
715
716
# File 'lib/ole/storage.rb', line 714

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



714
715
716
# File 'lib/ole/storage.rb', line 714

def first_block
  @first_block
end

#flagsObject

Returns the value of attribute flags

Returns:

  • (Object)

    the current value of flags



714
715
716
# File 'lib/ole/storage.rb', line 714

def flags
  @flags
end

#idxObject

i think its just used by the tree building



747
748
749
# File 'lib/ole/storage.rb', line 747

def idx
  @idx
end

#modify_timeObject (readonly)

Returns the value of attribute modify_time.



752
753
754
# File 'lib/ole/storage.rb', line 752

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



714
715
716
# File 'lib/ole/storage.rb', line 714

def modify_time_str
  @modify_time_str
end

#nameObject

Returns the value of attribute name.



751
752
753
# File 'lib/ole/storage.rb', line 751

def name
  @name
end

#name_lenObject

Returns the value of attribute name_len

Returns:

  • (Object)

    the current value of name_len



714
715
716
# File 'lib/ole/storage.rb', line 714

def name_len
  @name_len
end

#name_utf16Object

Returns the value of attribute name_utf16

Returns:

  • (Object)

    the current value of name_utf16



714
715
716
# File 'lib/ole/storage.rb', line 714

def name_utf16
  @name_utf16
end

#nextObject

Returns the value of attribute next

Returns:

  • (Object)

    the current value of next



714
715
716
# File 'lib/ole/storage.rb', line 714

def next
  @next
end

#oleObject (readonly)

Returns the value of attribute ole.



752
753
754
# File 'lib/ole/storage.rb', line 752

def ole
  @ole
end

#prevObject

Returns the value of attribute prev

Returns:

  • (Object)

    the current value of prev



714
715
716
# File 'lib/ole/storage.rb', line 714

def prev
  @prev
end

#reservedObject

Returns the value of attribute reserved

Returns:

  • (Object)

    the current value of reserved



714
715
716
# File 'lib/ole/storage.rb', line 714

def reserved
  @reserved
end

#sizeObject

Returns the value of attribute size

Returns:

  • (Object)

    the current value of size



714
715
716
# File 'lib/ole/storage.rb', line 714

def size
  @size
end

#typeObject (readonly)

Returns the value of attribute type.



752
753
754
# File 'lib/ole/storage.rb', line 752

def type
  @type
end

#type_idObject

Returns the value of attribute type_id

Returns:

  • (Object)

    the current value of type_id



714
715
716
# File 'lib/ole/storage.rb', line 714

def type_id
  @type_id
end

Class Method Details

.copy(src, dst) ⇒ Object

Raises:

  • (ArgumentError)


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

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.children << 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.



873
874
875
876
877
878
879
# File 'lib/ole/storage.rb', line 873

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.



832
833
834
# File 'lib/ole/storage.rb', line 832

def / name
	children.find { |child| name === child.name }
end

#[](idx) ⇒ Object



836
837
838
839
840
841
842
843
# File 'lib/ole/storage.rb', line 836

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

#delete(child) ⇒ Object

Raises:

  • (ArgumentError)


922
923
924
925
926
927
# File 'lib/ole/storage.rb', line 922

def delete child
	# remove from our child array, so that on reflatten and re-creation of @dirents, it will be gone
	raise ArgumentError, "#{child.inspect} not a child of #{self.inspect}" unless @children.delete child
	# free our blocks
	child.open { |io| io.truncate 0 }
end

#dir?Boolean

Returns:

  • (Boolean)


826
827
828
829
# File 'lib/ole/storage.rb', line 826

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

#each_child(&block) ⇒ Object



851
852
853
# File 'lib/ole/storage.rb', line 851

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

#file?Boolean

Returns:

  • (Boolean)


822
823
824
# File 'lib/ole/storage.rb', line 822

def file?
	type == :file
end

#flatten(dirents = []) ⇒ Object

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



856
857
858
859
860
861
862
863
864
865
866
# File 'lib/ole/storage.rb', line 856

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



907
908
909
910
911
912
913
914
915
916
917
918
919
920
# File 'lib/ole/storage.rb', line 907

def inspect
	str = "#<Dirent:#{name.inspect}"
	# perhaps i should remove the data snippet. its not that useful anymore.
	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}"
	else
		# there is some dir specific stuff. like clsid, flags.
	end
	str + '>'
end

#open(mode = 'r') ⇒ Object

Raises:

  • (Errno::EISDIR)


790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
# File 'lib/ole/storage.rb', line 790

def open mode='r'
	raise Errno::EISDIR unless file?
	io = RangesIOMigrateable.new self, mode
	# TODO work on the mode string stuff a bit more.
	# maybe let the io object know about the mode, so it can refuse
	# to work for read/write appropriately. maybe redefine all unusable
	# methods using singleton class to throw errors.
	# for now, i just want to implement truncation on use of 'w'. later,
	# i need to do 'a' etc.
	case mode
	when 'r', 'r+'
		# as i don't enforce reading/writing, nothing changes here. kind of
		# need to enforce tt if i want modify times to work better.
		@modify_time = Time.now if mode == 'r+'
	when 'w'
		@modify_time = Time.now
		#io.truncate 0
	else
		raise NotImplementedError, "unsupported mode - #{mode.inspect}"
	end
	if block_given?
		begin   yield io
		ensure; io.close
		end
	else io
	end
end

#read(limit = nil) ⇒ Object



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

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

#timeObject

move to ruby-msg. and remove from here



846
847
848
849
# File 'lib/ole/storage.rb', line 846

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

#to_sObject



881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
# File 'lib/ole/storage.rb', line 881

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