class Ole::Storage::AllocationTable

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.

Constants

AVAIL

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.

BAT

these blocks are used for storing the allocation table chains

EOC
META_BAT

Attributes

block_size[R]
io[R]
ole[R]

Public Class Methods

new(ole) click to toggle source
Calls superclass method
# File lib/ole/storage/base.rb, line 435
def initialize ole
  @ole = ole
  @sparse = true
  super()
end

Public Instance Methods

[]=(idx, val) click to toggle source
Calls superclass method
# File lib/ole/storage/base.rb, line 525
def []= idx, val
  @sparse = true if val == AVAIL
  super
end
blocks_to_ranges(chain, size=nil) click to toggle source

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 487
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
chain(idx) click to toggle source

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 471
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
free_block() click to toggle source
# File lib/ole/storage/base.rb, line 530
def free_block
  if @sparse
    i = index(AVAIL) and return i
    @sparse = false
  end
  push AVAIL
  length - 1
end
load(data) click to toggle source
# File lib/ole/storage/base.rb, line 441
def load data
  replace data.unpack('V*')
end
open(chain, size=nil, &block) click to toggle source

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 504
def open chain, size=nil, &block
  RangesIO.open @io, :ranges => ranges(chain, size), &block
end
ranges(chain, size=nil) click to toggle source
# File lib/ole/storage/base.rb, line 497
def ranges chain, size=nil
  chain = self.chain(chain) unless Array === chain
  blocks_to_ranges chain, size
end
read(chain, size=nil) click to toggle source
# File lib/ole/storage/base.rb, line 508
def read chain, size=nil
  open chain, size, &:read
end
resize_chain(blocks, size) click to toggle source

must return first_block. modifies blocks in place

# File lib/ole/storage/base.rb, line 540
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
to_s() click to toggle source
# File lib/ole/storage/base.rb, line 459
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
truncate() click to toggle source
# File lib/ole/storage/base.rb, line 445
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
truncate!() click to toggle source
# File lib/ole/storage/base.rb, line 455
def truncate!
  replace truncate
end