diff --git a/src/calibre/ebooks/oeb/iterator.py b/src/calibre/ebooks/oeb/iterator.py index 7f56cb4d2d..91028c2bc5 100644 --- a/src/calibre/ebooks/oeb/iterator.py +++ b/src/calibre/ebooks/oeb/iterator.py @@ -15,7 +15,7 @@ from calibre.customize.ui import available_input_formats from calibre.ebooks.metadata.opf2 import OPF from calibre.ptempfile import TemporaryDirectory from calibre.ebooks.chardet import xml_to_unicode -from calibre.utils.zipfile import safe_replace, ZipFile +from calibre.utils.zipfile import safe_replace from calibre.utils.config import DynamicConfig from calibre.utils.logging import Log from calibre import guess_type, prints @@ -294,12 +294,8 @@ class EbookIterator(object): zf = open(self.pathtoebook, 'r+b') except IOError: return - zipf = ZipFile(zf, mode='a') - for name in zipf.namelist(): - if name == 'META-INF/calibre_bookmarks.txt': - safe_replace(zf, 'META-INF/calibre_bookmarks.txt', StringIO(dat)) - return - zipf.writestr('META-INF/calibre_bookmarks.txt', dat) + safe_replace(zf, 'META-INF/calibre_bookmarks.txt', StringIO(dat), + add_missing=True) else: self.config['bookmarks_'+self.pathtoebook] = dat diff --git a/src/calibre/utils/zipfile.py b/src/calibre/utils/zipfile.py index 79f8a1a344..8f22b5f9e2 100644 --- a/src/calibre/utils/zipfile.py +++ b/src/calibre/utils/zipfile.py @@ -281,7 +281,7 @@ class ZipInfo (object): 'file_offset', ) - def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): + def __init__(self, filename=u"NoName", date_time=(1980,1,1,0,0,0)): self.orig_filename = filename # Original file name in archive # Terminate the file name at the first null byte. Null bytes in file @@ -1362,30 +1362,42 @@ class ZipFile: self.fp.close() self.fp = None -def safe_replace(zipstream, name, datastream, extra_replacements={}): +def safe_replace(zipstream, name, datastream, extra_replacements={}, + add_missing=False): ''' Replace a file in a zip file in a safe manner. This proceeds by extracting and re-creating the zipfile. This is necessary 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. :param extra_replacements: Extra replacements. Mapping of name to file-like objects + :param add_missing: If a replacement does not exist in the zip file, it is + added. Use with care as currently parent directories + are not created. ''' z = ZipFile(zipstream, 'r') replacements = {name:datastream} replacements.update(extra_replacements) names = frozenset(replacements.keys()) + found = set([]) with SpooledTemporaryFile(max_size=100*1024*1024) as temp: ztemp = ZipFile(temp, 'w') for obj in z.infolist(): + if isinstance(obj.filename, unicode): + obj.flag_bits |= 0x16 # Set isUTF-8 bit if obj.filename in names: ztemp.writestr(obj, replacements[obj.filename].read()) + found.add(obj.filename) else: ztemp.writestr(obj, z.read_raw(obj), raw_bytes=True) + if add_missing: + for name in names - found: + ztemp.writestr(name, replacements[name].read()) ztemp.close() z.close() temp.seek(0)