Class | Ole::Storage::AllocationTable |
In: |
lib/ole/storage/base.rb
|
Parent: | Array |
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.
AllocationTable.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.
AVAIL | = | 0xffffffff | 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. | |
EOC | = | 0xfffffffe | ||
BAT | = | 0xfffffffd | these blocks are used for storing the allocation table chains | |
META_BAT | = | 0xfffffffc |
block_size | [R] | |
io | [R] | |
ole | [R] |
# File lib/ole/storage/base.rb, line 431 431: def initialize ole 432: @ole = ole 433: @sparse = true 434: super() 435: end
# File lib/ole/storage/base.rb, line 522 522: def []= idx, val 523: @sparse = true if val == AVAIL 524: super 525: 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 483 483: def blocks_to_ranges chain, size=nil 484: # truncate the chain if required 485: chain = chain[0...(size.to_f / block_size).ceil] if size 486: # convert chain to ranges of the block size 487: ranges = chain.map { |i| [block_size * i, block_size] } 488: # truncate final range if required 489: ranges.last[1] -= (ranges.length * block_size - size) if ranges.last and size 490: ranges 491: 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 467 467: def chain idx 468: a = [] 469: until idx >= META_BAT 470: raise FormatError, "broken allocationtable chain" if idx < 0 || idx > length 471: a << idx 472: idx = self[idx] 473: end 474: Log.warn "invalid chain terminator #{idx}" unless idx == EOC 475: a 476: end
# File lib/ole/storage/base.rb, line 527 527: def free_block 528: if @sparse 529: i = index(AVAIL) and return i 530: end 531: @sparse = false 532: push AVAIL 533: length - 1 534: 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 500 500: def open chain, size=nil, &block 501: RangesIO.open @io, :ranges => ranges(chain, size), &block 502: end
# File lib/ole/storage/base.rb, line 493 493: def ranges chain, size=nil 494: chain = self.chain(chain) unless Array === chain 495: blocks_to_ranges chain, size 496: end
# File lib/ole/storage/base.rb, line 504 504: def read chain, size=nil 505: open chain, size, &:read 506: end
must return first_block. modifies blocks in place
# File lib/ole/storage/base.rb, line 537 537: def resize_chain blocks, size 538: new_num_blocks = (size / block_size.to_f).ceil 539: old_num_blocks = blocks.length 540: if new_num_blocks < old_num_blocks 541: # de-allocate some of our old blocks. TODO maybe zero them out in the file??? 542: (new_num_blocks...old_num_blocks).each { |i| self[blocks[i]] = AVAIL } 543: self[blocks[new_num_blocks-1]] = EOC if new_num_blocks > 0 544: blocks.slice! new_num_blocks..-1 545: elsif new_num_blocks > old_num_blocks 546: # need some more blocks. 547: last_block = blocks.last 548: (new_num_blocks - old_num_blocks).times do 549: block = free_block 550: # connect the chain. handle corner case of blocks being [] initially 551: self[last_block] = block if last_block 552: blocks << block 553: last_block = block 554: self[last_block] = EOC 555: end 556: end 557: # update ranges, and return that also now 558: blocks 559: end
# File lib/ole/storage/base.rb, line 455 455: def to_s 456: table = truncate 457: # pad it out some 458: num = @ole.bbat.block_size / 4 459: # do you really use AVAIL? they probably extend past end of file, and may shortly 460: # be used for the bat. not really good. 461: table += [AVAIL] * (num - (table.length % num)) if (table.length % num) != 0 462: table.pack 'V*' 463: end
# File lib/ole/storage/base.rb, line 441 441: def truncate 442: # this strips trailing AVAILs. come to think of it, this has the potential to break 443: # bogus ole. if you terminate using AVAIL instead of EOC, like I did before. but that is 444: # very broken. however, if a chain ends with AVAIL, it should probably be fixed to EOC 445: # at load time. 446: temp = reverse 447: not_avail = temp.find { |b| b != AVAIL } and temp = temp[temp.index(not_avail)..-1] 448: temp.reverse 449: end