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 ¬ifications 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 ¬ifications 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: