From 7226d2c639e45716b4b60db1ffaa4d28b28f7f5f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 12 Nov 2014 14:08:49 +0530 Subject: [PATCH] Add Books: Allow adding books from multiple ZIP/RAR archives, each containing many books, by right clicking on the Add Books and choosing "Add from Archive" --- manual/gui.rst | 2 +- src/calibre/gui2/actions/add.py | 8 +-- src/calibre/gui2/add2.py | 93 +++++++++++++++++---------------- 3 files changed, 54 insertions(+), 49 deletions(-) diff --git a/manual/gui.rst b/manual/gui.rst index fd6db32ab7..daa8eceb1a 100644 --- a/manual/gui.rst +++ b/manual/gui.rst @@ -51,7 +51,7 @@ Add books 3. **Add books from directories, including sub-directories (Multiple books per directory, assumes every ebook file is a different book)**: Allows you to choose a directory. The directory and all its sub-directories are scanned recursively and any ebooks found are added to the library. calibre assumes that each directory contains many books. All ebook files with the same name in a directory are assumed to be the same book in different formats. Ebooks with different names are added as different books. - 4. **Add multiple books from archive (ZIP/RAR)**: Allows you to add multiple ebooks that are stored inside a single ZIP or RAR file. It is a convenient shortcut that avoids having to first unzip the archive and then add the books via one of the above two options. + 4. **Add multiple books from archive (ZIP/RAR)**: Allows you to add multiple ebooks that are stored inside the selected ZIP or RAR files. It is a convenient shortcut that avoids having to first unzip the archive and then add the books via one of the above two options. 5. **Add empty book. (Book Entry with no formats)**: Allows you to create a blank book record. This can be used to then manually fill out the information about a book that you may not have yet in your collection. diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py index 3a8bdcb5ae..08308d539f 100644 --- a/src/calibre/gui2/actions/add.py +++ b/src/calibre/gui2/actions/add.py @@ -142,9 +142,9 @@ class AddAction(InterfaceAction): def add_archive(self, single): paths = choose_files( self.gui, 'recursive-archive-add', _('Choose archive file'), - filters=[(_('Archives'), ('zip', 'rar'))], all_files=False, select_only_single_file=True) + filters=[(_('Archives'), ('zip', 'rar'))], all_files=False, select_only_single_file=False) if paths: - self.do_add_recursive(paths[0], single) + self.do_add_recursive(paths, single, list_of_archives=True) def add_recursive(self, single): root = choose_dir(self.gui, 'recursive book import root dir dialog', @@ -157,9 +157,9 @@ class AddAction(InterfaceAction): 'Cannot add books from the folder: %s as it contains the currently opened calibre library') % root, show=True) self.do_add_recursive(root, single) - def do_add_recursive(self, root, single): + def do_add_recursive(self, root, single, list_of_archives=False): from calibre.gui2.add2 import Adder - Adder(root, single_book_per_directory=single, db=self.gui.current_db, + Adder(root, single_book_per_directory=single, db=self.gui.current_db, list_of_archives=list_of_archives, callback=self._files_added, parent=self.gui, pool=self.gui.spare_pool()) def add_recursive_single(self, *args): diff --git a/src/calibre/gui2/add2.py b/src/calibre/gui2/add2.py index a0901672a9..78b2605c9a 100644 --- a/src/calibre/gui2/add2.py +++ b/src/calibre/gui2/add2.py @@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import shutil, os, weakref, traceback +import shutil, os, weakref, traceback, tempfile from threading import Thread from collections import OrderedDict from Queue import Empty @@ -33,21 +33,21 @@ def validate_source(source, parent=None): # {{{ error_dialog(parent, _('Cannot add books'), _( 'The path %s does not exist') % source, show=True) return False - if os.path.isdir(source): - if not os.access(source, os.X_OK|os.R_OK): - error_dialog(parent, _('Cannot add books'), _( - 'You do not have permission to read %s') % source, show=True) - return False - else: - if not os.access(source, os.R_OK): - error_dialog(parent, _('Cannot add books'), _( - 'You do not have permission to read %s') % source, show=True) - return False - if not source.lower().rpartition(os.extsep)[-1] in {'zip', 'rar'}: - error_dialog(parent, _('Cannot add books'), _( - 'The file %s is not a recognized archive format') % source, show=True) - return False - + if not os.access(source, os.X_OK|os.R_OK): + error_dialog(parent, _('Cannot add books'), _( + 'You do not have permission to read %s') % source, show=True) + return False + else: + ok = False + for path in source: + if os.access(path, os.R_OK): + ok = True + break + if not ok: + error_dialog(parent, _('Cannot add books'), _( + 'You do not have permission to read any of the selected files'), + det_msg='\n'.join(source), show=True) + return False return True # }}} @@ -55,11 +55,12 @@ class Adder(QObject): do_one_signal = pyqtSignal() - def __init__(self, source, single_book_per_directory=True, db=None, parent=None, callback=None, pool=None): + def __init__(self, source, single_book_per_directory=True, db=None, parent=None, callback=None, pool=None, list_of_archives=False): if not validate_source(source, parent): return QObject.__init__(self, parent) self.single_book_per_directory = single_book_per_directory + self.list_of_archives = list_of_archives self.callback = callback self.add_formats_to_existing = prefs['add_formats_to_existing'] self.do_one_signal.connect(self.tick, type=Qt.QueuedConnection) @@ -111,26 +112,46 @@ class Adder(QObject): self.do_one() # Filesystem scan {{{ + def scan(self): + + def find_files(root): + for dirpath, dirnames, filenames in os.walk(root): + for files in find_books_in_directory(dirpath, self.single_book_per_directory): + if self.abort_scan: + return + if files: + self.file_groups[len(self.file_groups)] = files + + def extract(source): + tdir = tempfile.mkdtemp(suffix='_archive', dir=self.tdir) + if source.lower().endswith('.zip'): + from calibre.utils.zipfile import ZipFile + try: + with ZipFile(source) as zf: + zf.extractall(tdir) + except Exception: + prints('Corrupt ZIP file, trying to use local headers') + from calibre.utils.localunzip import extractall + extractall(source, tdir) + elif source.lower().endswith('.rar'): + from calibre.utils.unrar import extract + extract(source, tdir) + return tdir + try: if isinstance(self.source, basestring): - if os.path.isdir(self.source): - root = self.source - else: - root = self.extract() - for dirpath, dirnames, filenames in os.walk(root): - for files in find_books_in_directory(dirpath, self.single_book_per_directory): - if self.abort_scan: - return - if files: - self.file_groups[len(self.file_groups)] = files + find_files(self.source) else: unreadable_files = [] for path in self.source: if self.abort_scan: return if os.access(path, os.R_OK): - self.file_groups[len(self.file_groups)] = [path] + if self.list_of_archives: + find_files(extract(path)) + else: + self.file_groups[len(self.file_groups)] = [path] else: unreadable_files.append(path) if unreadable_files: @@ -145,22 +166,6 @@ class Adder(QObject): except Exception: self.scan_error = traceback.format_exc() - def extract(self): - tdir = os.path.join(self.tdir, 'archive') - if self.source.lower().endswith('.zip'): - from calibre.utils.zipfile import ZipFile - try: - with ZipFile(self.source) as zf: - zf.extractall(tdir) - except Exception: - prints('Corrupt ZIP file, trying to use local headers') - from calibre.utils.localunzip import extractall - extractall(self.source, tdir) - elif self.path.lower().endswith('.rar'): - from calibre.utils.unrar import extract - extract(self.source, tdir) - return tdir - def monitor_scan(self): self.scan_thread.join(0.05) if self.scan_thread.is_alive():