diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 084b352f48..c33036e183 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -1,7 +1,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' """ The GUI """ -import sys, os, re, StringIO, traceback +import sys, os, re, StringIO, traceback, time from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \ QByteArray, QLocale, QUrl, QTranslator, QCoreApplication, \ QModelIndex @@ -14,6 +14,9 @@ from calibre import __author__, islinux, iswindows, isosx from calibre.startup import get_lang from calibre.utils.config import Config, ConfigProxy, dynamic import calibre.resources as resources +from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats +from calibre.ebooks.metadata import MetaInformation + NONE = QVariant() #: Null value to return from the data function of item models @@ -148,7 +151,41 @@ class Dispatcher(QObject): def dispatch(self, args, kwargs): self.func(*args, **kwargs) + +class GetMetadata(QObject): + ''' + Convenience class to ensure that metadata readers are used only in the + GUI thread. Must be instantiated in the GUI thread. + ''' + + def __init__(self): + QObject.__init__(self) + self.connect(self, SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), + self._get_metadata, Qt.QueuedConnection) + self.connect(self, SIGNAL('idispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), + self._from_formats, Qt.QueuedConnection) + def __call__(self, id, *args, **kwargs): + self.emit(SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), + id, args, kwargs) + + def from_formats(self, id, *args, **kwargs): + self.emit(SIGNAL('idispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), + id, args, kwargs) + + def _from_formats(self, id, args, kwargs): + try: + mi = metadata_from_formats(*args, **kwargs) + except: + mi = MetaInformation('', [_('Unknown')]) + self.emit(SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), id, mi) + + def _get_metadata(self, id, args, kwargs): + try: + mi = get_metadata(*args, **kwargs) + except: + mi = MetaInformation('', [_('Unknown')]) + self.emit(SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'), id, mi) class TableView(QTableView): def __init__(self, parent): diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py new file mode 100644 index 0000000000..a6c6c699e8 --- /dev/null +++ b/src/calibre/gui2/add.py @@ -0,0 +1,224 @@ +''' +UI for adding books to the database +''' +import os + +from PyQt4.Qt import QThread, SIGNAL, QMutex, QWaitCondition, Qt + +from calibre.gui2.dialogs.progress import ProgressDialog +from calibre.constants import preferred_encoding +from calibre.gui2.widgets import WarningDialog + +class Add(QThread): + + def __init__(self): + QThread.__init__(self) + self._lock = QMutex() + self._waiting = QWaitCondition() + + def is_canceled(self): + if self.pd.canceled: + self.canceled = True + return self.canceled + + def wait_for_condition(self): + self._lock.lock() + self._waiting.wait(self._lock) + self._lock.unlock() + + def wake_up(self): + self._waiting.wakeAll() + +class AddFiles(Add): + + def __init__(self, paths, default_thumbnail, get_metadata, db=None): + Add.__init__(self) + self.paths = paths + self.get_metadata = get_metadata + self.default_thumbnail = default_thumbnail + self.db = db + self.formats, self.metadata, self.names, self.infos = [], [], [], [] + self.duplicates = [] + self.number_of_books_added = 0 + self.connect(self.get_metadata, + SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'), + self.metadata_delivered) + + def metadata_delivered(self, id, mi): + if self.is_canceled(): + self.reading.wakeAll() + return + if not mi.title: + mi.title = os.path.splitext(self.names[id])[0] + mi.title = mi.title if isinstance(mi.title, unicode) else \ + mi.title.decode(preferred_encoding, 'replace') + self.metadata.append(mi) + self.infos.append({'title':mi.title, + 'authors':', '.join(mi.authors), + 'cover':self.default_thumbnail, 'tags':[]}) + if self.db is not None: + duplicates, num = self.db.add_books(self.paths[id:id+1], + self.formats[id:id+1], [mi], + add_duplicates=False) + self.number_of_books_added += num + if duplicates: + if not self.duplicates: + self.duplicates = [[], [], [], []] + for i in range(4): + self.duplicates[i] += duplicates[i] + self.emit(SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'), + mi.title, id) + self.wake_up() + + def create_progress_dialog(self, title, msg, parent): + self._parent = parent + self.pd = ProgressDialog(title, msg, -1, len(self.paths)-1, parent) + self.connect(self, SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'), + self.update_progress_dialog) + self.pd.setModal(True) + self.pd.show() + self.connect(self, SIGNAL('finished()'), self.pd.hide) + return self.pd + + + def update_progress_dialog(self, title, count): + self.pd.set_value(count) + if self.db is not None: + self.pd.set_msg(_('Added %s to library')%title) + else: + self.pd.set_msg(_('Read metadata from ')+title) + + + def run(self): + self.canceled = False + for c, book in enumerate(self.paths): + if self.pd.canceled: + self.canceled = True + break + format = os.path.splitext(book)[1] + format = format[1:] if format else None + stream = open(book, 'rb') + self.formats.append(format) + self.names.append(os.path.basename(book)) + self.get_metadata(c, stream, stream_type=format, + use_libprs_metadata=True) + self.wait_for_condition() + + + def process_duplicates(self): + if self.duplicates: + files = _('

Books with the same title as the following already ' + 'exist in the database. Add them anyway?

', parent=self._parent) + if d.exec_() == d.Accepted: + num = self.db.add_books(*self.duplicates, + **dict(add_duplicates=True))[1] + self.number_of_books_added += num + + +class AddRecursive(Add): + + def __init__(self, path, db, get_metadata, single_book_per_directory, parent): + self.path = path + self.db = db + self.get_metadata = get_metadata + self.single_book_per_directory = single_book_per_directory + self.duplicates, self.books, self.metadata = [], [], [] + self.number_of_books_added = 0 + self.canceled = False + Add.__init__(self) + self.connect(self.get_metadata, + SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), + self.metadata_delivered, Qt.QueuedConnection) + self.connect(self, SIGNAL('searching_done()'), self.searching_done, + Qt.QueuedConnection) + self._parent = parent + self.pd = ProgressDialog(_('Adding books recursively...'), + _('Searching for books in all sub-directories...'), + 0, 0, parent) + self.connect(self, SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'), + self.update_progress_dialog) + self.connect(self, SIGNAL('update(PyQt_PyObject)'), self.pd.set_msg, + Qt.QueuedConnection) + self.connect(self, SIGNAL('pupdate(PyQt_PyObject)'), self.pd.set_value, + Qt.QueuedConnection) + self.pd.setModal(True) + self.pd.show() + self.connect(self, SIGNAL('finished()'), self.pd.hide) + + def update_progress_dialog(self, title, count): + self.pd.set_value(count) + if title: + self.pd.set_msg(_('Read metadata from ')+title) + + def metadata_delivered(self, id, mi): + if self.is_canceled(): + self.reading.wakeAll() + return + self.emit(SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'), + mi.title, id) + self.metadata.append((mi if mi.title else None, self.books[id])) + if len(self.metadata) >= len(self.books): + self.metadata = [x for x in self.metadata if x[0] is not None] + self.pd.set_min(-1) + self.pd.set_max(len(self.metadata)-1) + self.pd.set_value(-1) + self.pd.set_msg(_('Adding books to database...')) + self.wake_up() + + def searching_done(self): + self.pd.set_min(-1) + self.pd.set_max(len(self.books)-1) + self.pd.set_value(-1) + self.pd.set_msg(_('Reading metadata...')) + + + def run(self): + root = os.path.abspath(self.path) + for dirpath in os.walk(root): + if self.is_canceled(): + return + self.emit(SIGNAL('update(PyQt_PyObject)'), + _('Searching in')+' '+dirpath[0]) + self.books += list(self.db.find_books_in_directory(dirpath[0], + self.single_book_per_directory)) + self.books = [formats for formats in self.books if formats] + # Reset progress bar + self.emit(SIGNAL('searching_done()')) + + for c, formats in enumerate(self.books): + self.get_metadata.from_formats(c, formats) + self.wait_for_condition() + + # Add books to database + for c, x in enumerate(self.metadata): + mi, formats = x + if self.is_canceled(): + break + if self.db.has_book(mi): + self.duplicates.append((mi, formats)) + else: + self.db.import_book(mi, formats, notify=False) + self.number_of_books_added += 1 + self.emit(SIGNAL('pupdate(PyQt_PyObject)'), c) + + + def process_duplicates(self): + if self.duplicates: + files = _('

Books with the same title as the following already ' + 'exist in the database. Add them anyway?

', parent=self._parent) + if d.exec_() == d.Accepted: + for mi, formats in self.duplicates: + self.db.import_book(mi, formats, notify=False) + self.number_of_books_added += 1 + + \ No newline at end of file diff --git a/src/calibre/gui2/dialogs/confirm_delete.py b/src/calibre/gui2/dialogs/confirm_delete.py index 08db53e9a7..8c496987fb 100644 --- a/src/calibre/gui2/dialogs/confirm_delete.py +++ b/src/calibre/gui2/dialogs/confirm_delete.py @@ -5,7 +5,7 @@ __docformat__ = 'restructuredtext en' from calibre.gui2 import dynamic from calibre.gui2.dialogs.confirm_delete_ui import Ui_Dialog -from PyQt4.Qt import QDialog, SIGNAL +from PyQt4.Qt import QDialog, SIGNAL, Qt def _config_name(name): return name + '_again' @@ -19,6 +19,7 @@ class Dialog(QDialog, Ui_Dialog): self.msg.setText(msg) self.name = name self.connect(self.again, SIGNAL('stateChanged(int)'), self.toggle) + self.buttonBox.setFocus(Qt.OtherFocusReason) def toggle(self, x): diff --git a/src/calibre/gui2/dialogs/progress.py b/src/calibre/gui2/dialogs/progress.py index 2543cefb4d..0f64d7b041 100644 --- a/src/calibre/gui2/dialogs/progress.py +++ b/src/calibre/gui2/dialogs/progress.py @@ -20,6 +20,7 @@ class ProgressDialog(QDialog, Ui_Dialog): self.setWindowModality(Qt.ApplicationModal) self.set_min(min) self.set_max(max) + self.bar.setValue(min) self.canceled = False self.connect(self.button_box, SIGNAL('rejected()'), self._canceled) diff --git a/src/calibre/gui2/images/news/pobjeda.png b/src/calibre/gui2/images/news/pobjeda.png new file mode 100644 index 0000000000..d7612b4e9e Binary files /dev/null and b/src/calibre/gui2/images/news/pobjeda.png differ diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 3649a2264b..c2e755a742 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -13,7 +13,6 @@ from PyQt4.QtSvg import QSvgRenderer from calibre import __version__, __appname__, islinux, sanitize_file_name, \ iswindows, isosx, preferred_encoding from calibre.ptempfile import PersistentTemporaryFile -from calibre.ebooks.metadata.meta import get_metadata from calibre.devices.errors import FreeSpaceError from calibre.devices.interface import Device from calibre.utils.config import prefs, dynamic @@ -23,7 +22,7 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ set_sidebar_directories, Dispatcher, \ SingleApplication, Application, available_height, \ max_available_height, config, info_dialog, \ - available_width + available_width, GetMetadata from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.update import CheckForUpdates @@ -78,6 +77,7 @@ class Main(MainWindow, Ui_MainWindow): self.setupUi(self) self.setWindowTitle(__appname__) self.verbose = opts.verbose + self.get_metadata = GetMetadata() self.read_settings() self.job_manager = JobManager() self.jobs_dialog = JobsDialog(self, self.job_manager) @@ -608,36 +608,26 @@ class Main(MainWindow, Ui_MainWindow): ################################# Add books ################################ def add_recursive(self, single): - root = choose_dir(self, 'recursive book import root dir dialog', 'Select root folder') + root = choose_dir(self, 'recursive book import root dir dialog', + 'Select root folder') if not root: return - progress = ProgressDialog(_('Adding books recursively...'), - min=0, max=0, parent=self) - progress.show() - def callback(msg): - if msg != '.': - progress.set_msg((_('Added ')+msg) if msg else _('Searching...')) - QApplication.processEvents() - QApplication.sendPostedEvents() - QApplication.flush() - return progress.canceled - try: - duplicates = self.library_view.model().db.recursive_import(root, single, callback=callback) - finally: - progress.hide() - if duplicates: - files = _('

Books with the same title as the following already exist in the database. Add them anyway?

', self) - if d.exec_() == QDialog.Accepted: - for mi, formats in duplicates: - self.library_view.model().db.import_book(mi, formats ) - - self.library_view.model().resort() - self.library_view.model().research() - + from calibre.gui2.add import AddRecursive + self._add_recursive_thread = AddRecursive(root, + self.library_view.model().db, self.get_metadata, + single, self) + self.connect(self._add_recursive_thread, SIGNAL('finished()'), + self._recursive_files_added) + self._add_recursive_thread.start() + + def _recursive_files_added(self): + self._add_recursive_thread.process_duplicates() + if self._add_recursive_thread.number_of_books_added > 0: + self.library_view.model().resort(reset=False) + self.library_view.model().research() + self.library_view.model().count_changed() + self._add_recursive_thread = None + def add_recursive_single(self, checked): ''' Add books from the local filesystem to either the library or the device @@ -686,66 +676,41 @@ class Main(MainWindow, Ui_MainWindow): return to_device = self.stack.currentIndex() != 0 self._add_books(books, to_device) - if to_device: - self.status_bar.showMessage(_('Uploading books to device.'), 2000) + def _add_books(self, paths, to_device, on_card=None): if on_card is None: on_card = self.stack.currentIndex() == 2 if not paths: return - # Get format and metadata information - formats, metadata, names, infos = [], [], [], [] - progress = ProgressDialog(_('Adding books...'), _('Reading metadata...'), - min=0, max=len(paths), parent=self) - progress.show() - try: - for c, book in enumerate(paths): - progress.set_value(c+1) - if progress.canceled: - return - format = os.path.splitext(book)[1] - format = format[1:] if format else None - stream = open(book, 'rb') - try: - mi = get_metadata(stream, stream_type=format, use_libprs_metadata=True) - except: - mi = MetaInformation(None, None) - if not mi.title: - mi.title = os.path.splitext(os.path.basename(book))[0] - if not mi.authors: - mi.authors = [_('Unknown')] - formats.append(format) - metadata.append(mi) - names.append(os.path.basename(book)) - infos.append({'title':mi.title, 'authors':', '.join(mi.authors), - 'cover':self.default_thumbnail, 'tags':[]}) - title = mi.title if isinstance(mi.title, unicode) else mi.title.decode(preferred_encoding, 'replace') - progress.set_msg(_('Read metadata from ')+title) - QApplication.processEvents() - - if not to_device: - progress.set_msg(_('Adding books to database...')) - QApplication.processEvents() - model = self.library_view.model() - - paths = list(paths) - duplicates, number_added = model.add_books(paths, formats, metadata) - if duplicates: - files = _('

