mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk
This commit is contained in:
commit
4d97671e26
@ -76,6 +76,15 @@ def _config():
|
|||||||
'only take place when the Enter or Return key is pressed.')
|
'only take place when the Enter or Return key is pressed.')
|
||||||
c.add_opt('save_to_disk_template_history', default=[],
|
c.add_opt('save_to_disk_template_history', default=[],
|
||||||
help='Previously used Save to Disk templates')
|
help='Previously used Save to Disk templates')
|
||||||
|
c.add_opt('main_search_history', default=[],
|
||||||
|
help='Search history for the main GUI')
|
||||||
|
c.add_opt('viewer_search_history', default=[],
|
||||||
|
help='Search history for the ebook viewer')
|
||||||
|
c.add_opt('lrf_viewer_search_history', default=[],
|
||||||
|
help='Search history for the LRF viewer')
|
||||||
|
c.add_opt('scheduler_search_history', default=[],
|
||||||
|
help='Search history for the recipe scheduler')
|
||||||
|
|
||||||
return ConfigProxy(c)
|
return ConfigProxy(c)
|
||||||
|
|
||||||
config = _config()
|
config = _config()
|
||||||
|
@ -156,7 +156,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.formats.takeItem(row.row())
|
self.formats.takeItem(row.row())
|
||||||
self.formats_changed = True
|
self.formats_changed = True
|
||||||
|
|
||||||
def set_cover(self):
|
def get_selected_format_metadata(self):
|
||||||
row = self.formats.currentRow()
|
row = self.formats.currentRow()
|
||||||
fmt = self.formats.item(row)
|
fmt = self.formats.item(row)
|
||||||
if fmt is None:
|
if fmt is None:
|
||||||
@ -165,7 +165,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
if fmt is None:
|
if fmt is None:
|
||||||
error_dialog(self, _('No format selected'),
|
error_dialog(self, _('No format selected'),
|
||||||
_('No format selected')).exec_()
|
_('No format selected')).exec_()
|
||||||
return
|
return None, None
|
||||||
ext = fmt.ext.lower()
|
ext = fmt.ext.lower()
|
||||||
if fmt.path is None:
|
if fmt.path is None:
|
||||||
stream = self.db.format(self.row, ext, as_file=True)
|
stream = self.db.format(self.row, ext, as_file=True)
|
||||||
@ -173,9 +173,44 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
stream = open(fmt.path, 'r+b')
|
stream = open(fmt.path, 'r+b')
|
||||||
try:
|
try:
|
||||||
mi = get_metadata(stream, ext)
|
mi = get_metadata(stream, ext)
|
||||||
|
return mi, ext
|
||||||
except:
|
except:
|
||||||
error_dialog(self, _('Could not read metadata'),
|
error_dialog(self, _('Could not read metadata'),
|
||||||
_('Could not read metadata from %s format')%ext).exec_()
|
_('Could not read metadata from %s format')%ext).exec_()
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def set_metadata_from_format(self):
|
||||||
|
mi, ext = self.get_selected_format_metadata()
|
||||||
|
if mi is None:
|
||||||
|
return
|
||||||
|
if mi.title:
|
||||||
|
self.title.setText(mi.title)
|
||||||
|
if mi.authors:
|
||||||
|
self.authors.setEditText(authors_to_string(mi.authors))
|
||||||
|
if mi.author_sort:
|
||||||
|
self.author_sort.setText(mi.author_sort)
|
||||||
|
if mi.rating is not None:
|
||||||
|
try:
|
||||||
|
self.rating.setValue(mi.rating)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if mi.publisher:
|
||||||
|
self.publisher.setEditText(mi.publisher)
|
||||||
|
if mi.tags:
|
||||||
|
self.tags.setText(', '.join(mi.tags))
|
||||||
|
if mi.isbn:
|
||||||
|
self.isbn.setText(mi.isbn)
|
||||||
|
if mi.pubdate:
|
||||||
|
self.pubdate.setDate(QDate(mi.pubdate.year, mi.pubdate.month,
|
||||||
|
mi.pubdate.day))
|
||||||
|
if mi.series:
|
||||||
|
self.series.setEditText(mi.series)
|
||||||
|
if mi.series_index is not None:
|
||||||
|
self.series_index.setValue(float(mi.series_index))
|
||||||
|
|
||||||
|
def set_cover(self):
|
||||||
|
mi, ext = self.get_selected_format_metadata()
|
||||||
|
if mi is None:
|
||||||
return
|
return
|
||||||
cdata = None
|
cdata = None
|
||||||
if mi.cover and os.access(mi.cover, os.R_OK):
|
if mi.cover and os.access(mi.cover, os.R_OK):
|
||||||
@ -253,6 +288,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.connect(self.formats, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
|
self.connect(self.formats, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
|
||||||
self.show_format)
|
self.show_format)
|
||||||
self.connect(self.button_set_cover, SIGNAL('clicked()'), self.set_cover)
|
self.connect(self.button_set_cover, SIGNAL('clicked()'), self.set_cover)
|
||||||
|
self.connect(self.button_set_metadata, SIGNAL('clicked()'),
|
||||||
|
self.set_metadata_from_format)
|
||||||
self.connect(self.reset_cover, SIGNAL('clicked()'), self.do_reset_cover)
|
self.connect(self.reset_cover, SIGNAL('clicked()'), self.do_reset_cover)
|
||||||
self.connect(self.swap_button, SIGNAL('clicked()'), self.swap_title_author)
|
self.connect(self.swap_button, SIGNAL('clicked()'), self.swap_title_author)
|
||||||
self.timeout = float(prefs['network_timeout'])
|
self.timeout = float(prefs['network_timeout'])
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>879</width>
|
<width>879</width>
|
||||||
<height>710</height>
|
<height>711</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
@ -415,7 +415,7 @@
|
|||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0" rowspan="3">
|
<item row="0" column="1" rowspan="3">
|
||||||
<widget class="QListWidget" name="formats">
|
<widget class="QListWidget" name="formats">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||||
@ -437,7 +437,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="2">
|
||||||
<widget class="QToolButton" name="add_format_button">
|
<widget class="QToolButton" name="add_format_button">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Add a new format for this book to the database</string>
|
<string>Add a new format for this book to the database</string>
|
||||||
@ -457,7 +457,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="2">
|
||||||
<widget class="QToolButton" name="remove_format_button">
|
<widget class="QToolButton" name="remove_format_button">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Remove the selected formats for this book from the database.</string>
|
<string>Remove the selected formats for this book from the database.</string>
|
||||||
@ -477,7 +477,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="0" column="0">
|
||||||
<widget class="QToolButton" name="button_set_cover">
|
<widget class="QToolButton" name="button_set_cover">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Set the cover for the book from the selected format</string>
|
<string>Set the cover for the book from the selected format</string>
|
||||||
@ -497,6 +497,26 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QToolButton" name="button_set_metadata">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Update metadata from the metadata in the selected format</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../images.qrc">
|
||||||
|
<normaloff>:/images/edit_input.svg</normaloff>:/images/edit_input.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -680,7 +700,6 @@
|
|||||||
<tabstop>fetch_metadata_button</tabstop>
|
<tabstop>fetch_metadata_button</tabstop>
|
||||||
<tabstop>formats</tabstop>
|
<tabstop>formats</tabstop>
|
||||||
<tabstop>add_format_button</tabstop>
|
<tabstop>add_format_button</tabstop>
|
||||||
<tabstop>button_set_cover</tabstop>
|
|
||||||
<tabstop>remove_format_button</tabstop>
|
<tabstop>remove_format_button</tabstop>
|
||||||
<tabstop>cover_path</tabstop>
|
<tabstop>cover_path</tabstop>
|
||||||
<tabstop>cover_button</tabstop>
|
<tabstop>cover_button</tabstop>
|
||||||
|
@ -9,12 +9,13 @@ Scheduler for automated recipe downloads
|
|||||||
|
|
||||||
import sys, copy, time
|
import sys, copy, time
|
||||||
from datetime import datetime, timedelta, date
|
from datetime import datetime, timedelta, date
|
||||||
from PyQt4.Qt import QDialog, QApplication, QLineEdit, QPalette, SIGNAL, QBrush, \
|
from PyQt4.Qt import QDialog, QApplication, SIGNAL, \
|
||||||
QColor, QAbstractItemModel, Qt, QVariant, QFont, QIcon, \
|
QColor, QAbstractItemModel, Qt, QVariant, QFont, QIcon, \
|
||||||
QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime, QModelIndex
|
QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime, QModelIndex
|
||||||
|
|
||||||
from calibre import english_sort
|
from calibre import english_sort
|
||||||
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
|
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
|
||||||
|
from calibre.gui2.search_box import SearchBox2
|
||||||
from calibre.web.feeds.recipes import recipes, recipe_modules, compile_recipe
|
from calibre.web.feeds.recipes import recipes, recipe_modules, compile_recipe
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.utils.pyparsing import ParseException
|
from calibre.utils.pyparsing import ParseException
|
||||||
@ -163,7 +164,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
|||||||
results.add(recipe)
|
results.add(recipe)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def search(self, query):
|
def search(self, query, refinement):
|
||||||
try:
|
try:
|
||||||
results = self.parse(unicode(query))
|
results = self.parse(unicode(query))
|
||||||
except ParseException:
|
except ParseException:
|
||||||
@ -176,6 +177,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
|||||||
if recipe in results:
|
if recipe in results:
|
||||||
self._map[category].append(recipe)
|
self._map[category].append(recipe)
|
||||||
self.reset()
|
self.reset()
|
||||||
|
self.emit(SIGNAL('searched(PyQt_PyObject)'), True)
|
||||||
|
|
||||||
def resort(self):
|
def resort(self):
|
||||||
self.recipes.sort()
|
self.recipes.sort()
|
||||||
@ -235,45 +237,6 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
|||||||
srecipe.schedule = recipe.schedule
|
srecipe.schedule = recipe.schedule
|
||||||
|
|
||||||
|
|
||||||
class Search(QLineEdit):
|
|
||||||
|
|
||||||
HELP_TEXT = _('Search')
|
|
||||||
INTERVAL = 500 #: Time to wait before emitting search signal
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
QLineEdit.__init__(self, *args)
|
|
||||||
self.default_palette = QApplication.palette(self)
|
|
||||||
self.gray = QPalette(self.default_palette)
|
|
||||||
self.gray.setBrush(QPalette.Text, QBrush(QColor('gray')))
|
|
||||||
self.connect(self, SIGNAL('editingFinished()'),
|
|
||||||
lambda : self.emit(SIGNAL('goto(PyQt_PyObject)'), unicode(self.text())))
|
|
||||||
self.clear_to_help_mode()
|
|
||||||
self.timer = None
|
|
||||||
self.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot)
|
|
||||||
|
|
||||||
def focusInEvent(self, ev):
|
|
||||||
self.setPalette(QApplication.palette(self))
|
|
||||||
if self.in_help_mode():
|
|
||||||
self.setText('')
|
|
||||||
return QLineEdit.focusInEvent(self, ev)
|
|
||||||
|
|
||||||
def in_help_mode(self):
|
|
||||||
return unicode(self.text()) == self.HELP_TEXT
|
|
||||||
|
|
||||||
def clear_to_help_mode(self):
|
|
||||||
self.setPalette(self.gray)
|
|
||||||
self.setText(self.HELP_TEXT)
|
|
||||||
|
|
||||||
def text_edited_slot(self, text):
|
|
||||||
text = unicode(text)
|
|
||||||
self.timer = self.startTimer(self.INTERVAL)
|
|
||||||
|
|
||||||
def timerEvent(self, event):
|
|
||||||
self.killTimer(event.timerId())
|
|
||||||
if event.timerId() == self.timer:
|
|
||||||
text = unicode(self.text())
|
|
||||||
self.emit(SIGNAL('search(PyQt_PyObject)'), text)
|
|
||||||
|
|
||||||
def encode_schedule(day, hour, minute):
|
def encode_schedule(day, hour, minute):
|
||||||
day = 1e7 * (day+1)
|
day = 1e7 * (day+1)
|
||||||
hour = 1e4 * (hour+1)
|
hour = 1e4 * (hour+1)
|
||||||
@ -291,7 +254,8 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
|||||||
def __init__(self, db, *args):
|
def __init__(self, db, *args):
|
||||||
QDialog.__init__(self, *args)
|
QDialog.__init__(self, *args)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.search = Search(self)
|
self.search = SearchBox2(self)
|
||||||
|
self.search.initialize('scheduler_search_history')
|
||||||
self.recipe_box.layout().insertWidget(0, self.search)
|
self.recipe_box.layout().insertWidget(0, self.search)
|
||||||
self.detail_box.setVisible(False)
|
self.detail_box.setVisible(False)
|
||||||
self._model = RecipeModel(db)
|
self._model = RecipeModel(db)
|
||||||
@ -308,7 +272,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
|||||||
self.connect(self.time, SIGNAL('timeChanged(QTime)'), self.do_schedule)
|
self.connect(self.time, SIGNAL('timeChanged(QTime)'), self.do_schedule)
|
||||||
for button in (self.daily_button, self.interval_button):
|
for button in (self.daily_button, self.interval_button):
|
||||||
self.connect(button, SIGNAL('toggled(bool)'), self.do_schedule)
|
self.connect(button, SIGNAL('toggled(bool)'), self.do_schedule)
|
||||||
self.connect(self.search, SIGNAL('search(PyQt_PyObject)'), self._model.search)
|
self.connect(self.search, SIGNAL('search(PyQt_PyObject,PyQt_PyObject)'), self._model.search)
|
||||||
|
self.connect(self._model, SIGNAL('searched(PyQt_PyObject)'),
|
||||||
|
self.search.search_done)
|
||||||
self.connect(self._model, SIGNAL('modelReset()'), lambda : self.detail_box.setVisible(False))
|
self.connect(self._model, SIGNAL('modelReset()'), lambda : self.detail_box.setVisible(False))
|
||||||
self.connect(self.download_all_button, SIGNAL('clicked()'),
|
self.connect(self.download_all_button, SIGNAL('clicked()'),
|
||||||
self.download_all)
|
self.download_all)
|
||||||
|
@ -8,10 +8,10 @@ from operator import attrgetter
|
|||||||
from math import cos, sin, pi
|
from math import cos, sin, pi
|
||||||
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
|
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
|
||||||
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
|
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
|
||||||
QPen, QStyle, QPainter, QLineEdit, \
|
QPen, QStyle, QPainter, \
|
||||||
QPalette, QImage, QApplication, QMenu, \
|
QImage, QApplication, QMenu, \
|
||||||
QStyledItemDelegate, QCompleter
|
QStyledItemDelegate, QCompleter
|
||||||
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, \
|
||||||
SIGNAL, QObject, QSize, QModelIndex, QDate
|
SIGNAL, QObject, QSize, QModelIndex, QDate
|
||||||
|
|
||||||
from calibre import strftime
|
from calibre import strftime
|
||||||
@ -1100,94 +1100,4 @@ class DeviceBooksModel(BooksModel):
|
|||||||
self.editable = editable
|
self.editable = editable
|
||||||
|
|
||||||
|
|
||||||
class SearchBox(QLineEdit):
|
|
||||||
|
|
||||||
INTERVAL = 1000 #: Time to wait before emitting search signal
|
|
||||||
|
|
||||||
def __init__(self, parent, help_text=_('Search (For Advanced Search click the button to the left)')):
|
|
||||||
QLineEdit.__init__(self, parent)
|
|
||||||
self.help_text = help_text
|
|
||||||
self.initial_state = True
|
|
||||||
self.as_you_type = True
|
|
||||||
self.default_palette = QApplication.palette(self)
|
|
||||||
self.gray = QPalette(self.default_palette)
|
|
||||||
self.gray.setBrush(QPalette.Text, QBrush(QColor('gray')))
|
|
||||||
self.prev_search = ''
|
|
||||||
self.timer = None
|
|
||||||
self.clear_to_help()
|
|
||||||
QObject.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot)
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_state(self):
|
|
||||||
self.setText('')
|
|
||||||
self.setPalette(self.default_palette)
|
|
||||||
self.setStyleSheet('QLineEdit { background-color: white; }')
|
|
||||||
|
|
||||||
def clear_to_help(self):
|
|
||||||
self.setPalette(self.gray)
|
|
||||||
self.setText(self.help_text)
|
|
||||||
self.home(False)
|
|
||||||
self.initial_state = True
|
|
||||||
self.setStyleSheet('QLineEdit { background-color: white; }')
|
|
||||||
self.emit(SIGNAL('cleared()'))
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.clear_to_help()
|
|
||||||
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), '', False)
|
|
||||||
|
|
||||||
def search_done(self, ok):
|
|
||||||
col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
|
|
||||||
self.setStyleSheet('QLineEdit { background-color: %s; }' % col)
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if self.initial_state:
|
|
||||||
self.normalize_state()
|
|
||||||
self.initial_state = False
|
|
||||||
if not self.as_you_type:
|
|
||||||
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
|
||||||
self.do_search()
|
|
||||||
QLineEdit.keyPressEvent(self, event)
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
|
||||||
if self.initial_state:
|
|
||||||
self.normalize_state()
|
|
||||||
self.initial_state = False
|
|
||||||
QLineEdit.mouseReleaseEvent(self, event)
|
|
||||||
|
|
||||||
def text_edited_slot(self, text):
|
|
||||||
if self.as_you_type:
|
|
||||||
text = qstring_to_unicode(text) if isinstance(text, QString) else unicode(text)
|
|
||||||
self.prev_text = text
|
|
||||||
self.timer = self.startTimer(self.__class__.INTERVAL)
|
|
||||||
|
|
||||||
def timerEvent(self, event):
|
|
||||||
self.killTimer(event.timerId())
|
|
||||||
if event.timerId() == self.timer:
|
|
||||||
self.do_search()
|
|
||||||
|
|
||||||
def do_search(self):
|
|
||||||
text = qstring_to_unicode(self.text())
|
|
||||||
refinement = text.startswith(self.prev_search) and ':' not in text
|
|
||||||
self.prev_search = text
|
|
||||||
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), text, refinement)
|
|
||||||
|
|
||||||
def search_from_tokens(self, tokens, all):
|
|
||||||
ans = u' '.join([u'%s:%s'%x for x in tokens])
|
|
||||||
if not all:
|
|
||||||
ans = '[' + ans + ']'
|
|
||||||
self.set_search_string(ans)
|
|
||||||
|
|
||||||
def search_from_tags(self, tags, all):
|
|
||||||
joiner = ' and ' if all else ' or '
|
|
||||||
self.set_search_string(joiner.join(tags))
|
|
||||||
|
|
||||||
def set_search_string(self, txt):
|
|
||||||
self.normalize_state()
|
|
||||||
self.setText(txt)
|
|
||||||
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), txt, False)
|
|
||||||
self.end(False)
|
|
||||||
self.initial_state = False
|
|
||||||
|
|
||||||
def search_as_you_type(self, enabled):
|
|
||||||
self.as_you_type = enabled
|
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from calibre.gui2.lrf_renderer.main_ui import Ui_MainWindow
|
|||||||
from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig
|
from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig
|
||||||
from calibre.gui2.main_window import MainWindow
|
from calibre.gui2.main_window import MainWindow
|
||||||
from calibre.gui2.lrf_renderer.document import Document
|
from calibre.gui2.lrf_renderer.document import Document
|
||||||
from calibre.gui2.library import SearchBox
|
from calibre.gui2.search_box import SearchBox2
|
||||||
|
|
||||||
class RenderWorker(QThread):
|
class RenderWorker(QThread):
|
||||||
|
|
||||||
@ -73,7 +73,8 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.slider_action = self.slider = QSlider(Qt.Horizontal)
|
self.slider_action = self.slider = QSlider(Qt.Horizontal)
|
||||||
self.tool_bar.addWidget(self.slider)
|
self.tool_bar.addWidget(self.slider)
|
||||||
self.tool_bar.addSeparator()
|
self.tool_bar.addSeparator()
|
||||||
self.search = SearchBox(self)
|
self.search = SearchBox2(self)
|
||||||
|
self.search.initialize('lrf_viewer_search_history')
|
||||||
self.search_action = self.tool_bar.addWidget(self.search)
|
self.search_action = self.tool_bar.addWidget(self.search)
|
||||||
QObject.connect(self.document, SIGNAL('chapter_rendered(int)'), self.chapter_rendered)
|
QObject.connect(self.document, SIGNAL('chapter_rendered(int)'), self.chapter_rendered)
|
||||||
QObject.connect(self.document, SIGNAL('page_changed(PyQt_PyObject)'), self.page_changed)
|
QObject.connect(self.document, SIGNAL('page_changed(PyQt_PyObject)'), self.page_changed)
|
||||||
@ -123,7 +124,6 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.progress_label.setText('Parsing '+ self.file_name)
|
self.progress_label.setText('Parsing '+ self.file_name)
|
||||||
self.renderer = RenderWorker(self, stream, self.logger, self.opts)
|
self.renderer = RenderWorker(self, stream, self.logger, self.opts)
|
||||||
QObject.connect(self.renderer, SIGNAL('finished()'), self.parsed, Qt.QueuedConnection)
|
QObject.connect(self.renderer, SIGNAL('finished()'), self.parsed, Qt.QueuedConnection)
|
||||||
self.search.help_text = 'Search'
|
|
||||||
self.search.clear_to_help()
|
self.search.clear_to_help()
|
||||||
self.last_search = None
|
self.last_search = None
|
||||||
else:
|
else:
|
||||||
@ -155,6 +155,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.document.search(search)
|
self.document.search(search)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
error_dialog(self, _('No matches found'), _('<b>No matches</b> for the search phrase <i>%s</i> were found.')%(search,)).exec_()
|
error_dialog(self, _('No matches found'), _('<b>No matches</b> for the search phrase <i>%s</i> were found.')%(search,)).exec_()
|
||||||
|
self.search.search_done(True)
|
||||||
|
|
||||||
def parsed(self):
|
def parsed(self):
|
||||||
if not self.renderer.aborted and self.renderer.lrf is not None:
|
if not self.renderer.aborted and self.renderer.lrf is not None:
|
||||||
|
@ -121,6 +121,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
Ui_MainWindow.__init__(self)
|
Ui_MainWindow.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.setWindowTitle(__appname__)
|
self.setWindowTitle(__appname__)
|
||||||
|
self.search.initialize('main_search_history', colorize=True,
|
||||||
|
help_text=_('Search (For Advanced Search click the button to the left)'))
|
||||||
|
self.connect(self.clear_button, SIGNAL('clicked()'), self.search.clear)
|
||||||
self.progress_indicator = ProgressIndicator(self)
|
self.progress_indicator = ProgressIndicator(self)
|
||||||
self.verbose = opts.verbose
|
self.verbose = opts.verbose
|
||||||
self.get_metadata = GetMetadata()
|
self.get_metadata = GetMetadata()
|
||||||
@ -148,7 +151,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.system_tray_icon.hide()
|
self.system_tray_icon.hide()
|
||||||
else:
|
else:
|
||||||
self.system_tray_icon.show()
|
self.system_tray_icon.show()
|
||||||
self.search.search_as_you_type(config['search_as_you_type'])
|
|
||||||
self.system_tray_menu = QMenu(self)
|
self.system_tray_menu = QMenu(self)
|
||||||
self.restore_action = self.system_tray_menu.addAction(
|
self.restore_action = self.system_tray_menu.addAction(
|
||||||
QIcon(':/images/page.svg'), _('&Restore'))
|
QIcon(':/images/page.svg'), _('&Restore'))
|
||||||
|
@ -185,33 +185,18 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="SearchBox" name="search">
|
<widget class="SearchBox2" name="search">
|
||||||
<property name="enabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
<horstretch>1</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="acceptDrops">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
<string><p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="whatsThis">
|
<property name="whatsThis">
|
||||||
<string>Search the list of books by title, author, publisher, tags and comments<br><br>Words separated by spaces are ANDed</string>
|
<string><p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed</string>
|
||||||
</property>
|
|
||||||
<property name="autoFillBackground">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="frame">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -673,11 +658,6 @@
|
|||||||
</action>
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
|
||||||
<class>SearchBox</class>
|
|
||||||
<extends>QLineEdit</extends>
|
|
||||||
<header>library.h</header>
|
|
||||||
</customwidget>
|
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>BooksView</class>
|
<class>BooksView</class>
|
||||||
<extends>QTableView</extends>
|
<extends>QTableView</extends>
|
||||||
@ -698,26 +678,14 @@
|
|||||||
<extends>QTreeView</extends>
|
<extends>QTreeView</extends>
|
||||||
<header>calibre/gui2/tag_view.h</header>
|
<header>calibre/gui2/tag_view.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>SearchBox2</class>
|
||||||
|
<extends>QComboBox</extends>
|
||||||
|
<header>calibre.gui2.search_box</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="images.qrc"/>
|
<include location="images.qrc"/>
|
||||||
</resources>
|
</resources>
|
||||||
<connections>
|
<connections/>
|
||||||
<connection>
|
|
||||||
<sender>clear_button</sender>
|
|
||||||
<signal>clicked()</signal>
|
|
||||||
<receiver>search</receiver>
|
|
||||||
<slot>clear()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>787</x>
|
|
||||||
<y>215</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>755</x>
|
|
||||||
<y>213</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
</ui>
|
||||||
|
157
src/calibre/gui2/search_box.py
Normal file
157
src/calibre/gui2/search_box.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import QComboBox, SIGNAL, Qt, QLineEdit, QStringList
|
||||||
|
|
||||||
|
from calibre.gui2 import config
|
||||||
|
|
||||||
|
class SearchLineEdit(QLineEdit):
|
||||||
|
|
||||||
|
def keyPressEvent(self, event):
|
||||||
|
self.emit(SIGNAL('key_pressed(PyQt_PyObject)'), event)
|
||||||
|
QLineEdit.keyPressEvent(self, event)
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
self.emit(SIGNAL('mouse_released(PyQt_PyObject)'), event)
|
||||||
|
QLineEdit.mouseReleaseEvent(self, event)
|
||||||
|
|
||||||
|
class SearchBox2(QComboBox):
|
||||||
|
|
||||||
|
'''
|
||||||
|
To use this class:
|
||||||
|
|
||||||
|
* Call initialize()
|
||||||
|
* Connect to the search() and cleared() signals from this widget
|
||||||
|
* Call search_done() after evry search is complete
|
||||||
|
* Use clear() to clear back to the help message
|
||||||
|
'''
|
||||||
|
|
||||||
|
INTERVAL = 1500 #: Time to wait before emitting search signal
|
||||||
|
MAX_COUNT = 25
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QComboBox.__init__(self, parent)
|
||||||
|
self.line_edit = SearchLineEdit(self)
|
||||||
|
self.setLineEdit(self.line_edit)
|
||||||
|
self.connect(self.line_edit, SIGNAL('key_pressed(PyQt_PyObject)'),
|
||||||
|
self.key_pressed, Qt.DirectConnection)
|
||||||
|
self.connect(self.line_edit, SIGNAL('mouse_released(PyQt_PyObject)'),
|
||||||
|
self.key_pressed, Qt.DirectConnection)
|
||||||
|
self.setEditable(True)
|
||||||
|
self.help_state = True
|
||||||
|
self.as_you_type = True
|
||||||
|
self.prev_search = ''
|
||||||
|
self.timer = None
|
||||||
|
self.setInsertPolicy(self.NoInsert)
|
||||||
|
self.setMaxCount(self.MAX_COUNT)
|
||||||
|
|
||||||
|
def initialize(self, opt_name, colorize=False,
|
||||||
|
help_text=_('Search')):
|
||||||
|
self.as_you_type = config['search_as_you_type']
|
||||||
|
self.opt_name = opt_name
|
||||||
|
self.addItems(QStringList(list(set(config[opt_name]))))
|
||||||
|
self.help_text = help_text
|
||||||
|
self.colorize = colorize
|
||||||
|
self.clear_to_help()
|
||||||
|
self.connect(self, SIGNAL('editTextChanged(QString)'), self.text_edited_slot)
|
||||||
|
|
||||||
|
def normalize_state(self):
|
||||||
|
self.setEditText('')
|
||||||
|
self.line_edit.setStyleSheet('QLineEdit { color: black; background-color: white; }')
|
||||||
|
self.help_state = False
|
||||||
|
|
||||||
|
def clear_to_help(self):
|
||||||
|
self.setEditText(self.help_text)
|
||||||
|
self.line_edit.home(False)
|
||||||
|
self.help_state = True
|
||||||
|
self.line_edit.setStyleSheet('QLineEdit { color: gray; background-color: white; }')
|
||||||
|
self.emit(SIGNAL('cleared()'))
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
return self.currentText()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.clear_to_help()
|
||||||
|
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), '', False)
|
||||||
|
|
||||||
|
def search_done(self, ok):
|
||||||
|
if not unicode(self.currentText()).strip():
|
||||||
|
return self.clear_to_help()
|
||||||
|
col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
|
||||||
|
if not self.colorize:
|
||||||
|
col = 'white'
|
||||||
|
self.line_edit.setStyleSheet('QLineEdit { color: black; background-color: %s; }' % col)
|
||||||
|
|
||||||
|
def key_pressed(self, event):
|
||||||
|
if self.help_state:
|
||||||
|
self.normalize_state()
|
||||||
|
if not self.as_you_type:
|
||||||
|
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
||||||
|
self.do_search()
|
||||||
|
|
||||||
|
def mouse_released(self, event):
|
||||||
|
if self.help_state:
|
||||||
|
self.normalize_state()
|
||||||
|
|
||||||
|
def text_edited_slot(self, text):
|
||||||
|
if self.as_you_type:
|
||||||
|
text = unicode(text)
|
||||||
|
self.prev_text = text
|
||||||
|
self.timer = self.startTimer(self.__class__.INTERVAL)
|
||||||
|
|
||||||
|
def timerEvent(self, event):
|
||||||
|
self.killTimer(event.timerId())
|
||||||
|
if event.timerId() == self.timer:
|
||||||
|
self.do_search()
|
||||||
|
|
||||||
|
def do_search(self):
|
||||||
|
text = unicode(self.currentText()).strip()
|
||||||
|
if not text or text == self.help_text:
|
||||||
|
return self.clear()
|
||||||
|
self.help_state = False
|
||||||
|
refinement = text.startswith(self.prev_search) and ':' not in text
|
||||||
|
self.prev_search = text
|
||||||
|
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), text, refinement)
|
||||||
|
|
||||||
|
idx = self.findText(text, Qt.MatchFixedString)
|
||||||
|
self.block_signals(True)
|
||||||
|
if idx < 0:
|
||||||
|
self.insertItem(0, text)
|
||||||
|
else:
|
||||||
|
t = self.itemText(idx)
|
||||||
|
self.removeItem(idx)
|
||||||
|
self.insertItem(0, t)
|
||||||
|
self.setCurrentIndex(0)
|
||||||
|
self.block_signals(False)
|
||||||
|
config[self.opt_name] = [unicode(self.itemText(i)) for i in
|
||||||
|
range(self.count())]
|
||||||
|
|
||||||
|
def block_signals(self, yes):
|
||||||
|
self.blockSignals(yes)
|
||||||
|
self.line_edit.blockSignals(yes)
|
||||||
|
|
||||||
|
def search_from_tokens(self, tokens, all):
|
||||||
|
ans = u' '.join([u'%s:%s'%x for x in tokens])
|
||||||
|
if not all:
|
||||||
|
ans = '[' + ans + ']'
|
||||||
|
self.set_search_string(ans)
|
||||||
|
|
||||||
|
def search_from_tags(self, tags, all):
|
||||||
|
joiner = ' and ' if all else ' or '
|
||||||
|
self.set_search_string(joiner.join(tags))
|
||||||
|
|
||||||
|
def set_search_string(self, txt):
|
||||||
|
self.normalize_state()
|
||||||
|
self.setEditText(txt)
|
||||||
|
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), txt, False)
|
||||||
|
self.line_edit.end(False)
|
||||||
|
self.initial_state = False
|
||||||
|
|
||||||
|
def search_as_you_type(self, enabled):
|
||||||
|
self.as_you_type = enabled
|
||||||
|
|
@ -21,7 +21,7 @@ from calibre.ebooks.oeb.iterator import EbookIterator
|
|||||||
from calibre.ebooks import DRMError
|
from calibre.ebooks import DRMError
|
||||||
from calibre.constants import islinux
|
from calibre.constants import islinux
|
||||||
from calibre.utils.config import Config, StringConfig, dynamic
|
from calibre.utils.config import Config, StringConfig, dynamic
|
||||||
from calibre.gui2.library import SearchBox
|
from calibre.gui2.search_box import SearchBox2
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.customize.ui import available_input_formats
|
from calibre.customize.ui import available_input_formats
|
||||||
|
|
||||||
@ -218,7 +218,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
self.tool_bar2.insertWidget(self.action_find_next, self.reference)
|
self.tool_bar2.insertWidget(self.action_find_next, self.reference)
|
||||||
self.tool_bar2.insertSeparator(self.action_find_next)
|
self.tool_bar2.insertSeparator(self.action_find_next)
|
||||||
self.setFocusPolicy(Qt.StrongFocus)
|
self.setFocusPolicy(Qt.StrongFocus)
|
||||||
self.search = SearchBox(self, _('Search'))
|
self.search = SearchBox2(self)
|
||||||
|
self.search.initialize('viewer_search_history')
|
||||||
self.search.setToolTip(_('Search for text in book'))
|
self.search.setToolTip(_('Search for text in book'))
|
||||||
self.tool_bar2.insertWidget(self.action_find_next, self.search)
|
self.tool_bar2.insertWidget(self.action_find_next, self.search)
|
||||||
self.view.set_manager(self)
|
self.view.set_manager(self)
|
||||||
@ -408,10 +409,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
|
|
||||||
def find(self, text, refinement, repeat=False):
|
def find(self, text, refinement, repeat=False):
|
||||||
if not text:
|
if not text:
|
||||||
return
|
return self.search.search_done(False)
|
||||||
if self.view.search(text):
|
if self.view.search(text):
|
||||||
self.scrolled(self.view.scroll_fraction)
|
self.scrolled(self.view.scroll_fraction)
|
||||||
return
|
return self.search.search_done(True)
|
||||||
index = self.iterator.search(text, self.current_index)
|
index = self.iterator.search(text, self.current_index)
|
||||||
if index is None:
|
if index is None:
|
||||||
if self.current_index > 0:
|
if self.current_index > 0:
|
||||||
@ -419,8 +420,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
if index is None:
|
if index is None:
|
||||||
info_dialog(self, _('No matches found'),
|
info_dialog(self, _('No matches found'),
|
||||||
_('No matches found for: %s')%text).exec_()
|
_('No matches found for: %s')%text).exec_()
|
||||||
return
|
return self.search.search_done(True)
|
||||||
return
|
return self.search.search_done(True)
|
||||||
self.pending_search = text
|
self.pending_search = text
|
||||||
self.load_path(self.iterator.spine[index])
|
self.load_path(self.iterator.spine[index])
|
||||||
|
|
||||||
|
@ -27,15 +27,12 @@ from calibre.library.sqlite import connect, IntegrityError
|
|||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
|
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
|
||||||
MetaInformation, authors_to_sort_string
|
MetaInformation, authors_to_sort_string
|
||||||
from calibre.ebooks.metadata.meta import get_metadata, set_metadata, \
|
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
|
||||||
metadata_from_formats
|
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
|
||||||
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.customize.ui import run_plugins_on_import
|
from calibre.customize.ui import run_plugins_on_import
|
||||||
|
|
||||||
from calibre.utils.filenames import ascii_filename, shorten_components_to, \
|
from calibre.utils.filenames import ascii_filename
|
||||||
supports_long_names
|
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
|
|
||||||
if iswindows:
|
if iswindows:
|
||||||
@ -1587,124 +1584,6 @@ books_series_link feeds
|
|||||||
progress.reset()
|
progress.reset()
|
||||||
return len(books)
|
return len(books)
|
||||||
|
|
||||||
def export_to_dir(self, dir, indices, byauthor=False, single_dir=False,
|
|
||||||
index_is_id=False, callback=None):
|
|
||||||
if not os.path.exists(dir):
|
|
||||||
raise IOError('Target directory does not exist: '+dir)
|
|
||||||
by_author = {}
|
|
||||||
count = 0
|
|
||||||
path_len, au_len = (1000, 500) if supports_long_names(dir) else (240, 50)
|
|
||||||
for index in indices:
|
|
||||||
id = index if index_is_id else self.id(index)
|
|
||||||
au = self.conn.get('SELECT author_sort FROM books WHERE id=?',
|
|
||||||
(id,), all=False)
|
|
||||||
if not au:
|
|
||||||
au = self.authors(index, index_is_id=index_is_id)
|
|
||||||
if not au:
|
|
||||||
au = _('Unknown')
|
|
||||||
au = au.split(',')[0]
|
|
||||||
if not by_author.has_key(au):
|
|
||||||
by_author[au] = []
|
|
||||||
by_author[au].append(index)
|
|
||||||
for au in by_author.keys():
|
|
||||||
aname = ascii_filename(au)[:au_len]
|
|
||||||
apath = os.path.abspath(os.path.join(dir, aname))
|
|
||||||
if not single_dir and not os.path.exists(apath):
|
|
||||||
os.mkdir(apath)
|
|
||||||
for idx in by_author[au]:
|
|
||||||
title = re.sub(r'\s', ' ', self.title(idx, index_is_id=index_is_id))
|
|
||||||
name = au + ' - ' + title if byauthor else title + ' - ' + au
|
|
||||||
name = ascii_filename(name)
|
|
||||||
tname = ascii_filename(title)
|
|
||||||
tname, name = shorten_components_to(path_len-len(apath), (tname,
|
|
||||||
name))
|
|
||||||
name += '_'+str(id)
|
|
||||||
|
|
||||||
tpath = os.path.join(apath, tname)
|
|
||||||
id = idx if index_is_id else self.id(idx)
|
|
||||||
id = str(id)
|
|
||||||
if not single_dir and not os.path.exists(tpath):
|
|
||||||
os.makedirs(tpath)
|
|
||||||
|
|
||||||
base = dir if single_dir else tpath
|
|
||||||
mi = self.get_metadata(idx, index_is_id=index_is_id, get_cover=True)
|
|
||||||
if not mi.authors:
|
|
||||||
mi.authors = [_('Unknown')]
|
|
||||||
cdata = self.cover(int(id), index_is_id=True)
|
|
||||||
if cdata is not None:
|
|
||||||
cname = name+'.jpg'
|
|
||||||
open(os.path.join(base, cname), 'wb').write(cdata)
|
|
||||||
mi.cover = cname
|
|
||||||
with open(os.path.join(base, name+'.opf'),
|
|
||||||
'wb') as f:
|
|
||||||
f.write(metadata_to_opf(mi))
|
|
||||||
|
|
||||||
fmts = self.formats(idx, index_is_id=index_is_id)
|
|
||||||
if not fmts:
|
|
||||||
fmts = ''
|
|
||||||
for fmt in fmts.split(','):
|
|
||||||
data = self.format(idx, fmt, index_is_id=index_is_id)
|
|
||||||
if not data:
|
|
||||||
continue
|
|
||||||
fname = name +'.'+fmt.lower()
|
|
||||||
f = open(os.path.join(base, fname), 'w+b')
|
|
||||||
f.write(data)
|
|
||||||
f.flush()
|
|
||||||
f.seek(0)
|
|
||||||
try:
|
|
||||||
set_metadata(f, mi, fmt.lower())
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
f.close()
|
|
||||||
count += 1
|
|
||||||
if callable(callback):
|
|
||||||
if not callback(int(id), mi.title):
|
|
||||||
return
|
|
||||||
|
|
||||||
def export_single_format_to_dir(self, dir, indices, format,
|
|
||||||
index_is_id=False, callback=None):
|
|
||||||
dir = os.path.abspath(dir)
|
|
||||||
if not index_is_id:
|
|
||||||
indices = map(self.id, indices)
|
|
||||||
failures = []
|
|
||||||
plen = 1000 if supports_long_names(dir) else 245
|
|
||||||
for count, id in enumerate(indices):
|
|
||||||
try:
|
|
||||||
data = self.format(id, format, index_is_id=True)
|
|
||||||
if not data:
|
|
||||||
failures.append((id, self.title(id, index_is_id=True)))
|
|
||||||
continue
|
|
||||||
except:
|
|
||||||
failures.append((id, self.title(id, index_is_id=True)))
|
|
||||||
continue
|
|
||||||
title = self.title(id, index_is_id=True)
|
|
||||||
au = self.authors(id, index_is_id=True)
|
|
||||||
if not au:
|
|
||||||
au = _('Unknown')
|
|
||||||
fname = '%s - %s'%(title, au)
|
|
||||||
while fname.endswith('.'):
|
|
||||||
fname = fname[:-1]
|
|
||||||
fname = ascii_filename(fname)
|
|
||||||
fname = fname + '.' + format.lower()
|
|
||||||
dir = os.path.abspath(dir)
|
|
||||||
fname = shorten_components_to(plen - len(dir), (fname,))[0]
|
|
||||||
if not os.path.exists(dir):
|
|
||||||
os.makedirs(dir)
|
|
||||||
f = open(os.path.join(dir, fname), 'w+b')
|
|
||||||
f.write(data)
|
|
||||||
f.flush()
|
|
||||||
f.seek(0)
|
|
||||||
try:
|
|
||||||
set_metadata(f, self.get_metadata(id, index_is_id=True, get_cover=True),
|
|
||||||
stream_type=format.lower())
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
f.close()
|
|
||||||
if callable(callback):
|
|
||||||
if not callback(int(id), title):
|
|
||||||
break
|
|
||||||
return failures
|
|
||||||
|
|
||||||
def find_books_in_directory(self, dirpath, single_book_per_directory):
|
def find_books_in_directory(self, dirpath, single_book_per_directory):
|
||||||
dirpath = os.path.abspath(dirpath)
|
dirpath = os.path.abspath(dirpath)
|
||||||
if single_book_per_directory:
|
if single_book_per_directory:
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user