We can model the combination of a set of sectors and the associated file allocation table as a SectorSpace.
protocol SectorSpace
{
func data(index:SectorIndex) -> ByteData?
}
A SectorSpace
is an object capable of returning the contents of a stream given the index of the first sector in that ‘space’.
There are two possible SectorSpaces in a compound file. The first represents the sectors in the file itself in combination with the FAT. The second represents the sectors in the mini stream in combination with the mini FAT.
We can implements the first straight away. We have a SectorSource for the sectors in the file and the FAT.
For the second we have the mini FAT but we do not have the sectors stored in the mini stream.
The mini stream is an internal stream stored in sectors in the file itself, so it can be constructed using the first SectorSpace which represents those sectors and the FAT.
To construct the mini stream we need to know the starting sector and the size. These are stored in the directory entry for the root storage object.
We can define them as properties in the class RootStorageEntry
let miniStreamStart : SectorIndex
let miniStreamSize : StreamSize
We can make the mini stream sector space like this
private func makeMiniStreamSpace(
rootStorageEntry:
RootStorageEntry,
miniFAT:
FileAllocationTable,
fileSpace:
SectorSpace) -> SectorSpace?
{
if let data = fileSpace.data(rootStorageEntry.miniStreamStart)
{
return
MiniStreamSpace(
data:
data,
size:
rootStorageEntry.miniStreamSize,
fat:
miniFAT,
sectorSize:
CFBFFormat.MINI_SECTOR_SIZE)
}
else
{
return nil
}
}
Now we have our two sector spaces we can implement a stream factory that can create a stream object given the index of its first sector and its size.
The size below which a stream object is stored in the mini stream is defined by the miniStreamCutoffSize
field in the header. This and
the two sector spaces is all the stream factory needs.
final class StreamFactory
{
init(fileSpace:SectorSpace, miniStreamSpace:SectorSpace, miniStreamCutoffSize:StreamSize)
{
self.fileSpace = fileSpace
self.miniStreamSpace = miniStreamSpace
self.miniStreamCutoffSize = miniStreamCutoffSize
}
//
func makeStream(entry:StreamEntry) -> Stream?
{
let size = entry.streamSize
if size > miniStreamCutoffSize
{
return Stream(size:size, start: entry.startingSector, space: fileSpace)
}
else
{
return Stream(size:size, start: entry.startingSector, space: miniStreamSpace)
}
}
//
private let fileSpace : SectorSpace
private let miniStreamSpace : SectorSpace
private let miniStreamCutoffSize : StreamSize
}
Once we have the stream factory we can define a class which implements a storage object.
All it needs is the StorageEntry
which represents the storage object in the directory so it can find the stream and storage objects it contains, and the stream factory so that it can create stream objects as necessary,
final class Storage
{
init(entry:StorageEntry, streamFactory:StreamFactory)
{
self.entry = entry
self.streamFactory = streamFactory
self.storageTable = [String: Storage]()
self.streamTable = [String: Stream]()
}
//
func getStream(var path:[String], name:String) -> Stream?
{
if path.count != 0
{
return getStorage(path.removeAtIndex(0))?.getStream(path, name: name)
}
else
{
return getStream(name)
}
}
func getStorage(storageName:String) -> Storage?
{
var storage = storageTable[storageName]
if storage != nil
{
return storage
}
let storageEntry = entry.getStorageEntry(storageName)
if storageEntry == nil
{
return nil
}
storage = Storage(entry: storageEntry!, streamFactory: streamFactory)
storageTable[storageName] = storage
return storage
}
func getStream(streamName:String) -> Stream?
{
var stream = streamTable[streamName]
if stream == nil
{
let streamEntry = entry.getStreamEntry(streamName)
if streamEntry == nil
{
return nil
}
stream = streamFactory.makeStream(streamEntry!)
streamTable[streamName] = stream
}
return stream
}
//
private let entry : StorageEntry
private let streamFactory : StreamFactory
//
private var storageTable : [String: Storage]
private var streamTable : [String: Stream]
}
We can define a CompoundFile
as a very simple wrapper around the Storage
instance which represents the root storage object.
final class CompoundFile
{
init(rootStorage:Storage)
{
self.rootStorage = rootStorage
}
//
func getStream(#storage:[String], name:String) -> Stream?
{
return rootStorage.getStream(storage, name:name)
}
//
private let rootStorage: Storage
}
Copyright (c) 2014 By Simon Lewis. All Rights Reserved.
Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.
Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.