Converted overflow warnings into errors in LRFMetaFile

Fixed bug where setting a smaller thumbnail would not reduce lrf file size
Raised pylint score for lrf/*.py
This commit is contained in:
Kovid Goyal 2006-12-22 00:32:28 +00:00
parent 8bd8bfb39c
commit 9f4a2c9d72

View File

@ -14,8 +14,10 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
This module presents an easy to use interface for getting and setting meta information in LRF files.
Just create an L{LRFMetaFile} object and use its properties to get and set meta information. For example:
This module presents an easy to use interface for getting and setting
meta information in LRF files.
Just create an L{LRFMetaFile} object and use its properties
to get and set meta information. For example:
>>> lrf = LRFMetaFile("mybook.lrf")
>>> print lrf.title, lrf.author
@ -41,12 +43,16 @@ class versioned_field(field):
return self.vfield > self.version
def __get__(self, obj, typ=None):
if self.enabled(): return field.__get__(self, obj, typ=typ)
else: return None
if self.enabled():
return field.__get__(self, obj, typ=typ)
else:
return None
def __set__(self, obj, val):
if not self.enabled(): raise LRFException("Trying to set disabled field")
else: field.__set__(self, obj, val)
if not self.enabled():
raise LRFException("Trying to set disabled field")
else:
field.__set__(self, obj, val)
class LRFException(Exception):
pass
@ -67,11 +73,14 @@ class fixed_stringfield(object):
def __set__(self, obj, val):
if val.__class__.__name__ != 'str': val = str(val)
if len(val) != self._length: raise LRFException("Trying to set fixed_stringfield with a string of incorrect length")
if len(val) != self._length:
raise LRFException("Trying to set fixed_stringfield with a " + \
"string of incorrect length")
obj.pack(val, start=self._start, fmt="<"+str(len(val))+"s")
def __repr__(self):
return "A string of length " + str(self._length) + " starting at byte " + str(self._start)
return "A string of length " + str(self._length) + \
" starting at byte " + str(self._start)
class xml_field(object):
"""
@ -86,14 +95,16 @@ class xml_field(object):
document = dom.parseString(obj.info)
elem = document.getElementsByTagName(self.tag_name)[0]
elem.normalize()
if not elem.hasChildNodes(): return ""
if not elem.hasChildNodes():
return ""
return elem.firstChild.data.strip()
def __set__(self, obj, val):
document = dom.parseString(obj.info)
elem = document.getElementsByTagName(self.tag_name)[0]
elem.normalize()
while elem.hasChildNodes(): elem.removeChild(elem.lastChild)
while elem.hasChildNodes():
elem.removeChild(elem.lastChild)
elem.appendChild(dom.Text())
elem.firstChild.data = val
s = StringIO.StringIO()
@ -127,7 +138,8 @@ class LRFMetaFile(object):
compressed_info_size = field(fmt=WORD, start=0x4c)
thumbnail_type = versioned_field(version, 800, fmt=WORD, start=0x4e)
thumbnail_size = versioned_field(version, 800, fmt=DWORD, start=0x50)
uncompressed_info_size = versioned_field(compressed_info_size, 0, fmt=DWORD, start=0x54)
uncompressed_info_size = versioned_field(compressed_info_size, 0, \
fmt=DWORD, start=0x54)
title = xml_field("Title")
author = xml_field("Author")
@ -143,30 +155,40 @@ class LRFMetaFile(object):
page = xml_field("Page")
def safe(func):
""" Decorator that ensures that function calls leave the pos in the underlying file unchanged """
"""
Decorator that ensures that function calls leave the pos
in the underlying file unchanged
"""
def restore_pos(*args, **kwargs):
obj = args[0]
pos = obj._file.tell()
res = func(*args, **kwargs)
obj._file.seek(0, 2)
if obj._file.tell() >= pos: obj._file.seek(pos)
if obj._file.tell() >= pos:
obj._file.seek(pos)
return res
return restore_pos
def safe_property(func):
""" Decorator that ensures that read or writing a property leaves the position in the underlying file unchanged """
"""
Decorator that ensures that read or writing a property leaves
the position in the underlying file unchanged
"""
def decorator(f):
def restore_pos(*args, **kwargs):
obj = args[0]
pos = obj._file.tell()
res = f(*args, **kwargs)
obj._file.seek(0, 2)
if obj._file.tell() >= pos: obj._file.seek(pos)
if obj._file.tell() >= pos:
obj._file.seek(pos)
return res
return restore_pos
locals_ = func()
if locals_.has_key("fget"): locals_["fget"] = decorator(locals_["fget"])
if locals_.has_key("fset"): locals_["fset"] = decorator(locals_["fset"])
if locals_.has_key("fget"):
locals_["fget"] = decorator(locals_["fget"])
if locals_.has_key("fset"):
locals_["fset"] = decorator(locals_["fset"])
return property(**locals_)
@safe_property
@ -180,7 +202,8 @@ class LRFMetaFile(object):
try:
stream = zlib.decompress(self._file.read(size))
if len(stream) != self.uncompressed_info_size:
raise LRFException("Decompression of document meta info yielded unexpected results")
raise LRFException("Decompression of document meta info\
yielded unexpected results")
return stream
except zlib.error, e:
raise LRFException("Unable to decompress document meta information")
@ -192,19 +215,23 @@ class LRFMetaFile(object):
self._file.seek(self.info_start)
self._file.write(stream)
self._file.flush()
return locals()
return { "fget":fget, "fset":fset, "doc":doc }
@safe_property
def thumbnail_pos():
doc = """ The position of the thumbnail in the LRF file """
def fget(self):
return self.info_start+ self.compressed_info_size-4
return locals()
return { "fget":fget, "doc":doc }
@safe_property
def thumbnail():
doc = \
""" The thumbnail. Represented as a string. The string you would get from the file read function. """
"""
The thumbnail.
Represented as a string.
The string you would get from the file read function.
"""
def fget(self):
size = self.thumbnail_size
if size:
@ -212,7 +239,9 @@ class LRFMetaFile(object):
return self._file.read(size)
def fset(self, data):
if self.version <= 800: raise LRFException("Cannot store thumbnails in LRF files of version <= 800")
if self.version <= 800:
raise LRFException("Cannot store thumbnails in LRF files \
of version <= 800")
orig_size = self.thumbnail_size
self._file.seek(self.toc_object_offset)
toc = self._file.read(self.object_index_offset - self.toc_object_offset)
@ -226,14 +255,19 @@ class LRFMetaFile(object):
self._file.write(toc)
self.object_index_offset = self._file.tell()
self._file.write(objects)
ttype = 0x14
if data[1:4] == "PNG": ttype = 0x12
if data[0:2] == "BM": ttype = 0x13
if data[0:4] == "JIFF": ttype = 0x11
self.thumbnail_type = ttype
self._file.flush()
self.update_object_offsets(self.toc_object_offset - orig_offset) # Needed as new thumbnail may have different size than old thumbnail
return locals()
self._file.truncate() # Incase old thumbnail was bigger than new
ttype = 0x14
if data[1:4] == "PNG":
ttype = 0x12
if data[0:2] == "BM":
ttype = 0x13
if data[0:4] == "JIFF":
ttype = 0x11
self.thumbnail_type = ttype
# Needed as new thumbnail may have different size than old thumbnail
self.update_object_offsets(self.toc_object_offset - orig_offset)
return { "fget":fget, "fset":fset, "doc":doc }
def __init__(self, file):
""" @param file: A file object opened in the r+b mode """
@ -241,22 +275,30 @@ class LRFMetaFile(object):
self.size = file.tell()
self._file = file
if self.lrf_header != LRFMetaFile.LRF_HEADER:
raise LRFException(file.name + " has an invalid LRF header. Are you sure it is an LRF file?")
self.info_start = 0x58 if self.version > 800 else 0x53 #: Byte at which the compressed meta information starts
raise LRFException(file.name + \
" has an invalid LRF header. Are you sure it is an LRF file?")
# Byte at which the compressed meta information starts
self.info_start = 0x58 if self.version > 800 else 0x53
@safe
def update_object_offsets(self, delta):
""" Run through the LRF Object index changing the offset by C{delta}. """
self._file.seek(self.object_index_offset)
while(True):
try: self._file.read(4)
except EOFError: break
try:
self._file.read(4)
except EOFError:
break
pos = self._file.tell()
try: offset = self.unpack(fmt=DWORD, start=pos)[0] + delta
except struct.error: break
try:
offset = self.unpack(fmt=DWORD, start=pos)[0] + delta
except struct.error:
break
self.pack(offset, fmt=DWORD, start=pos)
try: self._file.read(12)
except EOFError: break
try:
self._file.read(12)
except EOFError:
break
self._file.flush()
@safe
@ -269,13 +311,15 @@ class LRFMetaFile(object):
"""
end = start + struct.calcsize(fmt)
self._file.seek(start)
self._file.seek(start)
ret = struct.unpack(fmt, self._file.read(end-start))
return ret
@safe
def pack(self, *args, **kwargs):
"""
Encode C{args} and write them to file. C{kwargs} must contain the keywords C{fmt} and C{start}
Encode C{args} and write them to file.
C{kwargs} must contain the keywords C{fmt} and C{start}
@param args: The values to pack
@param fmt: See U{struct<http://docs.python.org/lib/module-struct.html>}
@ -286,42 +330,53 @@ class LRFMetaFile(object):
self._file.write(encoded)
self._file.flush()
def __add__(self, tb):
""" Return a LRFFile rather than a list as the sum """
return LRFFile(list.__add__(self, tb))
def __getslice__(self, start, end):
""" Return a LRFFile rather than a list as the slice """
return LRFFile(list.__getslice__(self, start, end))
def thumbail_extension(self):
ext = "gif"
ttype = self.thumbnail_type
if ttype == 0x11: ext = "jpeg"
elif ttype == 0x12: ext = "png"
elif ttype == 0x13: ext = "bm"
if ttype == 0x11:
ext = "jpeg"
elif ttype == 0x12:
ext = "png"
elif ttype == 0x13:
ext = "bm"
return ext
def main():
import sys, os.path
from optparse import OptionParser
from libprs500 import __version__ as VERSION
parser = OptionParser(usage="usage: %prog [options] mybook.lrf\n\nWARNING: Based on reverse engineering the LRF format. Making changes may render your LRF file unreadable. ", version=VERSION)
parser.add_option("-t", "--title", action="store", type="string", dest="title", help="Set the book title")
parser.add_option("-a", "--author", action="store", type="string", dest="author", help="Set the author")
parser.add_option("-c", "--category", action="store", type="string", dest="category", help="The category this book belongs to. E.g.: History")
parser.add_option("--thumbnail", action="store", type="string", dest="thumbnail", help="Path to a graphic that will be set as this files' thumbnail")
parser.add_option("--get-thumbnail", action="store_true", dest="get_thumbnail", default=False, help="Extract thumbnail from LRF file")
parser.add_option("-p", "--page", action="store", type="string", dest="page", help="Don't know what this is for")
parser = OptionParser(usage="usage: %prog [options] mybook.lrf\n\
\nWARNING: Based on reverse engineering the LRF format."+\
" Making changes may render your LRF file unreadable. ", \
version=VERSION)
parser.add_option("-t", "--title", action="store", type="string", \
dest="title", help="Set the book title")
parser.add_option("-a", "--author", action="store", type="string", \
dest="author", help="Set the author")
parser.add_option("-c", "--category", action="store", type="string", \
dest="category", help="The category this book belongs"+\
" to. E.g.: History")
parser.add_option("--thumbnail", action="store", type="string", \
dest="thumbnail", help="Path to a graphic that will be"+\
" set as this files' thumbnail")
parser.add_option("--get-thumbnail", action="store_true", \
dest="get_thumbnail", default=False, \
help="Extract thumbnail from LRF file")
parser.add_option("-p", "--page", action="store", type="string", \
dest="page", help="Don't know what this is for")
options, args = parser.parse_args()
if len(args) != 1:
parser.print_help()
sys.exit(1)
lrf = LRFMetaFile(open(args[0], "r+b"))
if options.title: lrf.title = options.title
if options.author: lrf.author = options.author
if options.category: lrf.category = options.category
if options.page: lrf.page = options.page
if options.title:
lrf.title = options.title
if options.author:
lrf.author = options.author
if options.category:
lrf.category = options.category
if options.page:
lrf.page = options.page
if options.thumbnail:
f = open(options.thumbnail, "r")
lrf.thumbnail = f.read()
@ -340,4 +395,9 @@ def main():
for f in fields:
if "XML" in str(f):
print str(f[1]) + ":", lrf.__getattribute__(f[0])
if options.get_thumbnail: print "Thumbnail:", td
if options.get_thumbnail:
print "Thumbnail:", td
# This turns overflow warnings into errors
import warnings
warnings.simplefilter("error", DeprecationWarning)