mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
merge from trunk
This commit is contained in:
commit
a0cdf29379
@ -8,23 +8,36 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net> edited by Huan T'
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Slashdot(BasicNewsRecipe):
|
||||
title = u'Slashdot.org'
|
||||
description = '''Tech news. WARNING: This recipe downloads a lot
|
||||
of content and may result in your IP being banned from slashdot.org'''
|
||||
oldest_article = 7
|
||||
simultaneous_downloads = 1
|
||||
delay = 3
|
||||
max_articles_per_feed = 100
|
||||
language = 'en'
|
||||
title = u'Slashdot.org'
|
||||
description = '''Tech news. WARNING: This recipe downloads a lot
|
||||
of content and may result in your IP being banned from slashdot.org'''
|
||||
oldest_article = 7
|
||||
simultaneous_downloads = 1
|
||||
delay = 3
|
||||
max_articles_per_feed = 100
|
||||
language = 'en'
|
||||
|
||||
__author__ = 'floweros edited by Huan T'
|
||||
no_stylesheets = True
|
||||
# keep_only_tags = [
|
||||
# dict(name='div',attrs={'class':'article'}),
|
||||
# dict(name='div',attrs={'class':'commentTop'}),
|
||||
# ]
|
||||
__author__ = 'floweros edited by Huan T'
|
||||
no_stylesheets = True
|
||||
keep_only_tags = [
|
||||
dict(name='div',attrs={'id':'article'}),
|
||||
dict(name='div',attrs={'class':['postBody' 'details']}),
|
||||
dict(name='footer',attrs={'class':['clearfix meta article-foot']}),
|
||||
dict(name='article',attrs={'class':['fhitem fhitem-story article usermode thumbs grid_24']}),
|
||||
dict(name='dl',attrs={'class':'relatedPosts'}),
|
||||
dict(name='h2',attrs={'class':'story'}),
|
||||
dict(name='span',attrs={'class':'comments'}),
|
||||
]
|
||||
|
||||
feeds = [
|
||||
|
||||
remove_tags = [
|
||||
dict(name='aside',attrs={'id':'slashboxes'}),
|
||||
dict(name='div',attrs={'class':'paginate'}),
|
||||
dict(name='section',attrs={'id':'comments'}),
|
||||
dict(name='span',attrs={'class':'topic'}),
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Slashdot',
|
||||
u'http://rss.slashdot.org/Slashdot/slashdot'),
|
||||
(u'/. IT',
|
||||
@ -37,5 +50,3 @@ class Slashdot(BasicNewsRecipe):
|
||||
u'http://rss.slashdot.org/Slashdot/slashdotYourRightsOnline')
|
||||
]
|
||||
|
||||
def get_article_url(self, article):
|
||||
return article.get('feedburner_origlink', None)
|
||||
|
@ -48,7 +48,7 @@ authors_completer_append_separator = False
|
||||
# When this tweak is changed, the author_sort values stored with each author
|
||||
# must be recomputed by right-clicking on an author in the left-hand tags pane,
|
||||
# selecting 'manage authors', and pressing 'Recalculate all author sort values'.
|
||||
author_sort_copy_method = 'invert'
|
||||
author_sort_copy_method = 'comma'
|
||||
|
||||
#: Use author sort in Tag Browser
|
||||
# Set which author field to display in the tags pane (the list of authors,
|
||||
|
@ -108,10 +108,10 @@ class ANDROID(USBMS):
|
||||
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H',
|
||||
'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD',
|
||||
'7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2',
|
||||
'MB860', 'MULTI-CARD', 'MID7015A']
|
||||
'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE']
|
||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||
'A70S', 'A101IT', '7']
|
||||
'A70S', 'A101IT', '7', 'INCREDIBLE']
|
||||
|
||||
OSX_MAIN_MEM = 'Android Device Main Memory'
|
||||
|
||||
|
@ -325,9 +325,8 @@ class Source(Plugin):
|
||||
tokens = title.split()
|
||||
for token in tokens:
|
||||
token = token.strip()
|
||||
if token and token.lower() not in ('a', 'and', 'the', '&') and strip_joiners:
|
||||
yield token
|
||||
elif token:
|
||||
if token and (not strip_joiners or token.lower() not in ('a',
|
||||
'and', 'the', '&')):
|
||||
yield token
|
||||
|
||||
def split_jobs(self, jobs, num):
|
||||
@ -375,7 +374,12 @@ class Source(Plugin):
|
||||
def get_book_url(self, identifiers):
|
||||
'''
|
||||
Return the URL for the book identified by identifiers at this source.
|
||||
If no URL is found, return None.
|
||||
This URL must be browseable to by a human using a browser. It is meant
|
||||
to provide a clickable link for the user to easily visit the books page
|
||||
at this source.
|
||||
If no URL is found, return None. This method must be quick, and
|
||||
consistent, so only implement it if it is possible to construct the URL
|
||||
from a known scheme given identifiers.
|
||||
'''
|
||||
return None
|
||||
|
||||
|
@ -433,7 +433,7 @@ def urls_from_identifiers(identifiers): # {{{
|
||||
pass
|
||||
isbn = identifiers.get('isbn', None)
|
||||
if isbn:
|
||||
ans.append(('ISBN',
|
||||
ans.append((isbn,
|
||||
'http://www.worldcat.org/search?q=bn%%3A%s&qt=advanced'%isbn))
|
||||
return ans
|
||||
# }}}
|
||||
@ -444,13 +444,18 @@ if __name__ == '__main__': # tests {{{
|
||||
from calibre.ebooks.metadata.sources.test import (test_identify,
|
||||
title_test, authors_test)
|
||||
tests = [
|
||||
(
|
||||
{'title':'Magykal Papers',
|
||||
'authors':['Sage']},
|
||||
[title_test('The Magykal Papers', exact=True)],
|
||||
),
|
||||
|
||||
|
||||
( # An e-book ISBN not on Amazon, one of the authors is
|
||||
# unknown to Amazon
|
||||
{'identifiers':{'isbn': '9780307459671'},
|
||||
'title':'Invisible Gorilla', 'authors':['Christopher Chabris']},
|
||||
[title_test('The Invisible Gorilla',
|
||||
exact=True), authors_test(['Christopher Chabris', 'Daniel Simons'])]
|
||||
[title_test('The Invisible Gorilla', exact=True)]
|
||||
|
||||
),
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@ -6,14 +9,13 @@ __docformat__ = 'restructuredtext en'
|
||||
'''
|
||||
Fetch metadata using Overdrive Content Reserve
|
||||
'''
|
||||
import sys, re, random, urllib, mechanize, copy
|
||||
import re, random, mechanize, copy
|
||||
from threading import RLock
|
||||
from Queue import Queue, Empty
|
||||
|
||||
from lxml import html, etree
|
||||
from lxml import html
|
||||
from lxml.html import soupparser
|
||||
|
||||
from calibre import browser
|
||||
from calibre.ebooks.metadata import check_isbn
|
||||
from calibre.ebooks.metadata.sources.base import Source
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
@ -33,7 +35,7 @@ class OverDrive(Source):
|
||||
|
||||
capabilities = frozenset(['identify', 'cover'])
|
||||
touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate',
|
||||
'comments', 'publisher', 'identifier:isbn', 'series', 'series_num',
|
||||
'comments', 'publisher', 'identifier:isbn', 'series', 'series_index',
|
||||
'language', 'identifier:overdrive'])
|
||||
has_html_comments = True
|
||||
supports_gzip_transfer_encoding = False
|
||||
@ -43,7 +45,6 @@ class OverDrive(Source):
|
||||
Source.__init__(self, *args, **kwargs)
|
||||
self.prefs.defaults['ignore_fields'] =['tags', 'pubdate', 'comments', 'identifier:isbn', 'language']
|
||||
|
||||
|
||||
def identify(self, log, result_queue, abort, title=None, authors=None, # {{{
|
||||
identifiers={}, timeout=30):
|
||||
ovrdrv_id = identifiers.get('overdrive', None)
|
||||
@ -68,19 +69,6 @@ class OverDrive(Source):
|
||||
return None
|
||||
# }}}
|
||||
|
||||
|
||||
def get_book_url(self, identifiers): # {{{
|
||||
ovrdrv_id = identifiers.get('overdrive', None)
|
||||
if ovrdrv_id is not None:
|
||||
ovrdrv_data = ovrdrv_data_cache.get(ovrdrv_id, None)
|
||||
if ovrdrv_data:
|
||||
return ovrdrv_data[1]
|
||||
else:
|
||||
br = browser()
|
||||
ovrdrv_data = self.to_ovrdrv_data(br, None, None, ovrdrv_id)
|
||||
return ovrdrv_data[1]
|
||||
# }}}
|
||||
|
||||
def download_cover(self, log, result_queue, abort, # {{{
|
||||
title=None, authors=None, identifiers={}, timeout=30):
|
||||
cached_url = self.get_cached_cover_url(identifiers)
|
||||
@ -136,28 +124,6 @@ class OverDrive(Source):
|
||||
return url
|
||||
# }}}
|
||||
|
||||
def create_query(self, title=None, authors=None, identifiers={}):
|
||||
q = ''
|
||||
if title or authors:
|
||||
def build_term(prefix, parts):
|
||||
return ' '.join('in'+prefix + ':' + x for x in parts)
|
||||
title_tokens = list(self.get_title_tokens(title, False, True))
|
||||
if title_tokens:
|
||||
q += build_term('title', title_tokens)
|
||||
author_tokens = self.get_author_tokens(authors,
|
||||
only_first_author=True)
|
||||
if author_tokens:
|
||||
q += ('+' if q else '') + build_term('author',
|
||||
author_tokens)
|
||||
|
||||
if isinstance(q, unicode):
|
||||
q = q.encode('utf-8')
|
||||
if not q:
|
||||
return None
|
||||
return BASE_URL+urlencode({
|
||||
'q':q,
|
||||
})
|
||||
|
||||
def get_base_referer(self): # to be used for passing referrer headers to cover download
|
||||
choices = [
|
||||
'http://overdrive.chipublib.org/82DC601D-7DDE-4212-B43A-09D821935B01/10/375/en/',
|
||||
@ -209,7 +175,6 @@ class OverDrive(Source):
|
||||
|
||||
br.set_cookiejar(clean_cj)
|
||||
|
||||
|
||||
def overdrive_search(self, br, q, title, author):
|
||||
# re-initialize the cookiejar to so that it's clean
|
||||
clean_cj = mechanize.CookieJar()
|
||||
@ -275,11 +240,11 @@ class OverDrive(Source):
|
||||
if ovrdrv_id is not None and int(formatid) in [1, 50, 410, 900]:
|
||||
#print "overdrive id is not None, searching based on format type priority"
|
||||
return self.format_results(reserveid, od_title, subtitle, series, publisher,
|
||||
creators, thumbimage, worldcatlink, formatid)
|
||||
creators, thumbimage, worldcatlink, formatid)
|
||||
else:
|
||||
creators = creators.split(', ')
|
||||
# if an exact match in a preferred format occurs
|
||||
if creators[0] == author[0] and od_title == title and int(formatid) in [1, 50, 410, 900]:
|
||||
if (author and creators[0] == author[0]) and od_title == title and int(formatid) in [1, 50, 410, 900]:
|
||||
return self.format_results(reserveid, od_title, subtitle, series, publisher,
|
||||
creators, thumbimage, worldcatlink, formatid)
|
||||
else:
|
||||
@ -312,7 +277,6 @@ class OverDrive(Source):
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def overdrive_get_record(self, br, q, ovrdrv_id):
|
||||
search_url = q+'SearchResults.aspx?ReserveID={'+ovrdrv_id+'}'
|
||||
results_url = q+'SearchResults.svc/GetResults?sEcho=1&iColumns=18&sColumns=ReserveID%2CTitle%2CSubtitle%2CEdition%2CSeries%2CPublisher%2CFormat%2CFormatID%2CCreators%2CThumbImage%2CShortDescription%2CWorldCatLink%2CExcerptLink%2CCreatorFile%2CSortTitle%2CAvailableToLibrary%2CAvailableToRetailer%2CRelevancyRank&iDisplayStart=0&iDisplayLength=10&sSearch=&bEscapeRegex=true&iSortingCols=1&iSortCol_0=17&sSortDir_0=asc'
|
||||
@ -390,7 +354,10 @@ class OverDrive(Source):
|
||||
if len(ovrdrv_data[3]) > 1:
|
||||
mi.series = ovrdrv_data[3]
|
||||
if ovrdrv_data[4]:
|
||||
mi.series_index = ovrdrv_data[4]
|
||||
try:
|
||||
mi.series_index = float(ovrdrv_data[4])
|
||||
except:
|
||||
pass
|
||||
mi.publisher = ovrdrv_data[5]
|
||||
mi.authors = ovrdrv_data[6]
|
||||
mi.title = ovrdrv_data[8]
|
||||
@ -407,7 +374,7 @@ class OverDrive(Source):
|
||||
if callable(getattr(e, 'getcode', None)) and \
|
||||
e.getcode() == 404:
|
||||
return False
|
||||
raise
|
||||
raise
|
||||
raw = xml_to_unicode(raw, strip_encoding_pats=True,
|
||||
resolve_entities=True)[0]
|
||||
try:
|
||||
@ -427,12 +394,12 @@ class OverDrive(Source):
|
||||
if lang:
|
||||
mi.language = lang[0].strip()
|
||||
|
||||
#if ebook_isbn:
|
||||
# print "ebook isbn is "+str(ebook_isbn[0])
|
||||
# isbn = check_isbn(ebook_isbn[0].strip())
|
||||
# if isbn:
|
||||
# self.cache_isbn_to_identifier(isbn, ovrdrv_id)
|
||||
# mi.isbn = isbn
|
||||
if ebook_isbn:
|
||||
#print "ebook isbn is "+str(ebook_isbn[0])
|
||||
isbn = check_isbn(ebook_isbn[0].strip())
|
||||
if isbn:
|
||||
self.cache_isbn_to_identifier(isbn, ovrdrv_id)
|
||||
mi.isbn = isbn
|
||||
if subjects:
|
||||
mi.tags = [tag.strip() for tag in subjects[0].split(',')]
|
||||
|
||||
@ -448,8 +415,25 @@ class OverDrive(Source):
|
||||
return None
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
# To run these test use:
|
||||
# calibre-debug -e src/calibre/ebooks/metadata/sources/overdrive.py
|
||||
from calibre.ebooks.metadata.sources.test import (test_identify_plugin,
|
||||
title_test, authors_test)
|
||||
test_identify_plugin(OverDrive.name,
|
||||
[
|
||||
|
||||
(
|
||||
{'title':'Foundation and Earth',
|
||||
'authors':['Asimov']},
|
||||
[title_test('Foundation and Earth', exact=True),
|
||||
authors_test(['Isaac Asimov'])]
|
||||
),
|
||||
|
||||
(
|
||||
{'title': 'Elephants', 'authors':['Agatha']},
|
||||
[title_test('Elephants Can Remember', exact=False),
|
||||
authors_test(['Agatha Christie'])]
|
||||
),
|
||||
])
|
||||
|
||||
|
@ -29,8 +29,7 @@ from calibre.ebooks.metadata.meta import set_metadata
|
||||
from calibre.constants import DEBUG
|
||||
from calibre.utils.config import prefs, tweaks
|
||||
from calibre.utils.magick.draw import thumbnail
|
||||
from calibre.library.save_to_disk import plugboard_any_device_value, \
|
||||
plugboard_any_format_value
|
||||
from calibre.library.save_to_disk import find_plugboard
|
||||
# }}}
|
||||
|
||||
class DeviceJob(BaseJob): # {{{
|
||||
@ -93,23 +92,6 @@ class DeviceJob(BaseJob): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
def find_plugboard(device_name, format, plugboards):
|
||||
cpb = None
|
||||
if format in plugboards:
|
||||
cpb = plugboards[format]
|
||||
elif plugboard_any_format_value in plugboards:
|
||||
cpb = plugboards[plugboard_any_format_value]
|
||||
if cpb is not None:
|
||||
if device_name in cpb:
|
||||
cpb = cpb[device_name]
|
||||
elif plugboard_any_device_value in cpb:
|
||||
cpb = cpb[plugboard_any_device_value]
|
||||
else:
|
||||
cpb = None
|
||||
if DEBUG:
|
||||
prints('Device using plugboard', format, device_name, cpb)
|
||||
return cpb
|
||||
|
||||
def device_name_for_plugboards(device_class):
|
||||
if hasattr(device_class, 'DEVICE_PLUGBOARD_NAME'):
|
||||
return device_class.DEVICE_PLUGBOARD_NAME
|
||||
@ -607,6 +589,16 @@ class DeviceMenu(QMenu): # {{{
|
||||
|
||||
class DeviceMixin(object): # {{{
|
||||
|
||||
#: This signal is emitted once, after metadata is downloaded from the
|
||||
#: connected device.
|
||||
#: The sequence: gui.device_manager.is_device_connected will become True,
|
||||
#: then sometime later gui.device_metadata_available will be signaled.
|
||||
#: This does not mean that there are no more jobs running. Automatic metadata
|
||||
#: management might have kicked off a sync_booklists to write new metadata onto
|
||||
#: the device, and that job might still be running when the signal is emitted.
|
||||
device_metadata_available = pyqtSignal()
|
||||
device_connection_changed = pyqtSignal(object)
|
||||
|
||||
def __init__(self):
|
||||
self.device_error_dialog = error_dialog(self, _('Error'),
|
||||
_('Error communicating with device'), ' ')
|
||||
@ -753,6 +745,7 @@ class DeviceMixin(object): # {{{
|
||||
self.location_manager.update_devices()
|
||||
self.library_view.set_device_connected(self.device_connected)
|
||||
self.refresh_ondevice()
|
||||
self.device_connection_changed.emit(connected)
|
||||
|
||||
def info_read(self, job):
|
||||
'''
|
||||
@ -791,6 +784,7 @@ class DeviceMixin(object): # {{{
|
||||
self.sync_news()
|
||||
self.sync_catalogs()
|
||||
self.refresh_ondevice()
|
||||
self.device_metadata_available.emit()
|
||||
|
||||
def refresh_ondevice(self, reset_only = False):
|
||||
'''
|
||||
@ -892,7 +886,7 @@ class DeviceMixin(object): # {{{
|
||||
sub_dest_parts.append('')
|
||||
to = sub_dest_parts[0]
|
||||
fmts = sub_dest_parts[1]
|
||||
subject = ';'.join(sub_dest_parts[2:])
|
||||
subject = ';'.join(sub_dest_parts[2:])
|
||||
fmts = [x.strip().lower() for x in fmts.split(',')]
|
||||
self.send_by_mail(to, fmts, delete, subject=subject)
|
||||
|
||||
|
@ -30,7 +30,6 @@ from calibre.ebooks.metadata.book.base import Metadata
|
||||
from calibre.gui2 import error_dialog, NONE
|
||||
from calibre.utils.date import utcnow, fromordinal, format_date
|
||||
from calibre.library.comments import comments_to_html
|
||||
from calibre.constants import islinux
|
||||
from calibre import force_unicode
|
||||
# }}}
|
||||
|
||||
@ -117,12 +116,10 @@ class CoverDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
QStyledItemDelegate.paint(self, painter, option, index)
|
||||
if islinux:
|
||||
# On linux for some reason the selected color is drawn on top of
|
||||
# the decoration
|
||||
style = QApplication.style()
|
||||
style.drawItemPixmap(painter, option.rect, Qt.AlignTop|Qt.AlignHCenter,
|
||||
QPixmap(index.data(Qt.DecorationRole)))
|
||||
# Ensure the cover is rendered over any selection rect
|
||||
style = QApplication.style()
|
||||
style.drawItemPixmap(painter, option.rect, Qt.AlignTop|Qt.AlignHCenter,
|
||||
QPixmap(index.data(Qt.DecorationRole)))
|
||||
if self.timer.isActive() and index.data(Qt.UserRole).toBool():
|
||||
rect = QRect(0, 0, self.spinner_width, self.spinner_width)
|
||||
rect.moveCenter(option.rect.center())
|
||||
@ -952,7 +949,7 @@ class CoverFetch(QDialog): # {{{
|
||||
# }}}
|
||||
|
||||
if __name__ == '__main__':
|
||||
DEBUG_DIALOG = True
|
||||
#DEBUG_DIALOG = True
|
||||
app = QApplication([])
|
||||
d = FullFetch()
|
||||
d.start(title='great gatsby', authors=['fitzgerald'])
|
||||
|
@ -337,7 +337,13 @@ def show_config_widget(category, name, gui=None, show_restart_msg=False,
|
||||
bb.button(bb.RestoreDefaults).setEnabled(w.supports_restoring_to_defaults)
|
||||
bb.button(bb.Apply).setEnabled(False)
|
||||
bb.button(bb.Apply).clicked.connect(d.accept)
|
||||
w.changed_signal.connect(lambda : bb.button(bb.Apply).setEnabled(True))
|
||||
def onchange():
|
||||
b = bb.button(bb.Apply)
|
||||
b.setEnabled(True)
|
||||
b.setDefault(True)
|
||||
b.setAutoDefault(True)
|
||||
w.changed_signal.connect(onchange)
|
||||
bb.button(bb.Cancel).setFocus(True)
|
||||
l = QVBoxLayout()
|
||||
d.setLayout(l)
|
||||
l.addWidget(w)
|
||||
|
@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import Qt, QLineEdit, QComboBox, SIGNAL, QListWidgetItem
|
||||
|
||||
from calibre.customize.ui import is_disabled
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre.gui2.device import device_name_for_plugboards
|
||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||
@ -15,6 +16,8 @@ from calibre.gui2.preferences.plugboard_ui import Ui_Form
|
||||
from calibre.customize.ui import metadata_writers, device_plugins
|
||||
from calibre.library.save_to_disk import plugboard_any_format_value, \
|
||||
plugboard_any_device_value, plugboard_save_to_disk_value
|
||||
from calibre.library.server.content import plugboard_content_server_value, \
|
||||
plugboard_content_server_formats
|
||||
from calibre.utils.formatter import validation_formatter
|
||||
|
||||
|
||||
@ -68,19 +71,26 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.device_label.setText(_('Device currently connected: None'))
|
||||
|
||||
self.devices = ['', 'APPLE', 'FOLDER_DEVICE']
|
||||
self.device_to_formats_map = {}
|
||||
for device in device_plugins():
|
||||
n = device_name_for_plugboards(device)
|
||||
self.device_to_formats_map[n] = device.FORMATS
|
||||
if n not in self.devices:
|
||||
self.devices.append(n)
|
||||
self.devices.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
|
||||
self.devices.insert(1, plugboard_save_to_disk_value)
|
||||
self.devices.insert(2, plugboard_any_device_value)
|
||||
self.devices.insert(1, plugboard_content_server_value)
|
||||
self.device_to_formats_map[plugboard_content_server_value] = \
|
||||
plugboard_content_server_formats
|
||||
self.devices.insert(1, plugboard_any_device_value)
|
||||
self.new_device.addItems(self.devices)
|
||||
|
||||
self.formats = ['']
|
||||
for w in metadata_writers():
|
||||
for f in w.file_types:
|
||||
self.formats.append(f)
|
||||
if not is_disabled(w):
|
||||
for f in w.file_types:
|
||||
if not f in self.formats:
|
||||
self.formats.append(f)
|
||||
self.formats.append('device_db')
|
||||
self.formats.sort()
|
||||
self.formats.insert(1, plugboard_any_format_value)
|
||||
@ -230,6 +240,15 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
show=True)
|
||||
self.new_device.setCurrentIndex(0)
|
||||
return
|
||||
if self.current_device in self.device_to_formats_map:
|
||||
allowable_formats = self.device_to_formats_map[self.current_device]
|
||||
if self.current_format not in allowable_formats:
|
||||
error_dialog(self, '',
|
||||
_('The {0} device does not support the {1} format.').
|
||||
format(self.current_device, self.current_format),
|
||||
show=True)
|
||||
self.new_device.setCurrentIndex(0)
|
||||
return
|
||||
self.set_fields()
|
||||
|
||||
def new_format_changed(self, txt):
|
||||
|
@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
from contextlib import closing
|
||||
from random import shuffle
|
||||
from threading import Thread
|
||||
@ -20,9 +21,12 @@ from calibre import browser
|
||||
from calibre.gui2 import NONE
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.gui2.store.search_ui import Ui_Dialog
|
||||
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
|
||||
REGEXP_MATCH
|
||||
from calibre.utils.config import DynamicConfig
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.magick.draw import thumbnail
|
||||
from calibre.utils.search_query_parser import SearchQueryParser
|
||||
|
||||
HANG_TIME = 75000 # milliseconds seconds
|
||||
TIMEOUT = 75 # seconds
|
||||
@ -290,11 +294,15 @@ class SearchThread(Thread):
|
||||
while self._run and not self.tasks.empty():
|
||||
try:
|
||||
query, store_name, store_plugin, timeout = self.tasks.get()
|
||||
for res in store_plugin.search(query, timeout=timeout):
|
||||
squery = query
|
||||
for loc in SearchFilter.USABLE_LOCATIONS:
|
||||
squery = re.sub(r'%s:"?(?P<a>[^\s"]+)"?' % loc, '\g<a>', squery)
|
||||
for res in store_plugin.search(squery, timeout=timeout):
|
||||
if not self._run:
|
||||
return
|
||||
res.store_name = store_name
|
||||
self.results.put(res)
|
||||
if SearchFilter(res).parse(query):
|
||||
self.results.put(res)
|
||||
self.tasks.task_done()
|
||||
except:
|
||||
pass
|
||||
@ -450,3 +458,82 @@ class Matches(QAbstractItemModel):
|
||||
if reset:
|
||||
self.reset()
|
||||
|
||||
|
||||
class SearchFilter(SearchQueryParser):
|
||||
|
||||
USABLE_LOCATIONS = [
|
||||
'all',
|
||||
'author',
|
||||
'authors',
|
||||
'cover',
|
||||
'price',
|
||||
'title',
|
||||
'store',
|
||||
]
|
||||
|
||||
def __init__(self, search_result):
|
||||
SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS)
|
||||
self.search_result = search_result
|
||||
|
||||
def universal_set(self):
|
||||
return set([self.search_result])
|
||||
|
||||
def get_matches(self, location, query):
|
||||
location = location.lower().strip()
|
||||
if location == 'authors':
|
||||
location = 'author'
|
||||
|
||||
matchkind = CONTAINS_MATCH
|
||||
if len(query) > 1:
|
||||
if query.startswith('\\'):
|
||||
query = query[1:]
|
||||
elif query.startswith('='):
|
||||
matchkind = EQUALS_MATCH
|
||||
query = query[1:]
|
||||
elif query.startswith('~'):
|
||||
matchkind = REGEXP_MATCH
|
||||
query = query[1:]
|
||||
if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D
|
||||
query = query.lower()
|
||||
|
||||
if location not in self.USABLE_LOCATIONS:
|
||||
return set([])
|
||||
matches = set([])
|
||||
all_locs = set(self.USABLE_LOCATIONS) - set(['all'])
|
||||
locations = all_locs if location == 'all' else [location]
|
||||
q = {
|
||||
'author': self.search_result.author.lower(),
|
||||
'cover': self.search_result.cover_url,
|
||||
'format': '',
|
||||
'price': self.search_result.price,
|
||||
'store': self.search_result.store_name.lower(),
|
||||
'title': self.search_result.title.lower(),
|
||||
}
|
||||
for x in ('author', 'format'):
|
||||
q[x+'s'] = q[x]
|
||||
for locvalue in locations:
|
||||
ac_val = q[locvalue]
|
||||
if query == 'true':
|
||||
if ac_val is not None:
|
||||
matches.add(self.search_result)
|
||||
continue
|
||||
if query == 'false':
|
||||
if ac_val is None:
|
||||
matches.add(self.search_result)
|
||||
continue
|
||||
try:
|
||||
### Can't separate authors because comma is used for name sep and author sep
|
||||
### Exact match might not get what you want. For that reason, turn author
|
||||
### exactmatch searches into contains searches.
|
||||
if locvalue == 'author' and matchkind == EQUALS_MATCH:
|
||||
m = CONTAINS_MATCH
|
||||
else:
|
||||
m = matchkind
|
||||
|
||||
vals = [ac_val]
|
||||
if _match(query, vals, m):
|
||||
matches.add(self.search_result)
|
||||
break
|
||||
except ValueError: # Unicode errors
|
||||
traceback.print_exc()
|
||||
return matches
|
||||
|
@ -31,10 +31,14 @@ class NPWebView(QWebView):
|
||||
proxy_parts = urlparse(http_proxy)
|
||||
proxy = QNetworkProxy()
|
||||
proxy.setType(QNetworkProxy.HttpProxy)
|
||||
proxy.setUser(proxy_parts.username)
|
||||
proxy.setPassword(proxy_parts.password)
|
||||
proxy.setHostName(proxy_parts.hostname)
|
||||
proxy.setPort(proxy_parts.port)
|
||||
if proxy_parts.username:
|
||||
proxy.setUser(proxy_parts.username)
|
||||
if proxy_parts.password:
|
||||
proxy.setPassword(proxy_parts.password)
|
||||
if proxy_parts.hostname:
|
||||
proxy.setHostName(proxy_parts.hostname)
|
||||
if proxy_parts.port:
|
||||
proxy.setPort(proxy_parts.port)
|
||||
self.page().networkAccessManager().setProxy(proxy)
|
||||
|
||||
self.page().setForwardUnsupportedContent(True)
|
||||
|
@ -51,6 +51,23 @@ for x in FORMAT_ARG_DESCS:
|
||||
FORMAT_ARGS[x] = ''
|
||||
|
||||
|
||||
def find_plugboard(device_name, format, plugboards):
|
||||
cpb = None
|
||||
if format in plugboards:
|
||||
cpb = plugboards[format]
|
||||
elif plugboard_any_format_value in plugboards:
|
||||
cpb = plugboards[plugboard_any_format_value]
|
||||
if cpb is not None:
|
||||
if device_name in cpb:
|
||||
cpb = cpb[device_name]
|
||||
elif plugboard_any_device_value in cpb:
|
||||
cpb = cpb[plugboard_any_device_value]
|
||||
else:
|
||||
cpb = None
|
||||
if DEBUG:
|
||||
prints('Device using plugboard', format, device_name, cpb)
|
||||
return cpb
|
||||
|
||||
def config(defaults=None):
|
||||
if defaults is None:
|
||||
c = Config('save_to_disk', _('Options to control saving to disk'))
|
||||
@ -279,20 +296,7 @@ def do_save_book_to_disk(id_, mi, cover, plugboards,
|
||||
written = False
|
||||
for fmt in formats:
|
||||
global plugboard_save_to_disk_value, plugboard_any_format_value
|
||||
dev_name = plugboard_save_to_disk_value
|
||||
cpb = None
|
||||
if fmt in plugboards:
|
||||
cpb = plugboards[fmt]
|
||||
if dev_name in cpb:
|
||||
cpb = cpb[dev_name]
|
||||
else:
|
||||
cpb = None
|
||||
if cpb is None and plugboard_any_format_value in plugboards:
|
||||
cpb = plugboards[plugboard_any_format_value]
|
||||
if dev_name in cpb:
|
||||
cpb = cpb[dev_name]
|
||||
else:
|
||||
cpb = None
|
||||
cpb = find_plugboard(plugboard_save_to_disk_value, fmt, plugboards)
|
||||
# Leave this here for a while, in case problems arise.
|
||||
if cpb is not None:
|
||||
prints('Save-to-disk using plugboard:', fmt, cpb)
|
||||
|
@ -12,9 +12,14 @@ import cherrypy
|
||||
from calibre import fit_image, guess_type
|
||||
from calibre.utils.date import fromtimestamp
|
||||
from calibre.library.caches import SortKeyGenerator
|
||||
from calibre.library.save_to_disk import find_plugboard
|
||||
|
||||
from calibre.utils.magick.draw import save_cover_data_to, Image, \
|
||||
thumbnail as generate_thumbnail
|
||||
|
||||
plugboard_content_server_value = 'content_server'
|
||||
plugboard_content_server_formats = ['epub']
|
||||
|
||||
class CSSortKeyGenerator(SortKeyGenerator):
|
||||
|
||||
def __init__(self, fields, fm, db_prefs):
|
||||
@ -183,16 +188,30 @@ class ContentServer(object):
|
||||
if fmt is None:
|
||||
raise cherrypy.HTTPError(404, 'book: %d does not have format: %s'%(id, format))
|
||||
if format == 'EPUB':
|
||||
# Get the original metadata
|
||||
mi = self.db.get_metadata(id, index_is_id=True)
|
||||
|
||||
# Get any EPUB plugboards for the content server
|
||||
plugboards = self.db.prefs.get('plugboards', {})
|
||||
cpb = find_plugboard(plugboard_content_server_value,
|
||||
'epub', plugboards)
|
||||
if cpb:
|
||||
# Transform the metadata via the plugboard
|
||||
newmi = mi.deepcopy_metadata()
|
||||
newmi.template_to_attribute(mi, cpb)
|
||||
else:
|
||||
newmi = mi
|
||||
|
||||
# Write the updated file
|
||||
from tempfile import TemporaryFile
|
||||
from calibre.ebooks.metadata.meta import set_metadata
|
||||
raw = fmt.read()
|
||||
fmt = TemporaryFile()
|
||||
fmt.write(raw)
|
||||
fmt.seek(0)
|
||||
set_metadata(fmt, self.db.get_metadata(id, index_is_id=True,
|
||||
get_cover=True),
|
||||
'epub')
|
||||
set_metadata(fmt, newmi, 'epub')
|
||||
fmt.seek(0)
|
||||
|
||||
mt = guess_type('dummy.'+format.lower())[0]
|
||||
if mt is None:
|
||||
mt = 'application/octet-stream'
|
||||
|
Loading…
x
Reference in New Issue
Block a user