diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 9ca7ae590d..91fb7c46aa 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -189,6 +189,17 @@ class ZipMetadataReader(MetadataReaderPlugin): from calibre.ebooks.metadata.zip import get_metadata return get_metadata(stream) +class RARMetadataReader(MetadataReaderPlugin): + + name = 'Read RAR metadata' + file_types = set(['rar']) + description = _('Read metadata from ebooks in RAR archives') + + def get_metadata(self, stream, ftype): + from calibre.ebooks.metadata.rar import get_metadata + return get_metadata(stream) + + class EPUBMetadataWriter(MetadataWriterPlugin): name = 'Set EPUB metadata' diff --git a/src/calibre/ebooks/metadata/rar.py b/src/calibre/ebooks/metadata/rar.py new file mode 100644 index 0000000000..16f2c67af7 --- /dev/null +++ b/src/calibre/ebooks/metadata/rar.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + +''' +Read metadata from RAR archives +''' + +import os +from cStringIO import StringIO +from calibre.ptempfile import PersistentTemporaryFile +from calibre.libunrar import extract_member, names + +def get_metadata(stream): + path = getattr(stream, 'name', False) + if not path: + pt = PersistentTemporaryFile('_rar-meta.rar') + pt.write(stream.read()) + pt.close() + path = pt.name + path = os.path.abspath(path) + file_names = list(names(path)) + for f in file_names: + stream_type = os.path.splitext(f)[1].lower() + if stream_type: + stream_type = stream_type[1:] + if stream_type in ('lit', 'opf', 'prc', 'mobi', 'fb2', 'epub', + 'rb', 'imp', 'pdf', 'lrf'): + data = extract_member(path, match=None, name=f)[1] + stream = StringIO(data) + from calibre.ebooks.metadata.meta import get_metadata + return get_metadata(stream, stream_type) + raise ValueError('No ebook found in RAR archive') + + diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui index 4cb33dedc8..9f734f9a68 100644 --- a/src/calibre/gui2/dialogs/config.ui +++ b/src/calibre/gui2/dialogs/config.ui @@ -6,8 +6,8 @@ 0 0 - 800 - 581 + 755 + 557 @@ -328,8 +328,8 @@ - - + + Use &Roman numerals for series number @@ -339,12 +339,47 @@ - + + + + Enable system &tray icon (needs restart) + + + + + + + Show &notifications in system tray + + + + + + + Show cover &browser in a separate window (needs restart) + + + + + + + Automatically send downloaded &news to ebook reader + + + + + + + &Delete news from library when it is sent to reader + + + + - &Number of covers to show in browse mode (after restart): + &Number of covers to show in browse mode (needs restart): cover_browse @@ -356,7 +391,7 @@ - + Toolbar @@ -402,125 +437,112 @@ + toolbar_button_size + label_4 + show_toolbar_text + columns + + groupBox_3 - - - - Select visible &columns in library view - - - - + + + + + + Select visible &columns in library view + + - - - true - - - QAbstractItemView::SelectRows - - - - - + - - - ... + + + true - - - :/images/arrow-up.svg:/images/arrow-up.svg + + QAbstractItemView::SelectRows - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - ... - - - - :/images/arrow-down.svg:/images/arrow-down.svg - - + + + + + ... + + + + :/images/arrow-up.svg:/images/arrow-up.svg + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + ... + + + + :/images/arrow-down.svg:/images/arrow-down.svg + + + + - - - - - Use internal &viewer for the following formats: - - - - - - true - - - QAbstractItemView::NoSelection - - - - - - - - - - - - - Enable system &tray icon (needs restart) - - - - - - - Automatically send downloaded &news to ebook reader - - - - - - - &Delete news from library when it is sent to reader - - - - - - - Show cover &browser in a separate window (needs restart) - - - - - - - Show &notifications in system tray - - + columns + + + + + + Use internal &viewer for: + + + + + + true + + + QAbstractItemView::NoSelection + + + + + + + + roman_numerals + groupBox_2 + groupBox + systray_icon + sync_news + delete_news + separate_cover_flow + systray_notifications + groupBox_3 + + diff --git a/src/calibre/libunrar.py b/src/calibre/libunrar.py index 7c608b72f1..96ba08cea5 100644 --- a/src/calibre/libunrar.py +++ b/src/calibre/libunrar.py @@ -188,8 +188,31 @@ def extract(path, dir): finally: os.chdir(cwd) _libunrar.RARCloseArchive(arc_data) - -def extract_member(path, match=re.compile(r'\.(jpg|jpeg|gif|png)\s*$', re.I)): + +def names(path): + if hasattr(path, 'read'): + data = path.read() + f = NamedTemporaryFile(suffix='.rar') + f.write(data) + f.flush() + path = f.name + open_archive_data = RAROpenArchiveDataEx(ArcName=path, OpenMode=RAR_OM_LIST, CmtBuf=None) + arc_data = _libunrar.RAROpenArchiveEx(byref(open_archive_data)) + try: + if open_archive_data.OpenResult != 0: + raise UnRARException(_interpret_open_error(open_archive_data.OpenResult, path)) + header_data = RARHeaderDataEx(CmtBuf=None) + while True: + if _libunrar.RARReadHeaderEx(arc_data, byref(header_data)) != 0: + break + PFCode = _libunrar.RARProcessFileW(arc_data, RAR_SKIP, None, None) + if PFCode != 0: + raise UnRARException(_interpret_process_file_error(PFCode)) + yield header_data.FileNameW + finally: + _libunrar.RARCloseArchive(arc_data) + +def extract_member(path, match=re.compile(r'\.(jpg|jpeg|gif|png)\s*$', re.I), name=None): if hasattr(path, 'read'): data = path.read() f = NamedTemporaryFile(suffix='.rar') @@ -210,7 +233,9 @@ def extract_member(path, match=re.compile(r'\.(jpg|jpeg|gif|png)\s*$', re.I)): PFCode = _libunrar.RARProcessFileW(arc_data, RAR_EXTRACT, None, None) if PFCode != 0: raise UnRARException(_interpret_process_file_error(PFCode)) - if match.search(header_data.FileNameW): + file_name = header_data.FileNameW + if (name is not None and file_name == name) or \ + (match is not None and match.search(file_name)): return header_data.FileNameW.replace('/', os.sep), \ open(os.path.join(dir, *header_data.FileNameW.split('/')), 'rb').read() finally: