mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Store: Allow for direct downloading of ebooks in the Get Books search dialog. Allow the user to right click and choose between downloading or opening in the store.
This commit is contained in:
parent
12e9b6194e
commit
3c83b7873a
@ -59,14 +59,14 @@ class OpenSearchStore(StorePlugin):
|
||||
elif l['rel'] == u'http://opds-spec.org/acquisition/buy':
|
||||
s.detail_item = l.get('href', s.detail_item)
|
||||
elif l['rel'] == u'http://opds-spec.org/acquisition':
|
||||
s.downloads.append((l.get('type', ''), l.get('href', '')))
|
||||
mime = l.get('type', '')
|
||||
if mime:
|
||||
ext = mimetypes.guess_extension(mime)
|
||||
if ext:
|
||||
ext = ext[1:].upper()
|
||||
s.downloads[ext] = l.get('href', '')
|
||||
|
||||
formats = []
|
||||
for mime, url in s.downloads:
|
||||
ext = mimetypes.guess_extension(mime)
|
||||
if ext:
|
||||
formats.append(ext[1:])
|
||||
s.formats = ', '.join(formats)
|
||||
s.formats = ', '.join(s.downloads.keys())
|
||||
|
||||
s.title = r.get('title', '')
|
||||
s.author = r.get('author', '')
|
||||
|
@ -45,6 +45,7 @@ class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
|
||||
self.author_box.setText('')
|
||||
self.price_box.setText('')
|
||||
self.format_box.setText('')
|
||||
self.download_combo.setCurrentIndex(0)
|
||||
self.affiliate_combo.setCurrentIndex(0)
|
||||
|
||||
def tokens(self, raw):
|
||||
@ -119,6 +120,9 @@ class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
|
||||
format = unicode(self.format_box.text()).strip()
|
||||
if format:
|
||||
ans.append('format:"' + self.mc + format + '"')
|
||||
download = unicode(self.download_combo.currentText()).strip()
|
||||
if download:
|
||||
ans.append('download:' + download)
|
||||
affiliate = unicode(self.affiliate_combo.currentText()).strip()
|
||||
if affiliate:
|
||||
ans.append('affiliate:' + affiliate)
|
||||
|
@ -226,7 +226,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<item row="8" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QPushButton" name="clear_button">
|
||||
@ -244,7 +244,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="7" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@ -283,14 +283,14 @@
|
||||
<item row="3" column="1">
|
||||
<widget class="EnLineEdit" name="price_box"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Affiliate:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="affiliate_combo">
|
||||
<item>
|
||||
<property name="text">
|
||||
@ -309,6 +309,32 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Download:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="download_combo">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>true</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>false</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
@ -33,7 +33,7 @@ class Matches(QAbstractItemModel):
|
||||
|
||||
total_changed = pyqtSignal(int)
|
||||
|
||||
HEADERS = [_('Cover'), _('Title'), _('Price'), _('DRM'), _('Store'), '']
|
||||
HEADERS = [_('Cover'), _('Title'), _('Price'), _('DRM'), _('Store'), _('Download'), _('Affiliate')]
|
||||
HTML_COLS = (1, 4)
|
||||
|
||||
def __init__(self, cover_thread_count=2, detail_thread_count=4):
|
||||
@ -47,6 +47,8 @@ class Matches(QAbstractItemModel):
|
||||
Qt.SmoothTransformation)
|
||||
self.DONATE_ICON = QPixmap(I('donate.png')).scaledToHeight(16,
|
||||
Qt.SmoothTransformation)
|
||||
self.DOWNLOAD_ICON = QPixmap(I('arrow-down.png')).scaledToHeight(16,
|
||||
Qt.SmoothTransformation)
|
||||
|
||||
# All matches. Used to determine the order to display
|
||||
# self.matches because the SearchFilter returns
|
||||
@ -181,9 +183,11 @@ class Matches(QAbstractItemModel):
|
||||
elif result.drm == SearchResult.DRM_UNKNOWN:
|
||||
return QVariant(self.DRM_UNKNOWN_ICON)
|
||||
if col == 5:
|
||||
if result.downloads:
|
||||
return QVariant(self.DOWNLOAD_ICON)
|
||||
if col == 6:
|
||||
if result.affiliate:
|
||||
return QVariant(self.DONATE_ICON)
|
||||
return NONE
|
||||
elif role == Qt.ToolTipRole:
|
||||
if col == 1:
|
||||
return QVariant('<p>%s</p>' % result.title)
|
||||
@ -199,6 +203,9 @@ class Matches(QAbstractItemModel):
|
||||
elif col == 4:
|
||||
return QVariant('<p>%s</p>' % result.formats)
|
||||
elif col == 5:
|
||||
if result.downloads:
|
||||
return QVariant('<p>' + _('The following formats can be downloaded directly: %s.') % ', '.join(result.downloads.keys()) + '</p>')
|
||||
elif col == 6:
|
||||
if result.affiliate:
|
||||
return QVariant('<p>' + _('Buying from this store supports the calibre developer: %s.') % result.plugin_author + '</p>')
|
||||
elif role == Qt.SizeHintRole:
|
||||
@ -221,6 +228,11 @@ class Matches(QAbstractItemModel):
|
||||
elif col == 4:
|
||||
text = result.store_name
|
||||
elif col == 5:
|
||||
if result.downloads:
|
||||
text = 'a'
|
||||
else:
|
||||
text = 'b'
|
||||
elif col == 6:
|
||||
if result.affiliate:
|
||||
text = 'a'
|
||||
else:
|
||||
@ -257,6 +269,8 @@ class SearchFilter(SearchQueryParser):
|
||||
'author',
|
||||
'authors',
|
||||
'cover',
|
||||
'download',
|
||||
'downloads',
|
||||
'drm',
|
||||
'format',
|
||||
'formats',
|
||||
@ -282,6 +296,8 @@ class SearchFilter(SearchQueryParser):
|
||||
location = location.lower().strip()
|
||||
if location == 'authors':
|
||||
location = 'author'
|
||||
elif location == 'downloads':
|
||||
location = 'download'
|
||||
elif location == 'formats':
|
||||
location = 'format'
|
||||
|
||||
@ -308,12 +324,13 @@ class SearchFilter(SearchQueryParser):
|
||||
'author': lambda x: x.author.lower(),
|
||||
'cover': attrgetter('cover_url'),
|
||||
'drm': attrgetter('drm'),
|
||||
'download': attrgetter('downloads'),
|
||||
'format': attrgetter('formats'),
|
||||
'price': lambda x: comparable_price(x.price),
|
||||
'store': lambda x: x.store_name.lower(),
|
||||
'title': lambda x: x.title.lower(),
|
||||
}
|
||||
for x in ('author', 'format'):
|
||||
for x in ('author', 'download', 'format'):
|
||||
q[x+'s'] = q[x]
|
||||
for sr in self.srs:
|
||||
for locvalue in locations:
|
||||
@ -347,7 +364,7 @@ class SearchFilter(SearchQueryParser):
|
||||
matches.add(sr)
|
||||
continue
|
||||
# this is bool or treated as bool, so can't match below.
|
||||
if locvalue in ('affiliate', 'drm'):
|
||||
if locvalue in ('affiliate', 'drm', 'download', 'downloads'):
|
||||
continue
|
||||
try:
|
||||
### Can't separate authors because comma is used for name sep and author sep
|
||||
|
@ -6,13 +6,18 @@ __license__ = 'GPL 3'
|
||||
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import (QTreeView)
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import (pyqtSignal, QMenu, QTreeView)
|
||||
|
||||
from calibre.gui2.metadata.single_download import RichTextDelegate
|
||||
from calibre.gui2.store.search.models import Matches
|
||||
|
||||
class ResultsView(QTreeView):
|
||||
|
||||
download_requested = pyqtSignal(object)
|
||||
open_requested = pyqtSignal(object)
|
||||
|
||||
def __init__(self, *args):
|
||||
QTreeView.__init__(self,*args)
|
||||
|
||||
@ -24,3 +29,18 @@ class ResultsView(QTreeView):
|
||||
for i in self._model.HTML_COLS:
|
||||
self.setItemDelegateForColumn(i, self.rt_delegate)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
index = self.indexAt(event.pos())
|
||||
|
||||
if not index.isValid():
|
||||
return
|
||||
|
||||
result = self.model().get_result(index)
|
||||
|
||||
menu = QMenu()
|
||||
da = menu.addAction(_('Download...'), partial(self.download_requested.emit, result))
|
||||
if not result.downloads:
|
||||
da.setEnabled(False)
|
||||
menu.addSeparator()
|
||||
menu.addAction(_('Goto in store...'), partial(self.open_requested.emit, result))
|
||||
menu.exec_(event.globalPos())
|
||||
|
@ -14,6 +14,7 @@ from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, QLabel,
|
||||
QComboBox)
|
||||
|
||||
from calibre.gui2 import JSONConfig, info_dialog
|
||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
|
||||
from calibre.gui2.store.config.search.search_widget import StoreConfigWidget
|
||||
@ -72,7 +73,9 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
self.search.clicked.connect(self.do_search)
|
||||
self.checker.timeout.connect(self.get_results)
|
||||
self.progress_checker.timeout.connect(self.check_progress)
|
||||
self.results_view.activated.connect(self.open_store)
|
||||
self.results_view.activated.connect(self.result_item_activated)
|
||||
self.results_view.download_requested.connect(self.download_book)
|
||||
self.results_view.open_requested.connect(self.open_store)
|
||||
self.results_view.model().total_changed.connect(self.update_book_total)
|
||||
self.select_all_stores.clicked.connect(self.stores_select_all)
|
||||
self.select_invert_stores.clicked.connect(self.stores_select_invert)
|
||||
@ -129,11 +132,15 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
# Title / Author
|
||||
self.results_view.setColumnWidth(1,int(total*.40))
|
||||
# Price
|
||||
self.results_view.setColumnWidth(2,int(total*.20))
|
||||
self.results_view.setColumnWidth(2,int(total*.12))
|
||||
# DRM
|
||||
self.results_view.setColumnWidth(3, int(total*.15))
|
||||
# Store / Formats
|
||||
self.results_view.setColumnWidth(4, int(total*.25))
|
||||
# Download
|
||||
self.results_view.setColumnWidth(5, 20)
|
||||
# Affiliate
|
||||
self.results_view.setColumnWidth(6, 20)
|
||||
|
||||
def do_search(self):
|
||||
# Stop all running threads.
|
||||
@ -183,7 +190,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query)
|
||||
query = query.replace('%s:' % loc, '')
|
||||
# Remove the prefix and search text.
|
||||
for loc in ('cover', 'drm', 'format', 'formats', 'price', 'store'):
|
||||
for loc in ('cover', 'download', 'downloads', 'drm', 'format', 'formats', 'price', 'store'):
|
||||
query = re.sub(r'%s:"[^"]"' % loc, '', query)
|
||||
query = re.sub(r'%s:[^\s]*' % loc, '', query)
|
||||
# Remove logic.
|
||||
@ -330,8 +337,21 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
def update_book_total(self, total):
|
||||
self.total.setText('%s' % total)
|
||||
|
||||
def open_store(self, index):
|
||||
def result_item_activated(self, index):
|
||||
result = self.results_view.model().get_result(index)
|
||||
|
||||
if result.downloads:
|
||||
self.download_book(result)
|
||||
else:
|
||||
self.open_store(result)
|
||||
|
||||
def download_book(self, result):
|
||||
d = ChooseFormatDialog(self, _('Choose format to download to your library.'), result.downloads.keys())
|
||||
if d.exec_() == d.Accepted:
|
||||
ext = d.format()
|
||||
self.gui.download_ebook(result.downloads[ext])
|
||||
|
||||
def open_store(self, result):
|
||||
self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked())
|
||||
|
||||
def check_progress(self):
|
||||
|
@ -22,7 +22,9 @@ class SearchResult(object):
|
||||
self.detail_item = ''
|
||||
self.drm = None
|
||||
self.formats = ''
|
||||
self.downloads = []
|
||||
# key = format in upper case.
|
||||
# value = url to download the file.
|
||||
self.downloads = {}
|
||||
self.affiliate = False
|
||||
self.plugin_author = ''
|
||||
|
||||
|
@ -22,4 +22,6 @@ class EpubBudStore(BasicStoreConfig, OpenSearchStore):
|
||||
s.price = '$0.00'
|
||||
s.drm = SearchResult.DRM_UNLOCKED
|
||||
s.formats = 'EPUB'
|
||||
# Download links are broken for this store.
|
||||
s.downloads = {}
|
||||
yield s
|
||||
|
Loading…
x
Reference in New Issue
Block a user