Make EPUB metadata editing more robust and send LRF files to the 505 in preference to EPUB

This commit is contained in:
Kovid Goyal 2008-09-05 21:30:16 -07:00
parent deebf85442
commit c3611c0918
6 changed files with 67 additions and 16 deletions

View File

@ -31,7 +31,7 @@ class PRS505(Device):
PRODUCT_ID = 0x031e #: Product Id for the PRS-505 PRODUCT_ID = 0x031e #: Product Id for the PRS-505
PRODUCT_NAME = 'PRS-505' PRODUCT_NAME = 'PRS-505'
VENDOR_NAME = 'SONY' VENDOR_NAME = 'SONY'
FORMATS = ["lrf", 'epub', "rtf", "pdf", "txt"] FORMATS = ['lrf', 'epub', "rtf", "pdf", "txt"]
MEDIA_XML = 'database/cache/media.xml' MEDIA_XML = 'database/cache/media.xml'
CACHE_XML = 'Sony Reader/database/cache.xml' CACHE_XML = 'Sony Reader/database/cache.xml'

View File

@ -22,6 +22,7 @@ __docformat__ = "epytext"
preferred_source_formats = [ preferred_source_formats = [
'LIT', 'LIT',
'MOBI', 'MOBI',
'EPUB',
'HTML', 'HTML',
'HTM', 'HTM',
'XHTM', 'XHTM',

View File

@ -7,7 +7,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, os import sys, os
from calibre.utils.zipfile import ZipFile, BadZipfile from calibre.utils.zipfile import ZipFile, BadZipfile, safe_replace
from cStringIO import StringIO from cStringIO import StringIO
from contextlib import closing from contextlib import closing
@ -73,7 +73,7 @@ class OCFReader(OCF):
class OCFZipReader(OCFReader): class OCFZipReader(OCFReader):
def __init__(self, stream, mode='r'): def __init__(self, stream, mode='r'):
try: try:
self.archive = ZipFile(stream, mode) self.archive = ZipFile(stream, mode=mode)
except BadZipfile: except BadZipfile:
raise EPubException("not a ZIP .epub OCF container") raise EPubException("not a ZIP .epub OCF container")
self.root = getattr(stream, 'name', os.getcwd()) self.root = getattr(stream, 'name', os.getcwd())
@ -82,18 +82,20 @@ class OCFZipReader(OCFReader):
def open(self, name, mode='r'): def open(self, name, mode='r'):
return StringIO(self.archive.read(name)) return StringIO(self.archive.read(name))
class OCFZipWriter(OCFZipReader): class OCFZipWriter(object):
def __init__(self, stream): def __init__(self, stream):
OCFZipReader.__init__(self, stream, mode='a') reader = OCFZipReader(stream)
self.opf = reader.container[OPF.MIMETYPE]
self.stream = stream
self.root = getattr(stream, 'name', os.getcwd())
def set_metadata(self, mi): def set_metadata(self, mi):
name = self.container[OPF.MIMETYPE]
stream = StringIO() stream = StringIO()
opf = OPFCreator(self.root, mi) opf = OPFCreator(self.root, mi)
opf.render(stream) opf.render(stream)
self.archive.delete(name) stream.seek(0)
self.archive.writestr(name, stream.getvalue()) safe_replace(self.stream, self.opf, stream)
class OCFDirReader(OCFReader): class OCFDirReader(OCFReader):
def __init__(self, path): def __init__(self, path):
@ -133,9 +135,8 @@ def main(args=sys.argv):
mi.tags = opts.tags.split(',') mi.tags = opts.tags.split(',')
if opts.comment: if opts.comment:
mi.comments = opts.comment mi.comments = opts.comment
set_metadata(stream, mi) set_metadata(stream, mi)
print unicode(mi) print unicode(mi)
return 0 return 0

View File

@ -311,11 +311,14 @@ class BooksModel(QAbstractTableModel):
ans = [] ans = []
for row in (row.row() for row in rows): for row in (row.row() for row in rows):
format = None format = None
for f in self.db.formats(row).split(','): db_formats = set(self.db.formats(row).lower().split(','))
if f.lower() in formats: available_formats = set([f.lower() for f in formats])
u = available_formats.intersection(db_formats)
for f in formats:
if f.lower() in u:
format = f format = f
break break
if format: if format is not None:
pt = PersistentTemporaryFile(suffix='.'+format) pt = PersistentTemporaryFile(suffix='.'+format)
pt.write(self.db.format(row, format)) pt.write(self.db.format(row, format))
pt.flush() pt.flush()

View File

@ -1,3 +1,4 @@
from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
""" """
@ -61,4 +62,17 @@ def PersistentTemporaryDirectory(suffix='', prefix='', dir=None):
tdir = tempfile.mkdtemp(suffix, __appname__+"_"+ __version__+"_" +prefix, dir) tdir = tempfile.mkdtemp(suffix, __appname__+"_"+ __version__+"_" +prefix, dir)
atexit.register(shutil.rmtree, tdir, True) atexit.register(shutil.rmtree, tdir, True)
return tdir return tdir
class TemporaryDirectory(str):
def __init__(self, suffix='', prefix='', dir=None):
self.suffix = suffix
self.prefix = prefix
self.dir = dir
def __enter__(self):
self.tdir = tempfile.mkdtemp(self.suffix, __appname__+"_"+ __version__+"_" +self.prefix, self.dir)
return self.tdir
def __exit__(self, *args):
shutil.rmtree(self.tdir)

View File

@ -2,6 +2,8 @@
Read and write ZIP files. Modified by Kovid Goyal to support replacing files in Read and write ZIP files. Modified by Kovid Goyal to support replacing files in
a zip archive. a zip archive.
""" """
from __future__ import with_statement
from calibre.ptempfile import TemporaryDirectory
import struct, os, time, sys, shutil import struct, os, time, sys, shutil
import binascii, cStringIO import binascii, cStringIO
@ -653,10 +655,10 @@ class ZipFile:
fp = None # Set here since __del__ checks it fp = None # Set here since __del__ checks it
def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False): def __init__(self, file, mode="r", compression=ZIP_DEFLATED, allowZip64=False):
"""Open the ZIP file with mode read "r", write "w" or append "a".""" """Open the ZIP file with mode read "r", write "w" or append "a"."""
if mode not in ("r", "w", "a"): if mode not in ("r", "w", "a"):
raise RuntimeError('ZipFile() requires mode "r", "w", or "a"') raise RuntimeError('ZipFile() requires mode "r", "w", or "a" not %s'%mode)
if compression == ZIP_STORED: if compression == ZIP_STORED:
pass pass
@ -856,7 +858,8 @@ class ZipFile:
if self.filelist[j].header_offset > deleted_offset: if self.filelist[j].header_offset > deleted_offset:
self.filelist[j].header_offset -= deleted_size self.filelist[j].header_offset -= deleted_size
if self.filelist[j].file_offset > deleted_offset: if self.filelist[j].file_offset > deleted_offset:
self.filelist[j].file_offset -= deleted_size self.filelist[j].file_offset -= deleted_size
self._didModify = True
return return
if self.debug: if self.debug:
print name, "not in archive" print name, "not in archive"
@ -1178,6 +1181,9 @@ class ZipFile:
self.NameToInfo[zinfo.filename] = zinfo self.NameToInfo[zinfo.filename] = zinfo
def add_dir(self, path, prefix=''): def add_dir(self, path, prefix=''):
'''
Add a directory recursively to the zip file with an optional prefix.
'''
if prefix: if prefix:
self.writestr(prefix+'/', '', 0700) self.writestr(prefix+'/', '', 0700)
cwd = os.path.abspath(os.getcwd()) cwd = os.path.abspath(os.getcwd())
@ -1303,6 +1309,32 @@ class ZipFile:
self.fp.close() self.fp.close()
self.fp = None self.fp = None
def safe_replace(zipstream, name, datastream):
'''
Replace a file in a zip file in a safe manner. This proceeds by extracting
and re-creating the zipfile. This is neccessary because :method:`ZipFile.replace`
sometimes created corrupted zip files.
:param zipstream: Stream from a zip file
:param name: The name of the file to replace
:param datastream: The data to replace the file with.
'''
z = ZipFile(zipstream, 'r')
names = z.namelist()
with TemporaryDirectory('_zipfile_replace') as tdir:
z.extractall(path=tdir)
zipstream.seek(0)
zipstream.truncate()
z = ZipFile(zipstream, 'w')
path = os.path.join(tdir, *name.split('/'))
shutil.copyfileobj(datastream, open(path, 'wb'))
for name in names:
current = os.path.join(tdir, *name.split('/'))
if os.path.isdir(current):
z.writestr(name+'/', '', 0700)
else:
z.write(current, name)
z.close()
class PyZipFile(ZipFile): class PyZipFile(ZipFile):
"""Class to create ZIP archives with Python library files and packages.""" """Class to create ZIP archives with Python library files and packages."""