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