AllocationTable
's hold the chains corresponding to files.
Given an initial index, AllocationTable#chain
follows the
chain, returning the blocks that make up that file.
There are 2 allocation tables, the bbat, and sbat, for big and small blocks
respectively. The block chain should be loaded using either
Storage#read_big_blocks
or
Storage#read_small_blocks
as appropriate.
Whether or not big or small blocks are used for a file depends on whether
its size is over the Header#threshold
level.
An Ole::Storage
document is serialized as a series of
directory objects, which are stored in blocks throughout the file. The
blocks are either big or small, and are accessed using the
AllocationTable
.
The bbat allocation table's data is stored in the spare room in the header block, and in extra blocks throughout the file as referenced by the meta bat. That chain is linear, as there is no higher level table.
::new is used to create an empty table. It can parse a string with the load method. Serialization is accomplished with the to_s method.
a free block (I don't currently leave any blocks free), although I do pad out the allocation table with AVAIL to the block size.
these blocks are used for storing the allocation table chains
# File lib/ole/storage/base.rb, line 434 def initialize ole @ole = ole @sparse = true super() end
# File lib/ole/storage/base.rb, line 524 def []= idx, val @sparse = true if val == AVAIL super end
Turn a chain (an array given by chain
) of blocks (optionally
truncated to size
) into an array of arrays describing the
stretches of bytes in the file that it belongs to.
The blocks are Big or Small blocks depending on the table type.
# File lib/ole/storage/base.rb, line 486 def blocks_to_ranges chain, size=nil # truncate the chain if required chain = chain[0, (size.to_f / block_size).ceil] if size # convert chain to ranges of the block size ranges = chain.map { |i| [block_size * i, block_size] } # truncate final range if required ranges.last[1] -= (ranges.length * block_size - size) if ranges.last and size ranges end
rewrote this to be non-recursive as it broke on a large attachment chain with a stack error
# File lib/ole/storage/base.rb, line 470 def chain idx a = [] until idx >= META_BAT raise FormatError, "broken allocationtable chain" if idx < 0 || idx > length a << idx idx = self[idx] end Log.warn "invalid chain terminator #{idx}" unless idx == EOC a end
# File lib/ole/storage/base.rb, line 529 def free_block if @sparse i = index(AVAIL) and return i @sparse = false end push AVAIL length - 1 end
# File lib/ole/storage/base.rb, line 440 def load data replace data.unpack('V*') end
quick shortcut. chain can be either a head (in which case the table is used to turn it into a chain), or a chain. it is converted to ranges, then to rangesio.
# File lib/ole/storage/base.rb, line 503 def open chain, size=nil, &block RangesIO.open @io, :ranges => ranges(chain, size), &block end
# File lib/ole/storage/base.rb, line 496 def ranges chain, size=nil chain = self.chain(chain) unless Array === chain blocks_to_ranges chain, size end
# File lib/ole/storage/base.rb, line 507 def read chain, size=nil open chain, size, &:read end
must return first_block. modifies blocks
in place
# File lib/ole/storage/base.rb, line 539 def resize_chain blocks, size new_num_blocks = (size / block_size.to_f).ceil old_num_blocks = blocks.length if new_num_blocks < old_num_blocks # de-allocate some of our old blocks. TODO maybe zero them out in the file??? (new_num_blocks...old_num_blocks).each { |i| self[blocks[i]] = AVAIL } self[blocks[new_num_blocks-1]] = EOC if new_num_blocks > 0 blocks.slice! new_num_blocks..-1 elsif new_num_blocks > old_num_blocks # need some more blocks. last_block = blocks.last (new_num_blocks - old_num_blocks).times do block = free_block # connect the chain. handle corner case of blocks being [] initially self[last_block] = block if last_block blocks << block last_block = block self[last_block] = EOC end end # update ranges, and return that also now blocks end
# File lib/ole/storage/base.rb, line 458 def to_s table = truncate # pad it out some num = @ole.bbat.block_size / 4 # do you really use AVAIL? they probably extend past end of file, and may shortly # be used for the bat. not really good. table += [AVAIL] * (num - (table.length % num)) if (table.length % num) != 0 table.pack 'V*' end
# File lib/ole/storage/base.rb, line 444 def truncate # this strips trailing AVAILs. come to think of it, this has the potential to break # bogus ole. if you terminate using AVAIL instead of EOC, like I did before. but that is # very broken. however, if a chain ends with AVAIL, it should probably be fixed to EOC # at load time. temp = reverse not_avail = temp.find { |b| b != AVAIL } and temp = temp[temp.index(not_avail)..-1] temp.reverse end
# File lib/ole/storage/base.rb, line 454 def truncate! replace truncate end