Books with the same title as the following already exist in the database. Add them anyway?

', parent=self) - if d.exec_() == QDialog.Accepted: - num = model.add_books(*duplicates, **dict(add_duplicates=True))[1] - number_added += num - model.books_added(number_added) + from calibre.gui2.add import AddFiles + self._add_files_thread = AddFiles(paths, self.default_thumbnail, + self.get_metadata, + None if to_device else \ + self.library_view.model().db + ) + self._add_files_thread.send_to_device = to_device + self._add_files_thread.on_card = on_card + self._add_files_thread.create_progress_dialog(_('Adding books...'), + _('Reading metadata...'), self) + self.connect(self._add_files_thread, SIGNAL('finished()'), + self._files_added) + self._add_files_thread.start() + + def _files_added(self): + t = self._add_files_thread + self._add_files_thread = None + if not t.canceled: + if t.send_to_device: + self.upload_books(t.paths, + list(map(sanitize_file_name, t.names)), + t.infos, on_card=t.on_card) + self.status_bar.showMessage(_('Uploading books to device.'), 2000) else: - self.upload_books(paths, list(map(sanitize_file_name, names)), infos, on_card=on_card) - finally: - QApplication.processEvents() - progress.hide() - + t.process_duplicates() + if t.number_of_books_added > 0: + self.library_view.model().books_added(t.number_of_books_added) + def upload_books(self, files, names, metadata, on_card=False, memory=None): ''' Upload books to device. @@ -801,7 +766,10 @@ class Main(MainWindow, Ui_MainWindow): if not rows or len(rows) == 0: return if self.stack.currentIndex() == 0: - if not confirm('

'+_('The selected books will be permanently deleted and the files removed from your computer. Are you sure?')+'

', 'library_delete_books', self): + if not confirm('

'+_('The selected books will be ' + 'permanently deleted and the files ' + 'removed from your computer. Are you sure?') + +'

', 'library_delete_books', self): return view.model().delete_books(rows) else: @@ -1410,8 +1378,15 @@ class Main(MainWindow, Ui_MainWindow): def initialize_database(self): self.library_path = prefs['library_path'] if self.library_path is None: # Need to migrate to new database layout + base = os.path.expanduser('~') + if iswindows: + from calibre import plugins + from PyQt4.Qt import QDir + base = plugins['winutil'][0].special_folder_path(plugins['winutil'][0].CSIDL_PERSONAL) + if not base or not os.path.exists(base): + base = unicode(QDir.homePath()).replace('/', os.sep) dir = unicode(QFileDialog.getExistingDirectory(self, - _('Choose a location for your ebook library.'), os.getcwd())) + _('Choose a location for your ebook library.'), base)) if not dir: dir = os.path.expanduser('~/Library') self.library_path = os.path.abspath(dir) diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py index 37fdeb4ce4..e4fa71feed 100644 --- a/src/calibre/library/database.py +++ b/src/calibre/library/database.py @@ -4,13 +4,10 @@ __copyright__ = '2008, Kovid Goyal ' Backend that implements storage of ebooks in an sqlite database. ''' import sqlite3 as sqlite -import datetime, re, os, cPickle, sre_constants +import datetime, re, cPickle, sre_constants from zlib import compress, decompress -from calibre import sanitize_file_name -from calibre.ebooks.metadata.meta import set_metadata, metadata_from_formats from calibre.ebooks.metadata import MetaInformation -from calibre.ebooks import BOOK_EXTENSIONS from calibre.web.feeds.recipes import migrate_automatic_profile_to_automatic_recipe class Concatenate(object): @@ -1391,117 +1388,12 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; - def import_book(self, mi, formats): - series_index = 1 if mi.series_index is None else mi.series_index - if not mi.authors: - mi.authors = [_('Unknown')] - aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors) - obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', - (mi.title, None, series_index, aus)) - id = obj.lastrowid - self.conn.commit() - self.set_metadata(id, mi) - for path in formats: - ext = os.path.splitext(path)[1][1:].lower() - stream = open(path, 'rb') - stream.seek(0, 2) - usize = stream.tell() - stream.seek(0) - data = sqlite.Binary(compress(stream.read())) - try: - self.conn.execute('INSERT INTO data(book, format, uncompressed_size, data) VALUES (?,?,?,?)', - (id, ext, usize, data)) - except sqlite.IntegrityError: - self.conn.execute('UPDATE data SET uncompressed_size=?, data=? WHERE book=? AND format=?', - (usize, data, id, ext)) - self.conn.commit() - - def import_book_directory_multiple(self, dirpath, callback=None): - dirpath = os.path.abspath(dirpath) - duplicates = [] - books = {} - for path in os.listdir(dirpath): - if callable(callback): - callback('.') - path = os.path.abspath(os.path.join(dirpath, path)) - if os.path.isdir(path) or not os.access(path, os.R_OK): - continue - ext = os.path.splitext(path)[1] - if not ext: - continue - ext = ext[1:].lower() - if ext not in BOOK_EXTENSIONS: - continue - - key = os.path.splitext(path)[0] - if not books.has_key(key): - books[key] = [] - - books[key].append(path) - - for formats in books.values(): - mi = metadata_from_formats(formats) - if mi.title is None: - continue - if self.has_book(mi): - duplicates.append((mi, formats)) - continue - self.import_book(mi, formats) - if callable(callback): - if callback(mi.title): - break - return duplicates - - - def import_book_directory(self, dirpath, callback=None): - dirpath = os.path.abspath(dirpath) - formats = [] - for path in os.listdir(dirpath): - if callable(callback): - callback('.') - path = os.path.abspath(os.path.join(dirpath, path)) - if os.path.isdir(path) or not os.access(path, os.R_OK): - continue - ext = os.path.splitext(path)[1] - if not ext: - continue - ext = ext[1:].lower() - if ext not in BOOK_EXTENSIONS: - continue - formats.append(path) - - if not formats: - return - - mi = metadata_from_formats(formats) - if mi.title is None: - return - if self.has_book(mi): - return [(mi, formats)] - self.import_book(mi, formats) - if callable(callback): - callback(mi.title) - - + def has_id(self, id): return self.conn.get('SELECT id FROM books where id=?', (id,), all=False) is not None - def recursive_import(self, root, single_book_per_directory=True, callback=None): - root = os.path.abspath(root) - duplicates = [] - for dirpath in os.walk(root): - res = self.import_book_directory(dirpath[0], callback=callback) if \ - single_book_per_directory else \ - self.import_book_directory_multiple(dirpath[0], callback=callback) - if res is not None: - duplicates.extend(res) - if callable(callback): - if callback(''): - break - - return duplicates - + class SearchToken(object): diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 8c762f8680..a7f522cad0 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -12,7 +12,7 @@ from itertools import repeat from datetime import datetime from PyQt4.QtCore import QCoreApplication, QThread, QReadWriteLock -from PyQt4.QtGui import QApplication, QPixmap, QImage +from PyQt4.QtGui import QApplication, QImage __app = None from calibre.library import title_sort @@ -20,12 +20,15 @@ from calibre.library.database import LibraryDatabase from calibre.library.sqlite import connect, IntegrityError from calibre.utils.search_query_parser import SearchQueryParser from calibre.ebooks.metadata import string_to_authors, authors_to_string, MetaInformation -from calibre.ebooks.metadata.meta import get_metadata, set_metadata +from calibre.ebooks.metadata.meta import get_metadata, set_metadata, \ + metadata_from_formats from calibre.ebooks.metadata.opf2 import OPFCreator from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding from calibre.ptempfile import PersistentTemporaryFile from calibre.customize.ui import run_plugins_on_import + from calibre import sanitize_file_name +from calibre.ebooks import BOOK_EXTENSIONS copyfile = os.link if hasattr(os, 'link') else shutil.copyfile @@ -627,7 +630,7 @@ class LibraryDatabase2(LibraryDatabase): if not QCoreApplication.instance(): global __app __app = QApplication([]) - p = QPixmap() + p = QImage() if callable(getattr(data, 'read', None)): data = data.read() p.loadFromData(data) @@ -1142,7 +1145,7 @@ class LibraryDatabase2(LibraryDatabase): def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True): ''' Add a book to the database. The result cache is not updated. - @param paths: List of paths to book files or file-like objects + :param:`paths` List of paths to book files or file-like objects ''' formats, metadata, uris = iter(formats), iter(metadata), iter(uris) duplicates = [] @@ -1180,17 +1183,17 @@ class LibraryDatabase2(LibraryDatabase): self.conn.commit() self.data.refresh_ids(self.conn, ids) # Needed to update format list and size if duplicates: - paths = tuple(duplicate[0] for duplicate in duplicates) - formats = tuple(duplicate[1] for duplicate in duplicates) - metadata = tuple(duplicate[2] for duplicate in duplicates) - uris = tuple(duplicate[3] for duplicate in duplicates) + paths = list(duplicate[0] for duplicate in duplicates) + formats = list(duplicate[1] for duplicate in duplicates) + metadata = list(duplicate[2] for duplicate in duplicates) + uris = list(duplicate[3] for duplicate in duplicates) return (paths, formats, metadata, uris), len(ids) return None, len(ids) - def import_book(self, mi, formats): + def import_book(self, mi, formats, notify=True): series_index = 1 if mi.series_index is None else mi.series_index if not mi.authors: - mi.authors = ['Unknown'] + mi.authors = [_('Unknown')] aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors) obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', (mi.title, None, series_index, aus)) @@ -1204,7 +1207,8 @@ class LibraryDatabase2(LibraryDatabase): self.add_format(id, ext, stream, index_is_id=True) self.conn.commit() self.data.refresh_ids(self.conn, [id]) # Needed to update format list and size - self.notify('add', [id]) + if notify: + self.notify('add', [id]) def move_library_to(self, newloc, progress=None): header = _(u'

Copying books to %s

')%newloc @@ -1388,6 +1392,10 @@ books_series_link feeds f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb') if not mi.authors: mi.authors = [_('Unknown')] + cdata = self.cover(id, index_is_id=True) + cname = sanitize_file_name(name)+'.jpg' + open(os.path.join(base, cname), 'wb').write(cdata) + mi.cover = cname opf = OPFCreator(base, mi) opf.render(f) f.close() @@ -1451,6 +1459,88 @@ books_series_link feeds if not callback(count, title): break return failures + + def find_books_in_directory(self, dirpath, single_book_per_directory): + dirpath = os.path.abspath(dirpath) + if single_book_per_directory: + formats = [] + for path in os.listdir(dirpath): + path = os.path.abspath(os.path.join(dirpath, path)) + if os.path.isdir(path) or not os.access(path, os.R_OK): + continue + ext = os.path.splitext(path)[1] + if not ext: + continue + ext = ext[1:].lower() + if ext not in BOOK_EXTENSIONS: + continue + formats.append(path) + yield formats + else: + books = {} + for path in os.listdir(dirpath): + path = os.path.abspath(os.path.join(dirpath, path)) + if os.path.isdir(path) or not os.access(path, os.R_OK): + continue + ext = os.path.splitext(path)[1] + if not ext: + continue + ext = ext[1:].lower() + if ext not in BOOK_EXTENSIONS: + continue + + key = os.path.splitext(path)[0] + if not books.has_key(key): + books[key] = [] + books[key].append(path) + + for formats in books.values(): + yield formats + + def import_book_directory_multiple(self, dirpath, callback=None): + duplicates = [] + for formats in self.find_books_in_directory(dirpath, False): + mi = metadata_from_formats(formats) + if mi.title is None: + continue + if self.has_book(mi): + duplicates.append((mi, formats)) + continue + self.import_book(mi, formats) + if callable(callback): + if callback(mi.title): + break + return duplicates + + def import_book_directory(self, dirpath, callback=None): + dirpath = os.path.abspath(dirpath) + formats = self.find_books_in_directory(dirpath, True) + if not formats: + return + + mi = metadata_from_formats(formats) + if mi.title is None: + return + if self.has_book(mi): + return [(mi, formats)] + self.import_book(mi, formats) + if callable(callback): + callback(mi.title) + + def recursive_import(self, root, single_book_per_directory=True, callback=None): + root = os.path.abspath(root) + duplicates = [] + for dirpath in os.walk(root): + res = self.import_book_directory(dirpath[0], callback=callback) if \ + single_book_per_directory else \ + self.import_book_directory_multiple(dirpath[0], callback=callback) + if res is not None: + duplicates.extend(res) + if callable(callback): + if callback(''): + break + + return duplicates diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py index 60ae0761cf..dcbec14687 100644 --- a/src/calibre/web/feeds/recipes/__init__.py +++ b/src/calibre/web/feeds/recipes/__init__.py @@ -28,6 +28,7 @@ recipe_modules = ['recipe_' + r for r in ( 'la_tercera', 'el_mercurio_chile', 'la_cuarta', 'lanacion_chile', 'la_segunda', 'jb_online', 'estadao', 'o_globo', 'vijesti', 'elmundo', 'the_oz', 'honoluluadvertiser', 'starbulletin', 'exiled', 'indy_star', 'dna', + 'pobjeda', )] import re, imp, inspect, time, os diff --git a/src/calibre/web/feeds/recipes/recipe_pobjeda.py b/src/calibre/web/feeds/recipes/recipe_pobjeda.py new file mode 100644 index 0000000000..9a4dbb0eee --- /dev/null +++ b/src/calibre/web/feeds/recipes/recipe_pobjeda.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' + +''' +pobjeda.co.me +''' + +import re +from calibre import strftime +from calibre.web.feeds.news import BasicNewsRecipe + +class Pobjeda(BasicNewsRecipe): + title = 'Pobjeda Online' + __author__ = 'Darko Miletic' + description = 'News from Montenegro' + publisher = 'Pobjeda a.d.' + category = 'news, politics, Montenegro' + language = _('Serbian') + oldest_article = 2 + max_articles_per_feed = 100 + no_stylesheets = True + remove_javascript = True + encoding = 'utf8' + remove_javascript = True + use_embedded_content = False + INDEX = u'http://www.pobjeda.co.me' + extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{text-align: justify; font-family: serif1, serif} .article_description{font-family: serif1, serif}' + + html2lrf_options = [ + '--comment', description + , '--category', category + , '--publisher', publisher + ] + + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' + + preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] + + keep_only_tags = [dict(name='div', attrs={'class':'vijest'})] + + remove_tags = [dict(name=['object','link'])] + + feeds = [ + (u'Politika' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=1' ) + ,(u'Ekonomija' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=2' ) + ,(u'Drustvo' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=3' ) + ,(u'Crna Hronika' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=4' ) + ,(u'Kultura' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=5' ) + ,(u'Hronika Podgorice' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=7' ) + ,(u'Feljton' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=8' ) + ,(u'Crna Gora' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=9' ) + ,(u'Svijet' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=202') + ,(u'Ekonomija i Biznis', u'http://www.pobjeda.co.me/dodatak.php?rubrika=11' ) + ,(u'Djeciji Svijet' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=12' ) + ,(u'Kultura i Drustvo' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=13' ) + ,(u'Agora' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=133') + ,(u'Ekologija' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=252') + ] + + def preprocess_html(self, soup): + soup.html['xml:lang'] = 'sr-Latn-ME' + soup.html['lang'] = 'sr-Latn-ME' + mtag = '' + soup.head.insert(0,mtag) + for item in soup.findAll(style=True): + del item['style'] + return soup + + def get_cover_url(self): + cover_url = None + soup = self.index_to_soup(self.INDEX) + cover_item = soup.find('img',attrs={'alt':'Naslovna strana'}) + if cover_item: + cover_url = self.INDEX + cover_item.parent['href'] + return cover_url + + def parse_index(self): + totalfeeds = [] + lfeeds = self.get_feeds() + for feedobj in lfeeds: + feedtitle, feedurl = feedobj + self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl)) + articles = [] + soup = self.index_to_soup(feedurl) + for item in soup.findAll('div', attrs={'class':'vijest'}): + description = self.tag_to_string(item.h2) + atag = item.h1.find('a') + if atag: + url = self.INDEX + '/' + atag['href'] + title = self.tag_to_string(atag) + date = strftime(self.timefmt) + articles.append({ + 'title' :title + ,'date' :date + ,'url' :url + ,'description':description + }) + totalfeeds.append((feedtitle, articles)) + return totalfeeds +