1# filelike/wrappers/fixedblocksize.py 2# 3# Copyright (C) 2006-2008, Ryan Kelly 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the 17# Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18# Boston, MA 02111-1307, USA. 19# 20""" 21 22 filelike.wrappers.fixedblocksize: read/write only on block boundaries 23 24This module provides the filelike wrapper 'FixedBlockSize' to ensure that 25reads/writes to the underlying file are only performed at block boundaries. 26 27""" 28 29import filelike 30from filelike.wrappers import FileWrapper 31 32 33class FixedBlockSize(FileWrapper): 34 """Class reading/writing to files at a fixed block size. 35 36 This file wrapper can be used to read or write to a file-like 37 object at a specific block size. All reads request strings 38 whose length is a multiple of the block size, and all writes 39 pass on strings of a similar nature. This could be useful, for 40 example, to write data to a cipher function without manually 41 chunking text to match the cipher's block size. 42 43 No padding is added to the file if its length is not a multiple 44 of the blocksize. This might cause things to fail when this file 45 is flushed or closed, since an incorrectly-sized string could be 46 given in this case. 47 """ 48 49 def __init__(self,fileobj,blocksize,mode=None): 50 self.blocksize = blocksize 51 super(FixedBlockSize,self).__init__(fileobj,mode) 52 53 def _round_up(self,num): 54 """Round <num> up to a multiple of the block size.""" 55 if num % self.blocksize == 0: 56 return num 57 return ((num/self.blocksize)+1) * self.blocksize 58 59 def _round_down(self,num): 60 """Round <num> down to a multiple of the block size.""" 61 if num % self.blocksize == 0: 62 return num 63 return (num/self.blocksize) * self.blocksize 64 65 def _read(self,sizehint=-1): 66 """Read approximately <sizehint> bytes from the file.""" 67 if sizehint >= 0: 68 sizehint = self._round_up(sizehint) 69 data = self._fileobj.read(sizehint) 70 if data == "": 71 return None 72 return data 73 74 def _write(self,data,flushing=False): 75 """Write the given string to the file. 76 77 When flushing data to the file, it may need to be padded to the 78 block size. We attempt to read additional data from the 79 underlying file to use for the padding. 80 """ 81 size = self._round_down(len(data)) 82 self._fileobj.write(data[:size]) 83 if len(data) == size: 84 return "" 85 if not flushing: 86 return data[size:] 87 # Flushing, so we need to try to pad the data with existing contents. 88 # If we can't find such contents, just write at non-blocksize. 89 if self._check_mode("r"): 90 nextBlock = self._fileobj.read(self.blocksize) 91 self._fileobj.seek(-1*len(nextBlock),1) 92 else: 93 nextBlock = "" 94 padstart = len(data) - size 95 self._fileobj.write(data[size:] + nextBlock[padstart:]) 96 # Seek back to start of previous block, if the file is readable. 97 if self._check_mode("r"): 98 self.seek(padstart - self.blocksize,1) 99 return "" 100 101 # TODO: primitive implementation of relative seek 102 def _seek(self,offset,whence): 103 """Absolute seek, repecting block boundaries. 104 105 This method performs an absolute file seek to the block boundary 106 closest to (but not exceeding) the specified offset. 107 """ 108 if whence != 0: 109 raise NotImplementedError 110 boundary = self._round_down(offset) 111 self._fileobj.seek(boundary,0) 112 if boundary == offset: 113 return "" 114 else: 115 data = self._fileobj.read(self.blocksize) 116 diff = offset - boundary - len(data) 117 if diff > 0: 118 # Seeked past end of file. Actually do this on fileobj, so 119 # that it will raise an error if appropriate. 120 self._fileobj.seek(diff,1) 121 self._fileobj.seek(-1*diff,1) 122 self._fileobj.seek(-1*len(data),1) 123 return data[:(offset-boundary)] 124 125 126