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:
John Schember 2011-06-26 12:29:58 -04:00
parent 12e9b6194e
commit 3c83b7873a
8 changed files with 112 additions and 21 deletions

View File

@ -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', '')

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -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())

View File

@ -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):

View File

@ -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 = ''

View File

@ -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