diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 356bece5d9..8e0f243056 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -300,6 +300,17 @@ class PDFMetadataWriter(MetadataWriterPlugin): from calibre.ebooks.metadata.pdf import set_metadata set_metadata(stream, mi) +class PDBMetadataWriter(MetadataWriterPlugin): + + name = 'Set PDB metadata' + file_types = set(['pdb']) + description = _('Set metadata from %s files') % 'PDB' + author = 'John Schember' + + def set_metadata(self, stream, mi, type): + from calibre.ebooks.metadata.pdb import set_metadata + set_metadata(stream, mi) + from calibre.ebooks.epub.input import EPUBInput from calibre.ebooks.mobi.input import MOBIInput diff --git a/src/calibre/ebooks/metadata/ereader.py b/src/calibre/ebooks/metadata/ereader.py index f37ff9ab6d..b1edee10b0 100644 --- a/src/calibre/ebooks/metadata/ereader.py +++ b/src/calibre/ebooks/metadata/ereader.py @@ -10,8 +10,8 @@ __docformat__ = 'restructuredtext en' import re -from calibre.ebooks.metadata import MetaInformation -from calibre.ebooks.pdb.header import PdbHeaderReader +from calibre.ebooks.metadata import MetaInformation, authors_to_string +from calibre.ebooks.pdb.header import PdbHeaderReader, PdbHeaderBuilder from calibre.ebooks.pdb.ereader.reader import HeaderRecord def get_metadata(stream, extract_cover=True): @@ -24,8 +24,8 @@ def get_metadata(stream, extract_cover=True): pheader = PdbHeaderReader(stream) hr = HeaderRecord(pheader.section_data(0)) - if hr.version in (2, 10): - try: + if hr.version in (2, 10) and hr.has_metadata == 1: + try: mdata = pheader.section_data(hr.metadata_offset) mdata = mdata.split('\x00') @@ -41,3 +41,42 @@ def get_metadata(stream, extract_cover=True): return mi +def set_metadata(stream, mi): + pheader = PdbHeaderReader(stream) + sections = [pheader.section_data(x) for x in range(0, pheader.section_count())] + hr = HeaderRecord(sections[0]) + + if hr.version not in (2, 10): + return + + # Create a metadata record for the file if one does not alreay exist + if not hr.has_metadata: + sections += ['', 'MeTaInFo\x00'] + last_data = len(sections) - 1 + + for i in range(0, 132, 2): + val, = struct.unpack('>H', sections[0][i:i+2]) + if val >= hr.last_data_offset: + sections[0][i:i+2] = struct.pack('>H', last_data) + + sections[0][24:26] = struct.pack('>H', 1) # Set has metadata + sections[0][44:46] = struct.pack('>H', last_data - 1) # Set location of metadata + sections[0][52:54] = struct.pack('>H', last_data) # Ensure last data offset is updated + + # Merge the metadata into the file + file_mi = get_metadata(stream, False) + file_mi.smart_update(mi) + sections[hr.metadata_offset] = '%s\x00%s\x00%s\x00%s\x00%s\x00' % \ + (file_mi.title, authors_to_string(file_mi.authors), '', file_mi.publisher, file_mi.isbn) + + # Rebuild the PDB wrapper because the offsets have changed due to the + # new metadata. + pheader_builder = PdbHeaderBuilder(pheader.ident, pheader.title) + stream.seek(0) + stream.truncate(0) + pheader_builder.build_header([len(x) for x in sections], stream) + + # Write the data back to the file + for item in sections: + stream.write(item) + diff --git a/src/calibre/ebooks/metadata/pdb.py b/src/calibre/ebooks/metadata/pdb.py index a6f7c6796b..f3d2782d16 100644 --- a/src/calibre/ebooks/metadata/pdb.py +++ b/src/calibre/ebooks/metadata/pdb.py @@ -12,11 +12,18 @@ import re from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.pdb.header import PdbHeaderReader -from calibre.ebooks.metadata.ereader import get_metadata as eReader +from calibre.ebooks.metadata.ereader import get_metadata as get_eReader -MREADER = { - 'PNPdPPrs' : eReader, - 'PNRdPPrs' : eReader, +MREADER = { + 'PNPdPPrs' : get_eReader, + 'PNRdPPrs' : get_eReader, +} + +from calibre.ebooks.metadata.ereader import set_metadata as set_eReader + +MWRITER = { + 'PNPdPPrs' : set_eReader, + 'PNRdPPrs' : set_eReader, } def get_metadata(stream, extract_cover=True): @@ -34,3 +41,16 @@ def get_metadata(stream, extract_cover=True): return MetadataReader(stream, extract_cover) +def set_metadata(stream, mi): + stream.seek(0) + + pheader = PdbHeaderReader(stream) + + MetadataWriter = MWRITER.get(pheader.ident, None) + + if MetadataWriter: + MetadataWriter(stream, mi) + + stream.seek(0) + stream.write(re.sub('[^-A-Za-z0-9]+', '_', mi.title).ljust(32, '\x00')[:32]) + diff --git a/src/calibre/ebooks/pdb/ereader/reader.py b/src/calibre/ebooks/pdb/ereader/reader.py index 90138180d2..13429c5a98 100644 --- a/src/calibre/ebooks/pdb/ereader/reader.py +++ b/src/calibre/ebooks/pdb/ereader/reader.py @@ -12,7 +12,6 @@ import os, re, struct, zlib from calibre import CurrentDir from calibre.ebooks import DRMError -from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.pdb.formatreader import FormatReader from calibre.ebooks.pdb.ereader import EreaderError from calibre.ebooks.pml.pmlconverter import pml_to_html, \ @@ -31,6 +30,7 @@ class HeaderRecord(object): def __init__(self, raw): self.version, = struct.unpack('>H', raw[0:2]) self.non_text_offset, = struct.unpack('>H', raw[12:14]) + self.has_metadata, = struct.unpack('>H', raw[24:26]) self.footnote_rec, = struct.unpack('>H', raw[28:30]) self.sidebar_rec, = struct.unpack('>H', raw[30:32]) self.bookmark_offset, = struct.unpack('>H', raw[32:34]) @@ -62,6 +62,9 @@ class Reader(FormatReader): else: raise EreaderError('Unknown book version %i.' % self.header_record.version) + from calibre.ebooks.metadata.pdb import get_metadata + self.mi = get_metadata(stream, False) + def section_data(self, number): return self.sections[number] @@ -144,10 +147,8 @@ class Reader(FormatReader): return opf_path def create_opf(self, output_dir, images): - mi = MetaInformation(None, None) - with CurrentDir(output_dir): - opf = OPFCreator(output_dir, mi) + opf = OPFCreator(output_dir, self.mi) manifest = [('index.html', None)] diff --git a/src/calibre/ebooks/pdb/ereader/writer.py b/src/calibre/ebooks/pdb/ereader/writer.py index 3f2e0d9225..f49aa4e125 100644 --- a/src/calibre/ebooks/pdb/ereader/writer.py +++ b/src/calibre/ebooks/pdb/ereader/writer.py @@ -18,11 +18,7 @@ from calibre.ebooks.pdb.header import PdbHeaderBuilder from calibre.ebooks.pdb.ereader import image_name from calibre.ebooks.pml.pmlconverter import html_to_pml -# We are using the older identity because we do not user newer features -# (sidebar, footnotes). This will ensure compatibility with older readers. -# If newer features are used (anything supported by dropbook but not by makebook -# change the identity to the newer PNRdPPrs. -IDENTITY = 'PNPdPPrs' +IDENTITY = 'PNRdPPrs' # This is an arbitrary number that is small enough to work. The actual maximum # record size is unknown. diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index b58f90daf6..b176c25062 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -220,9 +220,11 @@ class DeviceManager(Thread): '''Copy books from device to disk''' for path in paths: name = path.rpartition('/')[2] - f = open(os.path.join(target, name), 'wb') - self.device.get_file(path, f) - f.close() + dest = os.path.join(target, name) + if os.path.abspath(dest) != os.path.abspath(path): + f = open(dest, 'wb') + self.device.get_file(path, f) + f.close() def save_books(self, done, paths, target): return self.create_job(self._save_books, done, args=[paths, target], @@ -493,6 +495,7 @@ class DeviceGUI(object): return files, _auto_ids = self.library_view.model().get_preferred_formats_from_ids(ids, fmts, paths=True, set_metadata=True, + specific_format=specific_format, exclude_auto=do_auto_convert) if do_auto_convert: ids = list(set(ids).difference(_auto_ids)) @@ -563,9 +566,9 @@ class DeviceGUI(object): autos = [self.library_view.model().db.title(id, index_is_id=True) for id in auto] autos = '\n'.join('%s'%i for i in autos) info_dialog(self, _('No suitable formats'), - _('Auto converting the following books before uploading to ' - 'the device:'), det_msg=autos, show=True) - self.auto_convert_mail(to, delete_from_library, auto, format) + _('Auto converting the following books before sending via ' + 'email:'), det_msg=autos, show=True) + self.auto_convert_mail(to, fmts, delete_from_library, auto, format) if bad: bad = '\n'.join('%s'%(i,) for i in bad) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index b586853052..e51121c3e6 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -1051,7 +1051,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): current = self.library_view.currentIndex() self.library_view.model().current_changed(current, previous) - def auto_convert_mail(self, to, delete_from_library, book_ids, format): + def auto_convert_mail(self, to, fmts, delete_from_library, book_ids, format): previous = self.library_view.currentIndex() rows = [x.row() for x in \ self.library_view.selectionModel().selectedRows()] @@ -1062,7 +1062,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): job = self.job_manager.run_job(Dispatcher(self.book_auto_converted_mail), func, args=args, description=desc) self.conversion_jobs[job] = (temp_files, fmt, id, - delete_from_library, to) + delete_from_library, to, fmts) if changed: self.library_view.model().refresh_rows(rows) @@ -1141,7 +1141,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.sync_to_device(on_card, False, specific_format=fmt, send_ids=[book_id], do_auto_convert=False) def book_auto_converted_mail(self, job): - temp_files, fmt, book_id, delete_from_library, to = self.conversion_jobs.pop(job) + temp_files, fmt, book_id, delete_from_library, to, fmts = self.conversion_jobs.pop(job) try: if job.failed: self.job_exception(job) @@ -1162,7 +1162,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): current = self.library_view.currentIndex() self.library_view.model().current_changed(current, QModelIndex()) - self.send_by_mail(to, fmt, delete_from_library, send_ids=[book_id], do_auto_convert=False) + self.send_by_mail(to, fmts, delete_from_library, specific_format=fmt, send_ids=[book_id], do_auto_convert=False) def book_converted(self, job): temp_files, fmt, book_id = self.conversion_jobs.pop(job)