From c3611c091851bc2c819216f046136f25c75b0446 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 5 Sep 2008 21:30:16 -0700 Subject: [PATCH] Make EPUB metadata editing more robust and send LRF files to the 505 in preference to EPUB --- src/calibre/devices/prs505/driver.py | 2 +- src/calibre/ebooks/lrf/__init__.py | 1 + src/calibre/ebooks/metadata/epub.py | 19 +++++++------- src/calibre/gui2/library.py | 9 ++++--- src/calibre/ptempfile.py | 14 ++++++++++ src/calibre/utils/zipfile.py | 38 +++++++++++++++++++++++++--- 6 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index a33b85a7dc..713a51638c 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -31,7 +31,7 @@ class PRS505(Device): PRODUCT_ID = 0x031e #: Product Id for the PRS-505 PRODUCT_NAME = 'PRS-505' VENDOR_NAME = 'SONY' - FORMATS = ["lrf", 'epub', "rtf", "pdf", "txt"] + FORMATS = ['lrf', 'epub', "rtf", "pdf", "txt"] MEDIA_XML = 'database/cache/media.xml' CACHE_XML = 'Sony Reader/database/cache.xml' diff --git a/src/calibre/ebooks/lrf/__init__.py b/src/calibre/ebooks/lrf/__init__.py index 1133158c63..19a584d0aa 100644 --- a/src/calibre/ebooks/lrf/__init__.py +++ b/src/calibre/ebooks/lrf/__init__.py @@ -22,6 +22,7 @@ __docformat__ = "epytext" preferred_source_formats = [ 'LIT', 'MOBI', + 'EPUB', 'HTML', 'HTM', 'XHTM', diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index 44eaedaf26..d63719868a 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -7,7 +7,7 @@ __copyright__ = '2008, Kovid Goyal ' import sys, os -from calibre.utils.zipfile import ZipFile, BadZipfile +from calibre.utils.zipfile import ZipFile, BadZipfile, safe_replace from cStringIO import StringIO from contextlib import closing @@ -73,7 +73,7 @@ class OCFReader(OCF): class OCFZipReader(OCFReader): def __init__(self, stream, mode='r'): try: - self.archive = ZipFile(stream, mode) + self.archive = ZipFile(stream, mode=mode) except BadZipfile: raise EPubException("not a ZIP .epub OCF container") self.root = getattr(stream, 'name', os.getcwd()) @@ -82,18 +82,20 @@ class OCFZipReader(OCFReader): def open(self, name, mode='r'): return StringIO(self.archive.read(name)) -class OCFZipWriter(OCFZipReader): +class OCFZipWriter(object): 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): - name = self.container[OPF.MIMETYPE] stream = StringIO() opf = OPFCreator(self.root, mi) opf.render(stream) - self.archive.delete(name) - self.archive.writestr(name, stream.getvalue()) + stream.seek(0) + safe_replace(self.stream, self.opf, stream) class OCFDirReader(OCFReader): def __init__(self, path): @@ -133,9 +135,8 @@ def main(args=sys.argv): mi.tags = opts.tags.split(',') if opts.comment: mi.comments = opts.comment - + set_metadata(stream, mi) - print unicode(mi) return 0 diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 61bef5c666..72096cd0a5 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -311,11 +311,14 @@ class BooksModel(QAbstractTableModel): ans = [] for row in (row.row() for row in rows): format = None - for f in self.db.formats(row).split(','): - if f.lower() in formats: + db_formats = set(self.db.formats(row).lower().split(',')) + 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 break - if format: + if format is not None: pt = PersistentTemporaryFile(suffix='.'+format) pt.write(self.db.format(row, format)) pt.flush() diff --git a/src/calibre/ptempfile.py b/src/calibre/ptempfile.py index 74831f95aa..00c9976bd2 100644 --- a/src/calibre/ptempfile.py +++ b/src/calibre/ptempfile.py @@ -1,3 +1,4 @@ +from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' """ @@ -61,4 +62,17 @@ def PersistentTemporaryDirectory(suffix='', prefix='', dir=None): tdir = tempfile.mkdtemp(suffix, __appname__+"_"+ __version__+"_" +prefix, dir) atexit.register(shutil.rmtree, tdir, True) 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) diff --git a/src/calibre/utils/zipfile.py b/src/calibre/utils/zipfile.py index b135bde601..81c2e98f5a 100644 --- a/src/calibre/utils/zipfile.py +++ b/src/calibre/utils/zipfile.py @@ -2,6 +2,8 @@ Read and write ZIP files. Modified by Kovid Goyal to support replacing files in a zip archive. """ +from __future__ import with_statement +from calibre.ptempfile import TemporaryDirectory import struct, os, time, sys, shutil import binascii, cStringIO @@ -653,10 +655,10 @@ class ZipFile: 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".""" 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: pass @@ -856,7 +858,8 @@ class ZipFile: if self.filelist[j].header_offset > deleted_offset: self.filelist[j].header_offset -= deleted_size 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 if self.debug: print name, "not in archive" @@ -1178,6 +1181,9 @@ class ZipFile: self.NameToInfo[zinfo.filename] = zinfo def add_dir(self, path, prefix=''): + ''' + Add a directory recursively to the zip file with an optional prefix. + ''' if prefix: self.writestr(prefix+'/', '', 0700) cwd = os.path.abspath(os.getcwd()) @@ -1303,6 +1309,32 @@ class ZipFile: self.fp.close() 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 to create ZIP archives with Python library files and packages."""