mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
...
This commit is contained in:
parent
03cb146ad0
commit
94c4b74e53
@ -8,20 +8,20 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import Qt, QMenu, QToolButton, QDialog, QVBoxLayout
|
from PyQt4.Qt import QMenu
|
||||||
|
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
|
|
||||||
class StoreAction(InterfaceAction):
|
class StoreAction(InterfaceAction):
|
||||||
|
|
||||||
name = 'Store'
|
name = 'Store'
|
||||||
action_spec = (_('Store'), 'store.png', None, None)
|
action_spec = (_('Get books'), 'store.png', None, None)
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
self.qaction.triggered.connect(self.search)
|
self.qaction.triggered.connect(self.search)
|
||||||
self.store_menu = QMenu()
|
self.store_menu = QMenu()
|
||||||
self.load_menu()
|
self.load_menu()
|
||||||
|
|
||||||
def load_menu(self):
|
def load_menu(self):
|
||||||
self.store_menu.clear()
|
self.store_menu.clear()
|
||||||
self.store_menu.addAction(_('Search'), self.search)
|
self.store_menu.addAction(_('Search'), self.search)
|
||||||
@ -29,11 +29,11 @@ class StoreAction(InterfaceAction):
|
|||||||
for n, p in self.gui.istores.items():
|
for n, p in self.gui.istores.items():
|
||||||
self.store_menu.addAction(n, partial(self.open_store, p))
|
self.store_menu.addAction(n, partial(self.open_store, p))
|
||||||
self.qaction.setMenu(self.store_menu)
|
self.qaction.setMenu(self.store_menu)
|
||||||
|
|
||||||
def search(self):
|
def search(self):
|
||||||
from calibre.gui2.store.search import SearchDialog
|
from calibre.gui2.store.search import SearchDialog
|
||||||
sd = SearchDialog(self.gui.istores, self.gui)
|
sd = SearchDialog(self.gui.istores, self.gui)
|
||||||
sd.exec_()
|
sd.exec_()
|
||||||
|
|
||||||
def open_store(self, store_plugin):
|
def open_store(self, store_plugin):
|
||||||
store_plugin.open(self.gui)
|
store_plugin.open(self.gui)
|
||||||
|
@ -19,27 +19,27 @@ class StorePlugin(object): # {{{
|
|||||||
|
|
||||||
If two :class:`StorePlugin` objects have the same name, the one with higher
|
If two :class:`StorePlugin` objects have the same name, the one with higher
|
||||||
priority takes precedence.
|
priority takes precedence.
|
||||||
|
|
||||||
Sub-classes must implement :meth:`open`, and :meth:`search`.
|
Sub-classes must implement :meth:`open`, and :meth:`search`.
|
||||||
|
|
||||||
Regarding :meth:`open`. Most stores only make themselves available
|
Regarding :meth:`open`. Most stores only make themselves available
|
||||||
though a web site thus most store plugins will open using
|
though a web site thus most store plugins will open using
|
||||||
:class:`calibre.gui2.store.web_store_dialog.WebStoreDialog`. This will
|
:class:`calibre.gui2.store.web_store_dialog.WebStoreDialog`. This will
|
||||||
open a modal window and display the store website in a QWebView.
|
open a modal window and display the store website in a QWebView.
|
||||||
|
|
||||||
Sub-classes should implement and use the :meth:`genesis` if they require
|
Sub-classes should implement and use the :meth:`genesis` if they require
|
||||||
plugin specific initialization. They should not override or otherwise
|
plugin specific initialization. They should not override or otherwise
|
||||||
reimplement :meth:`__init__`.
|
reimplement :meth:`__init__`.
|
||||||
|
|
||||||
Once initialized, this plugin has access to the main calibre GUI via the
|
Once initialized, this plugin has access to the main calibre GUI via the
|
||||||
:attr:`gui` member. You can access other plugins by name, for example::
|
:attr:`gui` member. You can access other plugins by name, for example::
|
||||||
|
|
||||||
self.gui.istores['Amazon Kindle']
|
self.gui.istores['Amazon Kindle']
|
||||||
|
|
||||||
Plugin authors can use affiliate programs within their plugin. The
|
Plugin authors can use affiliate programs within their plugin. The
|
||||||
distribution of money earned from a store plugin is 70/30. 70% going
|
distribution of money earned from a store plugin is 70/30. 70% going
|
||||||
to the pluin author / maintainer and 30% going to the calibre project.
|
to the pluin author / maintainer and 30% going to the calibre project.
|
||||||
|
|
||||||
The easiest way to handle affiliate money payouts is to randomly select
|
The easiest way to handle affiliate money payouts is to randomly select
|
||||||
between the author's affiliate id and calibre's affiliate id so that
|
between the author's affiliate id and calibre's affiliate id so that
|
||||||
70% of the time the author's id is used.
|
70% of the time the author's id is used.
|
||||||
@ -49,61 +49,61 @@ class StorePlugin(object): # {{{
|
|||||||
self.gui = gui
|
self.gui = gui
|
||||||
self.name = name
|
self.name = name
|
||||||
self.base_plugin = None
|
self.base_plugin = None
|
||||||
|
|
||||||
def open(self, gui, parent=None, detail_item=None, external=False):
|
def open(self, gui, parent=None, detail_item=None, external=False):
|
||||||
'''
|
'''
|
||||||
Open the store.
|
Open the store.
|
||||||
|
|
||||||
:param gui: The main GUI. This will be used to have the job
|
:param gui: The main GUI. This will be used to have the job
|
||||||
system start downloading an item from the store.
|
system start downloading an item from the store.
|
||||||
|
|
||||||
:param parent: The parent of the store dialog. This is used
|
:param parent: The parent of the store dialog. This is used
|
||||||
to create modal dialogs.
|
to create modal dialogs.
|
||||||
|
|
||||||
:param detail_item: A plugin specific reference to an item
|
:param detail_item: A plugin specific reference to an item
|
||||||
in the store that the user should be shown.
|
in the store that the user should be shown.
|
||||||
|
|
||||||
:param external: When False open an internal dialog with the
|
:param external: When False open an internal dialog with the
|
||||||
store. When True open the users default browser to the store's
|
store. When True open the users default browser to the store's
|
||||||
web site. :param:`detail_item` should still be respected when external
|
web site. :param:`detail_item` should still be respected when external
|
||||||
is True.
|
is True.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def search(self, query, max_results=10, timeout=60):
|
def search(self, query, max_results=10, timeout=60):
|
||||||
'''
|
'''
|
||||||
Searches the store for items matching query. This should
|
Searches the store for items matching query. This should
|
||||||
return items as a generator.
|
return items as a generator.
|
||||||
|
|
||||||
Don't be lazy with the search! Load as much data as possible in the
|
Don't be lazy with the search! Load as much data as possible in the
|
||||||
:class:`calibre.gui2.store.search_result.SearchResult` object. If you have to parse
|
:class:`calibre.gui2.store.search_result.SearchResult` object. If you have to parse
|
||||||
multiple pages to get all of the data then do so. However, if data (such as cover_url)
|
multiple pages to get all of the data then do so. However, if data (such as cover_url)
|
||||||
isn't available because the store does not display cover images then it's okay to
|
isn't available because the store does not display cover images then it's okay to
|
||||||
ignore it.
|
ignore it.
|
||||||
|
|
||||||
Also, by default search results can only include ebooks. A plugin can offer users
|
Also, by default search results can only include ebooks. A plugin can offer users
|
||||||
an option to include physical books in the search results but this must be
|
an option to include physical books in the search results but this must be
|
||||||
disabled by default.
|
disabled by default.
|
||||||
|
|
||||||
If a store doesn't provide search on it's own use something like a site specific
|
If a store doesn't provide search on it's own use something like a site specific
|
||||||
google search to get search results for this funtion.
|
google search to get search results for this funtion.
|
||||||
|
|
||||||
:param query: The string query search with.
|
:param query: The string query search with.
|
||||||
:param max_results: The maximum number of results to return.
|
:param max_results: The maximum number of results to return.
|
||||||
:param timeout: The maximum amount of time in seconds to spend download the search results.
|
:param timeout: The maximum amount of time in seconds to spend download the search results.
|
||||||
|
|
||||||
:return: :class:`calibre.gui2.store.search_result.SearchResult` objects
|
:return: :class:`calibre.gui2.store.search_result.SearchResult` objects
|
||||||
item_data is plugin specific and is used in :meth:`open` to open to a specifc place in the store.
|
item_data is plugin specific and is used in :meth:`open` to open to a specifc place in the store.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_settings(self):
|
def get_settings(self):
|
||||||
'''
|
'''
|
||||||
This is only useful for plugins that implement
|
This is only useful for plugins that implement
|
||||||
:attr:`config_widget` that is the only way to save
|
:attr:`config_widget` that is the only way to save
|
||||||
settings. This is used by plugins to get the saved
|
settings. This is used by plugins to get the saved
|
||||||
settings and apply when necessary.
|
settings and apply when necessary.
|
||||||
|
|
||||||
:return: A dictionary filled with the settings used
|
:return: A dictionary filled with the settings used
|
||||||
by this plugin.
|
by this plugin.
|
||||||
'''
|
'''
|
||||||
@ -117,23 +117,23 @@ class StorePlugin(object): # {{{
|
|||||||
Plugin specific initialization.
|
Plugin specific initialization.
|
||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def config_widget(self):
|
def config_widget(self):
|
||||||
'''
|
'''
|
||||||
See :class:`calibre.customize.Plugin` for details.
|
See :class:`calibre.customize.Plugin` for details.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def save_settings(self, config_widget):
|
def save_settings(self, config_widget):
|
||||||
'''
|
'''
|
||||||
See :class:`calibre.customize.Plugin` for details.
|
See :class:`calibre.customize.Plugin` for details.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def customization_help(self, gui=False):
|
def customization_help(self, gui=False):
|
||||||
'''
|
'''
|
||||||
See :class:`calibre.customize.Plugin` for details.
|
See :class:`calibre.customize.Plugin` for details.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -21,14 +21,14 @@ from calibre.gui2.store import StorePlugin
|
|||||||
from calibre.gui2.store.search_result import SearchResult
|
from calibre.gui2.store.search_result import SearchResult
|
||||||
|
|
||||||
class AmazonKindleStore(StorePlugin):
|
class AmazonKindleStore(StorePlugin):
|
||||||
|
|
||||||
def open(self, parent=None, detail_item=None, external=False):
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
'''
|
'''
|
||||||
Amazon comes with a number of difficulties.
|
Amazon comes with a number of difficulties.
|
||||||
|
|
||||||
QWebView has major issues with Amazon.com. The largest of
|
QWebView has major issues with Amazon.com. The largest of
|
||||||
issues is it simply doesn't work on a number of pages.
|
issues is it simply doesn't work on a number of pages.
|
||||||
|
|
||||||
When connecting to a number parts of Amazon.com (Kindle library
|
When connecting to a number parts of Amazon.com (Kindle library
|
||||||
for instance) QNetworkAccessManager fails to connect with a
|
for instance) QNetworkAccessManager fails to connect with a
|
||||||
NetworkError of 399 - ProtocolFailure. The strange thing is,
|
NetworkError of 399 - ProtocolFailure. The strange thing is,
|
||||||
@ -37,19 +37,19 @@ class AmazonKindleStore(StorePlugin):
|
|||||||
the QNetworkAccessManager decides there was a NetworkError it
|
the QNetworkAccessManager decides there was a NetworkError it
|
||||||
does not download the page from Amazon. So I can't even set the
|
does not download the page from Amazon. So I can't even set the
|
||||||
HTML in the QWebView myself.
|
HTML in the QWebView myself.
|
||||||
|
|
||||||
There is http://bugreports.qt.nokia.com/browse/QTWEBKIT-259 an
|
There is http://bugreports.qt.nokia.com/browse/QTWEBKIT-259 an
|
||||||
open bug about the issue but it is not correct. We can set the
|
open bug about the issue but it is not correct. We can set the
|
||||||
useragent (Arora does) to something else and the above issue
|
useragent (Arora does) to something else and the above issue
|
||||||
will persist. This http://developer.qt.nokia.com/forums/viewthread/793
|
will persist. This http://developer.qt.nokia.com/forums/viewthread/793
|
||||||
gives a bit more information about the issue but as of now (27/Feb/2011)
|
gives a bit more information about the issue but as of now (27/Feb/2011)
|
||||||
there is no solution or work around.
|
there is no solution or work around.
|
||||||
|
|
||||||
We cannot change the The linkDelegationPolicy to allow us to avoid
|
We cannot change the The linkDelegationPolicy to allow us to avoid
|
||||||
QNetworkAccessManager because it only works links. Forms aren't
|
QNetworkAccessManager because it only works links. Forms aren't
|
||||||
included so the same issue persists on any part of the site (login)
|
included so the same issue persists on any part of the site (login)
|
||||||
that use a form to load a new page.
|
that use a form to load a new page.
|
||||||
|
|
||||||
Using an aStore was evaluated but I've decided against using it.
|
Using an aStore was evaluated but I've decided against using it.
|
||||||
There are three major issues with an aStore. Because checkout is
|
There are three major issues with an aStore. Because checkout is
|
||||||
handled by sending the user to Amazon we can't put it in a QWebView.
|
handled by sending the user to Amazon we can't put it in a QWebView.
|
||||||
@ -57,7 +57,7 @@ class AmazonKindleStore(StorePlugin):
|
|||||||
nicer. Also, we cannot put the aStore in a QWebView and let it open the
|
nicer. Also, we cannot put the aStore in a QWebView and let it open the
|
||||||
redirection the users default browser because the cookies with the
|
redirection the users default browser because the cookies with the
|
||||||
shopping cart won't transfer.
|
shopping cart won't transfer.
|
||||||
|
|
||||||
Another issue with the aStore is how it handles the referral. It only
|
Another issue with the aStore is how it handles the referral. It only
|
||||||
counts the referral for the items in the shopping card / the item
|
counts the referral for the items in the shopping card / the item
|
||||||
that directed the user to Amazon. Kindle books do not use the shopping
|
that directed the user to Amazon. Kindle books do not use the shopping
|
||||||
@ -65,44 +65,44 @@ class AmazonKindleStore(StorePlugin):
|
|||||||
instance we would only get referral credit for the one book that the
|
instance we would only get referral credit for the one book that the
|
||||||
aStore directs to Amazon that the user buys. Any other purchases we
|
aStore directs to Amazon that the user buys. Any other purchases we
|
||||||
won't get credit for.
|
won't get credit for.
|
||||||
|
|
||||||
The last issue with the aStore is performance. Even though it's an
|
The last issue with the aStore is performance. Even though it's an
|
||||||
Amazon site it's alow. So much slower than Amazon.com that it makes
|
Amazon site it's alow. So much slower than Amazon.com that it makes
|
||||||
me not want to browse books using it. The look and feel are lesser
|
me not want to browse books using it. The look and feel are lesser
|
||||||
issues. So is the fact that it almost seems like the purchase is
|
issues. So is the fact that it almost seems like the purchase is
|
||||||
with calibre. This can cause some support issues because we can't
|
with calibre. This can cause some support issues because we can't
|
||||||
do much for issues with Amazon.com purchase hiccups.
|
do much for issues with Amazon.com purchase hiccups.
|
||||||
|
|
||||||
Another option that was evaluated was the Product Advertising API.
|
Another option that was evaluated was the Product Advertising API.
|
||||||
The reasons against this are complexity. It would take a lot of work
|
The reasons against this are complexity. It would take a lot of work
|
||||||
to basically re-create Amazon.com within calibre. The Product
|
to basically re-create Amazon.com within calibre. The Product
|
||||||
Advertising API is also designed with being run on a server not
|
Advertising API is also designed with being run on a server not
|
||||||
in an app. The signing keys would have to be made avaliable to ever
|
in an app. The signing keys would have to be made avaliable to ever
|
||||||
calibre user which means bad things could be done with our account.
|
calibre user which means bad things could be done with our account.
|
||||||
|
|
||||||
The Product Advertising API also assumes the same browser for easy
|
The Product Advertising API also assumes the same browser for easy
|
||||||
shopping cart transfer to Amazon. With QWebView not working and there
|
shopping cart transfer to Amazon. With QWebView not working and there
|
||||||
not being an easy way to transfer cookies between a QWebView and the
|
not being an easy way to transfer cookies between a QWebView and the
|
||||||
users default browser this won't work well.
|
users default browser this won't work well.
|
||||||
|
|
||||||
We could create our own website on the calibre server and create an
|
We could create our own website on the calibre server and create an
|
||||||
Amazon Product Advertising API store. However, this goes back to the
|
Amazon Product Advertising API store. However, this goes back to the
|
||||||
complexity argument. Why spend the time recreating Amazon.com
|
complexity argument. Why spend the time recreating Amazon.com
|
||||||
|
|
||||||
The final and largest issue against using the Product Advertising API
|
The final and largest issue against using the Product Advertising API
|
||||||
is the Efficiency Guidelines:
|
is the Efficiency Guidelines:
|
||||||
|
|
||||||
"Each account used to access the Product Advertising API will be allowed
|
"Each account used to access the Product Advertising API will be allowed
|
||||||
an initial usage limit of 2,000 requests per hour. Each account will
|
an initial usage limit of 2,000 requests per hour. Each account will
|
||||||
receive an additional 500 requests per hour (up to a maximum of 25,000
|
receive an additional 500 requests per hour (up to a maximum of 25,000
|
||||||
requests per hour) for every $1 of shipped item revenue driven per hour
|
requests per hour) for every $1 of shipped item revenue driven per hour
|
||||||
in a trailing 30-day period. Usage thresholds are recalculated daily based
|
in a trailing 30-day period. Usage thresholds are recalculated daily based
|
||||||
on revenue performance."
|
on revenue performance."
|
||||||
|
|
||||||
With over two million users a limit of 2,000 request per hour could
|
With over two million users a limit of 2,000 request per hour could
|
||||||
render our store unusable for no other reason than Amazon rate
|
render our store unusable for no other reason than Amazon rate
|
||||||
limiting our traffic.
|
limiting our traffic.
|
||||||
|
|
||||||
The best (I use the term lightly here) solution is to open Amazon.com
|
The best (I use the term lightly here) solution is to open Amazon.com
|
||||||
in the users default browser and set the affiliate id as part of the url.
|
in the users default browser and set the affiliate id as part of the url.
|
||||||
'''
|
'''
|
||||||
@ -119,14 +119,14 @@ class AmazonKindleStore(StorePlugin):
|
|||||||
def search(self, query, max_results=10, timeout=60):
|
def search(self, query, max_results=10, timeout=60):
|
||||||
url = 'http://www.amazon.com/s/url=search-alias%3Ddigital-text&field-keywords=' + urllib2.quote(query)
|
url = 'http://www.amazon.com/s/url=search-alias%3Ddigital-text&field-keywords=' + urllib2.quote(query)
|
||||||
br = browser()
|
br = browser()
|
||||||
|
|
||||||
counter = max_results
|
counter = max_results
|
||||||
with closing(br.open(url, timeout=timeout)) as f:
|
with closing(br.open(url, timeout=timeout)) as f:
|
||||||
doc = html.fromstring(f.read())
|
doc = html.fromstring(f.read())
|
||||||
for data in doc.xpath('//div[@class="productData"]'):
|
for data in doc.xpath('//div[@class="productData"]'):
|
||||||
if counter <= 0:
|
if counter <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
# Even though we are searching digital-text only Amazon will still
|
# Even though we are searching digital-text only Amazon will still
|
||||||
# put in results for non Kindle books (author pages). Se we need
|
# put in results for non Kindle books (author pages). Se we need
|
||||||
# to explicitly check if the item is a Kindle book and ignore it
|
# to explicitly check if the item is a Kindle book and ignore it
|
||||||
@ -134,7 +134,7 @@ class AmazonKindleStore(StorePlugin):
|
|||||||
type = ''.join(data.xpath('//span[@class="format"]/text()'))
|
type = ''.join(data.xpath('//span[@class="format"]/text()'))
|
||||||
if 'kindle' not in type.lower():
|
if 'kindle' not in type.lower():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# We must have an asin otherwise we can't easily reference the
|
# We must have an asin otherwise we can't easily reference the
|
||||||
# book later.
|
# book later.
|
||||||
asin_href = None
|
asin_href = None
|
||||||
@ -148,25 +148,25 @@ class AmazonKindleStore(StorePlugin):
|
|||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cover_url = ''
|
cover_url = ''
|
||||||
if asin_href:
|
if asin_href:
|
||||||
cover_img = data.xpath('//div[@class="productImage"]/a[@href="%s"]/img/@src' % asin_href)
|
cover_img = data.xpath('//div[@class="productImage"]/a[@href="%s"]/img/@src' % asin_href)
|
||||||
if cover_img:
|
if cover_img:
|
||||||
cover_url = cover_img[0]
|
cover_url = cover_img[0]
|
||||||
|
|
||||||
title = ''.join(data.xpath('div[@class="productTitle"]/a/text()'))
|
title = ''.join(data.xpath('div[@class="productTitle"]/a/text()'))
|
||||||
author = ''.join(data.xpath('div[@class="productTitle"]/span[@class="ptBrand"]/text()'))
|
author = ''.join(data.xpath('div[@class="productTitle"]/span[@class="ptBrand"]/text()'))
|
||||||
author = author.split('by')[-1]
|
author = author.split('by')[-1]
|
||||||
price = ''.join(data.xpath('div[@class="newPrice"]/span/text()'))
|
price = ''.join(data.xpath('div[@class="newPrice"]/span/text()'))
|
||||||
|
|
||||||
counter -= 1
|
counter -= 1
|
||||||
|
|
||||||
s = SearchResult()
|
s = SearchResult()
|
||||||
s.cover_url = cover_url
|
s.cover_url = cover_url
|
||||||
s.title = title.strip()
|
s.title = title.strip()
|
||||||
s.author = author.strip()
|
s.author = author.strip()
|
||||||
s.price = price.strip()
|
s.price = price.strip()
|
||||||
s.detail_item = asin.strip()
|
s.detail_item = asin.strip()
|
||||||
|
|
||||||
yield s
|
yield s
|
||||||
|
@ -6,7 +6,6 @@ __license__ = 'GPL 3'
|
|||||||
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import re
|
|
||||||
import urllib2
|
import urllib2
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
|
|
||||||
@ -22,7 +21,7 @@ from calibre.gui2.store.search_result import SearchResult
|
|||||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||||
|
|
||||||
class BeWriteStore(BasicStoreConfig, StorePlugin):
|
class BeWriteStore(BasicStoreConfig, StorePlugin):
|
||||||
|
|
||||||
def open(self, parent=None, detail_item=None, external=False):
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
settings = self.get_settings()
|
settings = self.get_settings()
|
||||||
url = 'http://www.bewrite.net/mm5/merchant.mvc?Screen=SFNT'
|
url = 'http://www.bewrite.net/mm5/merchant.mvc?Screen=SFNT'
|
||||||
@ -42,9 +41,9 @@ class BeWriteStore(BasicStoreConfig, StorePlugin):
|
|||||||
|
|
||||||
def search(self, query, max_results=10, timeout=60):
|
def search(self, query, max_results=10, timeout=60):
|
||||||
url = 'http://www.bewrite.net/mm5/merchant.mvc?Search_Code=B&Screen=SRCH&Search=' + urllib2.quote(query)
|
url = 'http://www.bewrite.net/mm5/merchant.mvc?Search_Code=B&Screen=SRCH&Search=' + urllib2.quote(query)
|
||||||
|
|
||||||
br = browser()
|
br = browser()
|
||||||
|
|
||||||
counter = max_results
|
counter = max_results
|
||||||
with closing(br.open(url, timeout=timeout)) as f:
|
with closing(br.open(url, timeout=timeout)) as f:
|
||||||
doc = html.fromstring(f.read())
|
doc = html.fromstring(f.read())
|
||||||
@ -55,12 +54,12 @@ class BeWriteStore(BasicStoreConfig, StorePlugin):
|
|||||||
id = ''.join(data.xpath('.//a/@href'))
|
id = ''.join(data.xpath('.//a/@href'))
|
||||||
if not id:
|
if not id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
heading = ''.join(data.xpath('./td[2]//text()'))
|
heading = ''.join(data.xpath('./td[2]//text()'))
|
||||||
title, q, author = heading.partition('by ')
|
title, q, author = heading.partition('by ')
|
||||||
cover_url = ''
|
cover_url = ''
|
||||||
price = ''
|
price = ''
|
||||||
|
|
||||||
with closing(br.open(id.strip(), timeout=timeout/4)) as nf:
|
with closing(br.open(id.strip(), timeout=timeout/4)) as nf:
|
||||||
idata = html.fromstring(nf.read())
|
idata = html.fromstring(nf.read())
|
||||||
price = ''.join(idata.xpath('//div[@id="content"]//td[contains(text(), "ePub")]/text()'))
|
price = ''.join(idata.xpath('//div[@id="content"]//td[contains(text(), "ePub")]/text()'))
|
||||||
@ -68,14 +67,14 @@ class BeWriteStore(BasicStoreConfig, StorePlugin):
|
|||||||
cover_img = idata.xpath('//div[@id="content"]//img[1]/@src')
|
cover_img = idata.xpath('//div[@id="content"]//img[1]/@src')
|
||||||
if cover_img:
|
if cover_img:
|
||||||
cover_url = 'http://www.bewrite.net/mm5/' + cover_img[0]
|
cover_url = 'http://www.bewrite.net/mm5/' + cover_img[0]
|
||||||
|
|
||||||
counter -= 1
|
counter -= 1
|
||||||
|
|
||||||
s = SearchResult()
|
s = SearchResult()
|
||||||
s.cover_url = cover_url.strip()
|
s.cover_url = cover_url.strip()
|
||||||
s.title = title.strip()
|
s.title = title.strip()
|
||||||
s.author = author.strip()
|
s.author = author.strip()
|
||||||
s.price = price.strip()
|
s.price = price.strip()
|
||||||
s.detail_item = id.strip()
|
s.detail_item = id.strip()
|
||||||
|
|
||||||
yield s
|
yield s
|
||||||
|
@ -13,9 +13,8 @@ from random import shuffle
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
|
|
||||||
from PyQt4.Qt import Qt, QAbstractItemModel, QDialog, QTimer, QVariant, \
|
from PyQt4.Qt import (Qt, QAbstractItemModel, QDialog, QTimer, QVariant,
|
||||||
QModelIndex, QPixmap, QSize, QCheckBox, QVBoxLayout, QHBoxLayout, \
|
QModelIndex, QPixmap, QSize, QCheckBox, QVBoxLayout)
|
||||||
QPushButton, QString, QByteArray
|
|
||||||
|
|
||||||
from calibre import browser
|
from calibre import browser
|
||||||
from calibre.gui2 import NONE
|
from calibre.gui2 import NONE
|
||||||
@ -35,7 +34,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
def __init__(self, istores, *args):
|
def __init__(self, istores, *args):
|
||||||
QDialog.__init__(self, *args)
|
QDialog.__init__(self, *args)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
self.config = DynamicConfig('store_search')
|
self.config = DynamicConfig('store_search')
|
||||||
|
|
||||||
# We keep a cache of store plugins and reference them by name.
|
# We keep a cache of store plugins and reference them by name.
|
||||||
@ -44,7 +43,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
# Check for results and hung threads.
|
# Check for results and hung threads.
|
||||||
self.checker = QTimer()
|
self.checker = QTimer()
|
||||||
self.hang_check = 0
|
self.hang_check = 0
|
||||||
|
|
||||||
self.model = Matches()
|
self.model = Matches()
|
||||||
self.results_view.setModel(self.model)
|
self.results_view.setModel(self.model)
|
||||||
|
|
||||||
@ -59,7 +58,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
stores_group_layout.addWidget(cbox)
|
stores_group_layout.addWidget(cbox)
|
||||||
setattr(self, 'store_check_' + x, cbox)
|
setattr(self, 'store_check_' + x, cbox)
|
||||||
stores_group_layout.addStretch()
|
stores_group_layout.addStretch()
|
||||||
|
|
||||||
# Create and add the progress indicator
|
# Create and add the progress indicator
|
||||||
self.pi = ProgressIndicator(self, 24)
|
self.pi = ProgressIndicator(self, 24)
|
||||||
self.bottom_layout.insertWidget(0, self.pi)
|
self.bottom_layout.insertWidget(0, self.pi)
|
||||||
@ -71,9 +70,9 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.select_invert_stores.clicked.connect(self.stores_select_invert)
|
self.select_invert_stores.clicked.connect(self.stores_select_invert)
|
||||||
self.select_none_stores.clicked.connect(self.stores_select_none)
|
self.select_none_stores.clicked.connect(self.stores_select_none)
|
||||||
self.finished.connect(self.dialog_closed)
|
self.finished.connect(self.dialog_closed)
|
||||||
|
|
||||||
self.restore_state()
|
self.restore_state()
|
||||||
|
|
||||||
def resize_columns(self):
|
def resize_columns(self):
|
||||||
total = 600
|
total = 600
|
||||||
# Cover
|
# Cover
|
||||||
@ -87,19 +86,19 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.results_view.setColumnWidth(3, int(total*.10))
|
self.results_view.setColumnWidth(3, int(total*.10))
|
||||||
# Store
|
# Store
|
||||||
self.results_view.setColumnWidth(4, int(total*.20))
|
self.results_view.setColumnWidth(4, int(total*.20))
|
||||||
|
|
||||||
def do_search(self, checked=False):
|
def do_search(self, checked=False):
|
||||||
# Stop all running threads.
|
# Stop all running threads.
|
||||||
self.checker.stop()
|
self.checker.stop()
|
||||||
self.search_pool.abort()
|
self.search_pool.abort()
|
||||||
# Clear the visible results.
|
# Clear the visible results.
|
||||||
self.results_view.model().clear_results()
|
self.results_view.model().clear_results()
|
||||||
|
|
||||||
# Don't start a search if there is nothing to search for.
|
# Don't start a search if there is nothing to search for.
|
||||||
query = unicode(self.search_edit.text())
|
query = unicode(self.search_edit.text())
|
||||||
if not query.strip():
|
if not query.strip():
|
||||||
return
|
return
|
||||||
|
|
||||||
# Plugins are in alphebetic order. Randomize the
|
# Plugins are in alphebetic order. Randomize the
|
||||||
# order of plugin names. This way plugins closer
|
# order of plugin names. This way plugins closer
|
||||||
# to a don't have an unfair advantage over
|
# to a don't have an unfair advantage over
|
||||||
@ -117,12 +116,12 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.checker.start(100)
|
self.checker.start(100)
|
||||||
self.search_pool.start_threads()
|
self.search_pool.start_threads()
|
||||||
self.pi.startAnimation()
|
self.pi.startAnimation()
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
self.config['store_search_geometry'] = self.saveGeometry()
|
self.config['store_search_geometry'] = self.saveGeometry()
|
||||||
self.config['store_search_store_splitter_state'] = self.store_splitter.saveState()
|
self.config['store_search_store_splitter_state'] = self.store_splitter.saveState()
|
||||||
self.config['store_search_results_view_column_width'] = [self.results_view.columnWidth(i) for i in range(self.model.columnCount())]
|
self.config['store_search_results_view_column_width'] = [self.results_view.columnWidth(i) for i in range(self.model.columnCount())]
|
||||||
|
|
||||||
store_check = {}
|
store_check = {}
|
||||||
for n in self.store_plugins:
|
for n in self.store_plugins:
|
||||||
store_check[n] = getattr(self, 'store_check_' + n).isChecked()
|
store_check[n] = getattr(self, 'store_check_' + n).isChecked()
|
||||||
@ -132,11 +131,11 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
geometry = self.config['store_search_geometry']
|
geometry = self.config['store_search_geometry']
|
||||||
if geometry:
|
if geometry:
|
||||||
self.restoreGeometry(geometry)
|
self.restoreGeometry(geometry)
|
||||||
|
|
||||||
splitter_state = self.config['store_search_store_splitter_state']
|
splitter_state = self.config['store_search_store_splitter_state']
|
||||||
if splitter_state:
|
if splitter_state:
|
||||||
self.store_splitter.restoreState(splitter_state)
|
self.store_splitter.restoreState(splitter_state)
|
||||||
|
|
||||||
results_cwidth = self.config['store_search_results_view_column_width']
|
results_cwidth = self.config['store_search_results_view_column_width']
|
||||||
if results_cwidth:
|
if results_cwidth:
|
||||||
for i, x in enumerate(results_cwidth):
|
for i, x in enumerate(results_cwidth):
|
||||||
@ -145,7 +144,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.results_view.setColumnWidth(i, x)
|
self.results_view.setColumnWidth(i, x)
|
||||||
else:
|
else:
|
||||||
self.resize_columns()
|
self.resize_columns()
|
||||||
|
|
||||||
store_check = self.config['store_search_store_checked']
|
store_check = self.config['store_search_store_checked']
|
||||||
if store_check:
|
if store_check:
|
||||||
for n in store_check:
|
for n in store_check:
|
||||||
@ -165,7 +164,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
if not self.search_pool.threads_running() and not self.search_pool.has_tasks():
|
if not self.search_pool.threads_running() and not self.search_pool.has_tasks():
|
||||||
self.checker.stop()
|
self.checker.stop()
|
||||||
self.pi.stopAnimation()
|
self.pi.stopAnimation()
|
||||||
|
|
||||||
while self.search_pool.has_results():
|
while self.search_pool.has_results():
|
||||||
res = self.search_pool.get_result()
|
res = self.search_pool.get_result()
|
||||||
if res:
|
if res:
|
||||||
@ -189,15 +188,15 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
def stores_select_all(self):
|
def stores_select_all(self):
|
||||||
for check in self.get_store_checks():
|
for check in self.get_store_checks():
|
||||||
check.setChecked(True)
|
check.setChecked(True)
|
||||||
|
|
||||||
def stores_select_invert(self):
|
def stores_select_invert(self):
|
||||||
for check in self.get_store_checks():
|
for check in self.get_store_checks():
|
||||||
check.setChecked(not check.isChecked())
|
check.setChecked(not check.isChecked())
|
||||||
|
|
||||||
def stores_select_none(self):
|
def stores_select_none(self):
|
||||||
for check in self.get_store_checks():
|
for check in self.get_store_checks():
|
||||||
check.setChecked(False)
|
check.setChecked(False)
|
||||||
|
|
||||||
def dialog_closed(self, result):
|
def dialog_closed(self, result):
|
||||||
self.model.closing()
|
self.model.closing()
|
||||||
self.search_pool.abort()
|
self.search_pool.abort()
|
||||||
@ -208,46 +207,46 @@ class GenericDownloadThreadPool(object):
|
|||||||
'''
|
'''
|
||||||
add_task must be implemented in a subclass.
|
add_task must be implemented in a subclass.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, thread_type, thread_count):
|
def __init__(self, thread_type, thread_count):
|
||||||
self.thread_type = thread_type
|
self.thread_type = thread_type
|
||||||
self.thread_count = thread_count
|
self.thread_count = thread_count
|
||||||
|
|
||||||
self.tasks = Queue()
|
self.tasks = Queue()
|
||||||
self.results = Queue()
|
self.results = Queue()
|
||||||
self.threads = []
|
self.threads = []
|
||||||
|
|
||||||
def add_task(self):
|
def add_task(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def start_threads(self):
|
def start_threads(self):
|
||||||
for i in range(self.thread_count):
|
for i in range(self.thread_count):
|
||||||
t = self.thread_type(self.tasks, self.results)
|
t = self.thread_type(self.tasks, self.results)
|
||||||
self.threads.append(t)
|
self.threads.append(t)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
def abort(self):
|
def abort(self):
|
||||||
self.tasks = Queue()
|
self.tasks = Queue()
|
||||||
self.results = Queue()
|
self.results = Queue()
|
||||||
for t in self.threads:
|
for t in self.threads:
|
||||||
t.abort()
|
t.abort()
|
||||||
self.threads = []
|
self.threads = []
|
||||||
|
|
||||||
def has_tasks(self):
|
def has_tasks(self):
|
||||||
return not self.tasks.empty()
|
return not self.tasks.empty()
|
||||||
|
|
||||||
def get_result(self):
|
def get_result(self):
|
||||||
return self.results.get()
|
return self.results.get()
|
||||||
|
|
||||||
def get_result_no_wait(self):
|
def get_result_no_wait(self):
|
||||||
return self.results.get_nowait()
|
return self.results.get_nowait()
|
||||||
|
|
||||||
def result_count(self):
|
def result_count(self):
|
||||||
return len(self.results)
|
return len(self.results)
|
||||||
|
|
||||||
def has_results(self):
|
def has_results(self):
|
||||||
return not self.results.empty()
|
return not self.results.empty()
|
||||||
|
|
||||||
def threads_running(self):
|
def threads_running(self):
|
||||||
for t in self.threads:
|
for t in self.threads:
|
||||||
if t.is_alive():
|
if t.is_alive():
|
||||||
@ -260,7 +259,7 @@ class SearchThreadPool(GenericDownloadThreadPool):
|
|||||||
Threads will run until there is no work or
|
Threads will run until there is no work or
|
||||||
abort is called. Create and start new threads
|
abort is called. Create and start new threads
|
||||||
using start_threads(). Reset by calling abort().
|
using start_threads(). Reset by calling abort().
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
sp = SearchThreadPool(SearchThread, 3)
|
sp = SearchThreadPool(SearchThread, 3)
|
||||||
add tasks using add_task(...)
|
add tasks using add_task(...)
|
||||||
@ -270,13 +269,13 @@ class SearchThreadPool(GenericDownloadThreadPool):
|
|||||||
add tasks using add_task(...)
|
add tasks using add_task(...)
|
||||||
sp.start_threads()
|
sp.start_threads()
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def add_task(self, query, store_name, store_plugin, timeout):
|
def add_task(self, query, store_name, store_plugin, timeout):
|
||||||
self.tasks.put((query, store_name, store_plugin, timeout))
|
self.tasks.put((query, store_name, store_plugin, timeout))
|
||||||
|
|
||||||
|
|
||||||
class SearchThread(Thread):
|
class SearchThread(Thread):
|
||||||
|
|
||||||
def __init__(self, tasks, results):
|
def __init__(self, tasks, results):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
@ -286,7 +285,7 @@ class SearchThread(Thread):
|
|||||||
|
|
||||||
def abort(self):
|
def abort(self):
|
||||||
self._run = False
|
self._run = False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while self._run and not self.tasks.empty():
|
while self._run and not self.tasks.empty():
|
||||||
try:
|
try:
|
||||||
@ -305,7 +304,7 @@ class CoverThreadPool(GenericDownloadThreadPool):
|
|||||||
'''
|
'''
|
||||||
Once started all threads run until abort is called.
|
Once started all threads run until abort is called.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def add_task(self, search_result, update_callback, timeout=5):
|
def add_task(self, search_result, update_callback, timeout=5):
|
||||||
self.tasks.put((search_result, update_callback, timeout))
|
self.tasks.put((search_result, update_callback, timeout))
|
||||||
|
|
||||||
@ -318,12 +317,12 @@ class CoverThread(Thread):
|
|||||||
self.tasks = tasks
|
self.tasks = tasks
|
||||||
self.results = results
|
self.results = results
|
||||||
self._run = True
|
self._run = True
|
||||||
|
|
||||||
self.br = browser()
|
self.br = browser()
|
||||||
|
|
||||||
def abort(self):
|
def abort(self):
|
||||||
self._run = False
|
self._run = False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while self._run:
|
while self._run:
|
||||||
try:
|
try:
|
||||||
@ -354,13 +353,13 @@ class Matches(QAbstractItemModel):
|
|||||||
|
|
||||||
def closing(self):
|
def closing(self):
|
||||||
self.cover_pool.abort()
|
self.cover_pool.abort()
|
||||||
|
|
||||||
def clear_results(self):
|
def clear_results(self):
|
||||||
self.matches = []
|
self.matches = []
|
||||||
self.cover_pool.abort()
|
self.cover_pool.abort()
|
||||||
self.cover_pool.start_threads()
|
self.cover_pool.start_threads()
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def add_result(self, result):
|
def add_result(self, result):
|
||||||
self.layoutAboutToBeChanged.emit()
|
self.layoutAboutToBeChanged.emit()
|
||||||
self.matches.append(result)
|
self.matches.append(result)
|
||||||
@ -391,7 +390,7 @@ class Matches(QAbstractItemModel):
|
|||||||
|
|
||||||
def columnCount(self, *args):
|
def columnCount(self, *args):
|
||||||
return len(self.HEADERS)
|
return len(self.HEADERS)
|
||||||
|
|
||||||
def headerData(self, section, orientation, role):
|
def headerData(self, section, orientation, role):
|
||||||
if role != Qt.DisplayRole:
|
if role != Qt.DisplayRole:
|
||||||
return NONE
|
return NONE
|
||||||
@ -434,7 +433,7 @@ class Matches(QAbstractItemModel):
|
|||||||
elif col == 3:
|
elif col == 3:
|
||||||
text = result.price
|
text = result.price
|
||||||
if len(text) < 3 or text[-3] not in ('.', ','):
|
if len(text) < 3 or text[-3] not in ('.', ','):
|
||||||
text += '00'
|
text += '00'
|
||||||
text = re.sub(r'\D', '', text)
|
text = re.sub(r'\D', '', text)
|
||||||
text = text.rjust(6, '0')
|
text = text.rjust(6, '0')
|
||||||
elif col == 4:
|
elif col == 4:
|
||||||
@ -444,7 +443,7 @@ class Matches(QAbstractItemModel):
|
|||||||
def sort(self, col, order, reset=True):
|
def sort(self, col, order, reset=True):
|
||||||
if not self.matches:
|
if not self.matches:
|
||||||
return
|
return
|
||||||
descending = order == Qt.DescendingOrder
|
descending = order == Qt.DescendingOrder
|
||||||
self.matches.sort(None,
|
self.matches.sort(None,
|
||||||
lambda x: sort_key(unicode(self.data_as_text(x, col))),
|
lambda x: sort_key(unicode(self.data_as_text(x, col))),
|
||||||
descending)
|
descending)
|
||||||
|
@ -9,8 +9,8 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import os
|
import os
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
from PyQt4.Qt import QWebView, QWebPage, QNetworkCookieJar, QNetworkRequest, QString, \
|
from PyQt4.Qt import (QWebView, QWebPage, QNetworkCookieJar,
|
||||||
QFileDialog, QNetworkProxy
|
QFileDialog, QNetworkProxy)
|
||||||
|
|
||||||
from calibre import USER_AGENT, get_proxies, get_download_filename
|
from calibre import USER_AGENT, get_proxies, get_download_filename
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
@ -35,13 +35,13 @@ class NPWebView(QWebView):
|
|||||||
proxy.setPassword(proxy_parts.password)
|
proxy.setPassword(proxy_parts.password)
|
||||||
proxy.setHostName(proxy_parts.hostname)
|
proxy.setHostName(proxy_parts.hostname)
|
||||||
proxy.setPort(proxy_parts.port)
|
proxy.setPort(proxy_parts.port)
|
||||||
self.page().networkAccessManager().setProxy(proxy)
|
self.page().networkAccessManager().setProxy(proxy)
|
||||||
|
|
||||||
self.page().setForwardUnsupportedContent(True)
|
self.page().setForwardUnsupportedContent(True)
|
||||||
self.page().unsupportedContent.connect(self.start_download)
|
self.page().unsupportedContent.connect(self.start_download)
|
||||||
self.page().downloadRequested.connect(self.start_download)
|
self.page().downloadRequested.connect(self.start_download)
|
||||||
self.page().networkAccessManager().sslErrors.connect(self.ignore_ssl_errors)
|
self.page().networkAccessManager().sslErrors.connect(self.ignore_ssl_errors)
|
||||||
|
|
||||||
def createWindow(self, type):
|
def createWindow(self, type):
|
||||||
if type == QWebPage.WebBrowserWindow:
|
if type == QWebPage.WebBrowserWindow:
|
||||||
return self
|
return self
|
||||||
@ -50,17 +50,17 @@ class NPWebView(QWebView):
|
|||||||
|
|
||||||
def set_gui(self, gui):
|
def set_gui(self, gui):
|
||||||
self.gui = gui
|
self.gui = gui
|
||||||
|
|
||||||
def set_tags(self, tags):
|
def set_tags(self, tags):
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
|
|
||||||
def start_download(self, request):
|
def start_download(self, request):
|
||||||
if not self.gui:
|
if not self.gui:
|
||||||
return
|
return
|
||||||
|
|
||||||
url = unicode(request.url().toString())
|
url = unicode(request.url().toString())
|
||||||
cf = self.get_cookies()
|
cf = self.get_cookies()
|
||||||
|
|
||||||
filename = get_download_filename(url, cf)
|
filename = get_download_filename(url, cf)
|
||||||
ext = os.path.splitext(filename)[1][1:].lower()
|
ext = os.path.splitext(filename)[1][1:].lower()
|
||||||
if ext not in BOOK_EXTENSIONS:
|
if ext not in BOOK_EXTENSIONS:
|
||||||
@ -76,21 +76,21 @@ class NPWebView(QWebView):
|
|||||||
|
|
||||||
def ignore_ssl_errors(self, reply, errors):
|
def ignore_ssl_errors(self, reply, errors):
|
||||||
reply.ignoreSslErrors(errors)
|
reply.ignoreSslErrors(errors)
|
||||||
|
|
||||||
def get_cookies(self):
|
def get_cookies(self):
|
||||||
'''
|
'''
|
||||||
Writes QNetworkCookies to Mozilla cookie .txt file.
|
Writes QNetworkCookies to Mozilla cookie .txt file.
|
||||||
|
|
||||||
:return: The file path to the cookie file.
|
:return: The file path to the cookie file.
|
||||||
'''
|
'''
|
||||||
cf = PersistentTemporaryFile(suffix='.txt')
|
cf = PersistentTemporaryFile(suffix='.txt')
|
||||||
|
|
||||||
cf.write('# Netscape HTTP Cookie File\n\n')
|
cf.write('# Netscape HTTP Cookie File\n\n')
|
||||||
|
|
||||||
for c in self.page().networkAccessManager().cookieJar().allCookies():
|
for c in self.page().networkAccessManager().cookieJar().allCookies():
|
||||||
cookie = []
|
cookie = []
|
||||||
domain = unicode(c.domain())
|
domain = unicode(c.domain())
|
||||||
|
|
||||||
cookie.append(domain)
|
cookie.append(domain)
|
||||||
cookie.append('TRUE' if domain.startswith('.') else 'FALSE')
|
cookie.append('TRUE' if domain.startswith('.') else 'FALSE')
|
||||||
cookie.append(unicode(c.path()))
|
cookie.append(unicode(c.path()))
|
||||||
@ -98,15 +98,15 @@ class NPWebView(QWebView):
|
|||||||
cookie.append(unicode(c.expirationDate().toTime_t()))
|
cookie.append(unicode(c.expirationDate().toTime_t()))
|
||||||
cookie.append(unicode(c.name()))
|
cookie.append(unicode(c.name()))
|
||||||
cookie.append(unicode(c.value()))
|
cookie.append(unicode(c.value()))
|
||||||
|
|
||||||
cf.write('\t'.join(cookie))
|
cf.write('\t'.join(cookie))
|
||||||
cf.write('\n')
|
cf.write('\n')
|
||||||
|
|
||||||
cf.close()
|
cf.close()
|
||||||
return cf.name
|
return cf.name
|
||||||
|
|
||||||
|
|
||||||
class NPWebPage(QWebPage):
|
class NPWebPage(QWebPage):
|
||||||
|
|
||||||
def userAgentForUrl(self, url):
|
def userAgentForUrl(self, url):
|
||||||
return USER_AGENT
|
return USER_AGENT
|
||||||
|
Loading…
x
Reference in New Issue
Block a user