Merge from trunk
50
COPYRIGHT
@ -79,13 +79,6 @@ License: GPL2+
|
|||||||
The full text of the GPL is distributed as in
|
The full text of the GPL is distributed as in
|
||||||
/usr/share/common-licenses/GPL-2 on Debian systems.
|
/usr/share/common-licenses/GPL-2 on Debian systems.
|
||||||
|
|
||||||
Files: src/pyPdf/*
|
|
||||||
Copyright: Copyright (c) 2006, Mathieu Fenniak
|
|
||||||
Copyright: Copyright (c) 2007, Ashish Kulkarni <kulkarni.ashish@gmail.com>
|
|
||||||
License: BSD
|
|
||||||
The full text of the BSD license is distributed as in
|
|
||||||
/usr/share/common-licenses/BSD on Debian systems.
|
|
||||||
|
|
||||||
Files: src/calibre/utils/lzx/*
|
Files: src/calibre/utils/lzx/*
|
||||||
Copyright: Copyright (C) 2002, Matthew T. Russotto
|
Copyright: Copyright (C) 2002, Matthew T. Russotto
|
||||||
Copyright: Copyright (C) 2008, Marshall T. Vandegrift <llasram@gmail.com>
|
Copyright: Copyright (C) 2008, Marshall T. Vandegrift <llasram@gmail.com>
|
||||||
@ -100,49 +93,6 @@ License: BSD
|
|||||||
The full text of the BSD license is distributed as in
|
The full text of the BSD license is distributed as in
|
||||||
/usr/share/common-licenses/BSD on Debian systems.
|
/usr/share/common-licenses/BSD on Debian systems.
|
||||||
|
|
||||||
Files: src/calibre/utils/pyparsing.py
|
|
||||||
Copyright: Copyright (c) 2003-2008, Paul T. McGuire
|
|
||||||
License: MIT
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
Files: src/calibre/utils/PythonMagickWand.py
|
|
||||||
Copyright: (c) 2007 - Achim Domma - domma@procoders.net
|
|
||||||
License: MIT
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
||||||
Files: src/calibre/utils/msdes/d3des.h:
|
Files: src/calibre/utils/msdes/d3des.h:
|
||||||
Files: src/calibre/utils/msdes/des.c:
|
Files: src/calibre/utils/msdes/des.c:
|
||||||
Copyright: Copyright (C) 1988,1989,1990,1991,1992, Richard Outerbridge
|
Copyright: Copyright (C) 1988,1989,1990,1991,1992, Richard Outerbridge
|
||||||
|
@ -426,6 +426,8 @@ Identifiers (e.g., isbn, doi, lccn etc) also use an extended syntax. First, note
|
|||||||
|
|
||||||
:guilabel:`Advanced Search Dialog`
|
:guilabel:`Advanced Search Dialog`
|
||||||
|
|
||||||
|
.. _saved_searches:
|
||||||
|
|
||||||
Saving searches
|
Saving searches
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@ -435,6 +437,15 @@ Now you can access your saved search in the Tag Browser under "Searches". A sing
|
|||||||
|
|
||||||
.. _config_filename_metadata:
|
.. _config_filename_metadata:
|
||||||
|
|
||||||
|
Virtual Libraries
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
A :guilabel:`Virtual Library` is a way to pretend that your |app| library has
|
||||||
|
only a few books instead of its full collection. This is an excellent way to
|
||||||
|
partition your large collection of books into smaller, manageable chunks. To
|
||||||
|
learn how to create and use virtual libraries, see the tutorial:
|
||||||
|
:ref:`virtual_libraries`.
|
||||||
|
|
||||||
Guessing metadata from file names
|
Guessing metadata from file names
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
In the :guilabel:`Add/Save` section of the configuration dialog, you can specify a regular expression that |app| will use to try and guess metadata from the names of ebook files
|
In the :guilabel:`Add/Save` section of the configuration dialog, you can specify a regular expression that |app| will use to try and guess metadata from the names of ebook files
|
||||||
|
BIN
manual/images/virtual_library_button.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
manual/images/vl_by_author.png
Normal file
After Width: | Height: | Size: 65 KiB |
@ -20,4 +20,5 @@ Here you will find tutorials to get you started using |app|'s more advanced feat
|
|||||||
creating_plugins
|
creating_plugins
|
||||||
typesetting_math
|
typesetting_math
|
||||||
catalogs
|
catalogs
|
||||||
|
virtual_libraries
|
||||||
|
|
||||||
|
88
manual/virtual_libraries.rst
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
|
||||||
|
.. include:: global.rst
|
||||||
|
|
||||||
|
.. _virtual_libraries:
|
||||||
|
|
||||||
|
|
||||||
|
Virtual Libraries
|
||||||
|
============================
|
||||||
|
|
||||||
|
In |app|, a virtual library is a way to tell |app| to open only a subset of a
|
||||||
|
normal library. For example, if you want to only work with books by a certain
|
||||||
|
author, or books having only a certain tag. Using virtual libraries is the
|
||||||
|
preferred way of partitioning your large book collection into smaller sub
|
||||||
|
collections. It is superior to splitting up your library into multiple smaller
|
||||||
|
libraries as, when you want to search through your entire collection, you can
|
||||||
|
simply go back to the full library. There is no way to search through multiple
|
||||||
|
separate libraries simultaneously in |app|.
|
||||||
|
|
||||||
|
A virtual library is different to a simple search. A search will only restrict
|
||||||
|
the list of books shown in the book list. A virtual library does that an in
|
||||||
|
addition, it also restricts the entries shown in the :guilabel:`Tag Browser` to
|
||||||
|
the left. The Tag Browser will only show tags, authors, series, publishers, etc.
|
||||||
|
that come from the books in the virtual library. A virtual library thus behaves
|
||||||
|
as though the actual library contains only the restricted set of books.
|
||||||
|
|
||||||
|
Creating Virtual Libraries
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. |vlb| image:: images/virtual_library_button.png
|
||||||
|
:class: float-left-img
|
||||||
|
|
||||||
|
|vlb| To use a virtual library click the :guilabel:`Virtual Library` button located
|
||||||
|
to the left of the search bar and select the :guilabel:`Create Virtual Library`
|
||||||
|
option. As a first example, let's create a virtual library that shows us only
|
||||||
|
the books by a particular author. Click the :guilabel:`Authors` link as shown
|
||||||
|
in the image below and choose the author you want to use and click OK.
|
||||||
|
|
||||||
|
.. image:: images/vl_by_author.png
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
The Create Virtual Library dialog has been filled in for you. Click OK and you
|
||||||
|
will see that a new Virtual Library has been created, and automatically
|
||||||
|
switched to, that displays only the books by the selected author. As far as
|
||||||
|
|app| is concerned, it is as if your library contains only the books by the
|
||||||
|
selected author.
|
||||||
|
|
||||||
|
You can switch back to the full library at any time by once again clicking the
|
||||||
|
:guilabel:`Virtual Library` and selecting the entry named :guilabel:`<None>`.
|
||||||
|
|
||||||
|
Virtual Libraries are based on *searches*. You can use any search as the basis
|
||||||
|
of a virtual library. The virtual library will contain only the books matched
|
||||||
|
by that search. First, type in the search you want to use in the search bar,
|
||||||
|
when you are happy with the returned results, click the Virtual Library button,
|
||||||
|
choose Create Library and enter a name for the new virtual library. The virtual
|
||||||
|
library will then be created based on the search you just typed in. Searches
|
||||||
|
are very powerful, for examples of the kinds of things you can do with them,
|
||||||
|
see :ref:`search_interface`.
|
||||||
|
|
||||||
|
Working with Virtual Libraries
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
You can edit a previously created virtual library or remove it, by clicking the
|
||||||
|
:guilabel:`Virtual Library` and choosing the appropriate action.
|
||||||
|
|
||||||
|
You can tell |app| that you always want to apply a particular virtual library
|
||||||
|
when the current library is opened, by going to
|
||||||
|
:guilabel:`Preferences->Behavior`.
|
||||||
|
|
||||||
|
If you use the |app| Content Server, you can have it share a virtual library
|
||||||
|
instead of the full library by going to :guilabel:`Preferences->Sharing over the net`.
|
||||||
|
|
||||||
|
You can quickly use the current search as a temporary virtual library by
|
||||||
|
clicking the :guilabel:`Virtual Library` button and choosing the
|
||||||
|
:guilabel:`*current search` entry.
|
||||||
|
|
||||||
|
Using additional restrictions
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
You can further restrict the books shown in a Virtual Library by using
|
||||||
|
:guilabel:`Additional restrictions`. An additional restriction is saved search
|
||||||
|
you previously created that can be applied to the current Virtual Library to
|
||||||
|
further restrict the books shown in a virtual library. For example, say you
|
||||||
|
have a Virtual Library for books tagged as :guilabel:`Historical Fiction` and a
|
||||||
|
saved search that shows you unread books, you can click the :guilabel:`Virtual
|
||||||
|
Library` button and choose the :guilabel:`Additional restriction` option to
|
||||||
|
show only unread Historical Fiction books. To learn about saved searches, see
|
||||||
|
:ref:`saved_searches`.
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2010-2012, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2010-2013, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
www.ft.com/uk-edition
|
www.ft.com/intl/uk-edition
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
@ -29,7 +29,7 @@ class FinancialTimes(BasicNewsRecipe):
|
|||||||
masthead_url = 'http://im.media.ft.com/m/img/masthead_main.jpg'
|
masthead_url = 'http://im.media.ft.com/m/img/masthead_main.jpg'
|
||||||
LOGIN = 'https://registration.ft.com/registration/barrier/login'
|
LOGIN = 'https://registration.ft.com/registration/barrier/login'
|
||||||
LOGIN2 = 'http://media.ft.com/h/subs3.html'
|
LOGIN2 = 'http://media.ft.com/h/subs3.html'
|
||||||
INDEX = 'http://www.ft.com/uk-edition'
|
INDEX = 'http://www.ft.com/intl/uk-edition'
|
||||||
PREFIX = 'http://www.ft.com'
|
PREFIX = 'http://www.ft.com'
|
||||||
|
|
||||||
conversion_options = {
|
conversion_options = {
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2013, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2010-2013, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
http://www.ft.com/intl/us-edition
|
www.ft.com/intl/international-edition
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre import strftime
|
from calibre import strftime
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
class FinancialTimes(BasicNewsRecipe):
|
class FinancialTimes(BasicNewsRecipe):
|
||||||
title = 'Financial Times (US) printed edition'
|
title = 'Financial Times (International) printed edition'
|
||||||
__author__ = 'Darko Miletic'
|
__author__ = 'Darko Miletic'
|
||||||
description = "The Financial Times (FT) is one of the world's leading business news and information organisations, recognised internationally for its authority, integrity and accuracy."
|
description = "The Financial Times (FT) is one of the world's leading business news and information organisations, recognised internationally for its authority, integrity and accuracy."
|
||||||
publisher = 'The Financial Times Ltd.'
|
publisher = 'The Financial Times Ltd.'
|
||||||
category = 'news, finances, politics, UK, World'
|
category = 'news, finances, politics, World'
|
||||||
oldest_article = 2
|
oldest_article = 2
|
||||||
language = 'en'
|
language = 'en'
|
||||||
max_articles_per_feed = 250
|
max_articles_per_feed = 250
|
||||||
@ -28,7 +29,7 @@ class FinancialTimes(BasicNewsRecipe):
|
|||||||
masthead_url = 'http://im.media.ft.com/m/img/masthead_main.jpg'
|
masthead_url = 'http://im.media.ft.com/m/img/masthead_main.jpg'
|
||||||
LOGIN = 'https://registration.ft.com/registration/barrier/login'
|
LOGIN = 'https://registration.ft.com/registration/barrier/login'
|
||||||
LOGIN2 = 'http://media.ft.com/h/subs3.html'
|
LOGIN2 = 'http://media.ft.com/h/subs3.html'
|
||||||
INDEX = 'http://www.ft.com/intl/us-edition'
|
INDEX = 'http://www.ft.com/intl/international-edition'
|
||||||
PREFIX = 'http://www.ft.com'
|
PREFIX = 'http://www.ft.com'
|
||||||
|
|
||||||
conversion_options = {
|
conversion_options = {
|
||||||
@ -105,29 +106,30 @@ class FinancialTimes(BasicNewsRecipe):
|
|||||||
return articles
|
return articles
|
||||||
|
|
||||||
def parse_index(self):
|
def parse_index(self):
|
||||||
feeds = []
|
feeds = OrderedDict()
|
||||||
soup = self.index_to_soup(self.INDEX)
|
soup = self.index_to_soup(self.INDEX)
|
||||||
dates= self.tag_to_string(soup.find('div', attrs={'class':'btm-links'}).find('div'))
|
#dates= self.tag_to_string(soup.find('div', attrs={'class':'btm-links'}).find('div'))
|
||||||
self.timefmt = ' [%s]'%dates
|
#self.timefmt = ' [%s]'%dates
|
||||||
wide = soup.find('div',attrs={'class':'wide'})
|
section_title = 'Untitled'
|
||||||
if not wide:
|
|
||||||
return feeds
|
for column in soup.findAll('div', attrs = {'class':'feedBoxes clearfix'}):
|
||||||
allsections = wide.findAll(attrs={'class':lambda x: x and 'footwell' in x.split()})
|
for section in column. findAll('div', attrs = {'class':'feedBox'}):
|
||||||
if not allsections:
|
sectiontitle=self.tag_to_string(section.find('h4'))
|
||||||
return feeds
|
if '...' not in sectiontitle: section_title=sectiontitle
|
||||||
count = 0
|
for article in section.ul.findAll('li'):
|
||||||
for item in allsections:
|
articles = []
|
||||||
count = count + 1
|
title=self.tag_to_string(article.a)
|
||||||
if self.test and count > 2:
|
url=article.a['href']
|
||||||
return feeds
|
articles.append({'title':title, 'url':url, 'description':'', 'date':''})
|
||||||
fitem = item.h3
|
|
||||||
if not fitem:
|
if articles:
|
||||||
fitem = item.h4
|
if section_title not in feeds:
|
||||||
ftitle = self.tag_to_string(fitem)
|
feeds[section_title] = []
|
||||||
self.report_progress(0, _('Fetching feed')+' %s...'%(ftitle))
|
feeds[section_title] += articles
|
||||||
feedarts = self.get_artlinks(item.ul)
|
|
||||||
feeds.append((ftitle,feedarts))
|
|
||||||
return feeds
|
ans = [(key, val) for key, val in feeds.iteritems()]
|
||||||
|
return ans
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
items = ['promo-box','promo-title',
|
items = ['promo-box','promo-title',
|
||||||
@ -177,6 +179,3 @@ class FinancialTimes(BasicNewsRecipe):
|
|||||||
tfile.close()
|
tfile.close()
|
||||||
self.temp_files.append(tfile)
|
self.temp_files.append(tfile)
|
||||||
return tfile.name
|
return tfile.name
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
self.browser.open('https://registration.ft.com/registration/login/logout?location=')
|
|
@ -4,7 +4,7 @@ class AdvancedUserRecipe1366025923(BasicNewsRecipe):
|
|||||||
title = u'Lightspeed Magazine'
|
title = u'Lightspeed Magazine'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
__author__ = 'Jose Pinto'
|
__author__ = 'Jose Pinto'
|
||||||
oldest_article = 7
|
oldest_article = 31
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
auto_cleanup = True
|
auto_cleanup = True
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
|
@ -36,6 +36,9 @@ from BeautifulSoup import BeautifulSoup
|
|||||||
Changed order of regex to speedup proces
|
Changed order of regex to speedup proces
|
||||||
Version 1.9.3 23-05-2012
|
Version 1.9.3 23-05-2012
|
||||||
Updated Cover image
|
Updated Cover image
|
||||||
|
Version 1.9.4 19-04-2013
|
||||||
|
Added regex filter for mailto
|
||||||
|
Updated for new layout of metro-site
|
||||||
'''
|
'''
|
||||||
|
|
||||||
class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
||||||
@ -43,7 +46,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
|||||||
oldest_article = 1.2
|
oldest_article = 1.2
|
||||||
max_articles_per_feed = 25
|
max_articles_per_feed = 25
|
||||||
__author__ = u'DrMerry'
|
__author__ = u'DrMerry'
|
||||||
description = u'Metro Nederland'
|
description = u'Metro Nederland v1.9.4 2013-04-19'
|
||||||
language = u'nl'
|
language = u'nl'
|
||||||
simultaneous_downloads = 5
|
simultaneous_downloads = 5
|
||||||
masthead_url = 'http://blog.metronieuws.nl/wp-content/themes/metro/images/header.gif'
|
masthead_url = 'http://blog.metronieuws.nl/wp-content/themes/metro/images/header.gif'
|
||||||
@ -68,13 +71,17 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
|||||||
#(re.compile('(</?)h2', re.DOTALL|re.IGNORECASE),lambda match:'\1em')
|
#(re.compile('(</?)h2', re.DOTALL|re.IGNORECASE),lambda match:'\1em')
|
||||||
]
|
]
|
||||||
|
|
||||||
remove_tags_before= dict(id='date')
|
remove_tags_before= dict(id='subwrapper')
|
||||||
remove_tags_after = [dict(name='div', attrs={'class':['column-1-3','gallery-text']})]#id='share-and-byline')]
|
remove_tags_after = dict(name='div', attrs={'class':['body-area','article-main-area']})
|
||||||
|
#name='div', attrs={'class':['subwrapper']})]
|
||||||
|
#'column-1-3','gallery-text']})]#id='share-and-byline')]
|
||||||
|
|
||||||
|
filter_regexps = [r'mailto:.*']
|
||||||
|
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name=['iframe','script','noscript','style']),
|
dict(name=['iframe','script','noscript','style']),
|
||||||
dict(name='div', attrs={'class':['column-4-5','column-1-5','ad-msg','col-179 ','col-373 ','clear','ad','navigation',re.compile('share-tools(-top)?'),'tools','metroCommentFormWrap','article-tools-below-title','related-links','padding-top-15',re.compile('^promo.*?$'),'teaser-component',re.compile('fb(-comments|_iframe_widget)'),'promos','header-links','promo-2']}),
|
dict(name='div', attrs={'class':['aside clearfix','aside clearfix middle-col-line','comments','share-tools','article-right-column','column-4-5','column-1-5','ad-msg','col-179 ','col-373 ','clear','ad','navigation',re.compile('share-tools(-top)?'),'tools','metroCommentFormWrap','article-tools-below-title','related-links','padding-top-15',re.compile('^promo.*?$'),'teaser-component',re.compile('fb(-comments|_iframe_widget)'),'promos','header-links','promo-2']}),
|
||||||
dict(id=['column-1-5-bottom','column-4-5',re.compile('^ad(\d+|adcomp.*?)?$'),'adadcomp-4','margin-5','sidebar',re.compile('^article-\d'),'comments','gallery-1']),
|
dict(id=['article-2','googleads','column-1-5-bottom','column-4-5',re.compile('^ad(\d+|adcomp.*?)?$'),'adadcomp-4','margin-5','sidebar',re.compile('^article-\d'),'comments','gallery-1','sharez_container','ts-container','topshares','ts-title']),
|
||||||
dict(name='a', attrs={'name':'comments'}),
|
dict(name='a', attrs={'name':'comments'}),
|
||||||
#dict(name='div', attrs={'data-href'}),
|
#dict(name='div', attrs={'data-href'}),
|
||||||
dict(name='img', attrs={'class':'top-line','title':'volledig scherm'}),
|
dict(name='img', attrs={'class':'top-line','title':'volledig scherm'}),
|
||||||
|
27
recipes/voice_of_america.recipe
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class HindustanTimes(BasicNewsRecipe):
|
||||||
|
title = u'Voice of America'
|
||||||
|
language = 'en'
|
||||||
|
__author__ = 'Krittika Goyal'
|
||||||
|
oldest_article = 15 #days
|
||||||
|
max_articles_per_feed = 25
|
||||||
|
#encoding = 'cp1252'
|
||||||
|
use_embedded_content = False
|
||||||
|
|
||||||
|
no_stylesheets = True
|
||||||
|
auto_cleanup = True
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
('All Zones',
|
||||||
|
'http://learningenglish.voanews.com/rss/?count=20'),
|
||||||
|
('World',
|
||||||
|
'http://learningenglish.voanews.com/rss/?count=20&zoneid=957'),
|
||||||
|
('USA',
|
||||||
|
'http://learningenglish.voanews.com/rss/?count=20&zoneid=958'),
|
||||||
|
('Health',
|
||||||
|
'http://learningenglish.voanews.com/rss/?count=20&zoneid=955'),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 397 KiB |
@ -38,7 +38,7 @@ class Check(Command):
|
|||||||
if cache.get(y, 0) == mtime:
|
if cache.get(y, 0) == mtime:
|
||||||
continue
|
continue
|
||||||
if (f.endswith('.py') and f not in (
|
if (f.endswith('.py') and f not in (
|
||||||
'feedparser.py', 'pyparsing.py', 'markdown.py') and
|
'feedparser.py', 'markdown.py') and
|
||||||
'prs500/driver.py' not in y):
|
'prs500/driver.py' not in y):
|
||||||
yield y, mtime
|
yield y, mtime
|
||||||
if f.endswith('.coffee'):
|
if f.endswith('.coffee'):
|
||||||
|
@ -48,7 +48,7 @@ binary_includes = [
|
|||||||
'/usr/lib/libpng14.so.14',
|
'/usr/lib/libpng14.so.14',
|
||||||
'/usr/lib/libexslt.so.0',
|
'/usr/lib/libexslt.so.0',
|
||||||
# Ensure that libimobiledevice is compiled against openssl, not gnutls
|
# Ensure that libimobiledevice is compiled against openssl, not gnutls
|
||||||
'/usr/lib/libimobiledevice.so.3',
|
'/usr/lib/libimobiledevice.so.4',
|
||||||
'/usr/lib/libusbmuxd.so.2',
|
'/usr/lib/libusbmuxd.so.2',
|
||||||
'/usr/lib/libplist.so.1',
|
'/usr/lib/libplist.so.1',
|
||||||
MAGICK_PREFIX+'/lib/libMagickWand.so.5',
|
MAGICK_PREFIX+'/lib/libMagickWand.so.5',
|
||||||
@ -112,7 +112,6 @@ class LinuxFreeze(Command):
|
|||||||
else:
|
else:
|
||||||
ffi = glob.glob('/usr/lib/libffi.so.?')[-1]
|
ffi = glob.glob('/usr/lib/libffi.so.?')[-1]
|
||||||
|
|
||||||
|
|
||||||
for x in binary_includes + [stdcpp, ffi]:
|
for x in binary_includes + [stdcpp, ffi]:
|
||||||
dest = self.bin_dir if '/bin/' in x else self.lib_dir
|
dest = self.bin_dir if '/bin/' in x else self.lib_dir
|
||||||
shutil.copy2(x, dest)
|
shutil.copy2(x, dest)
|
||||||
@ -226,7 +225,6 @@ class LinuxFreeze(Command):
|
|||||||
except:
|
except:
|
||||||
self.warn('Failed to byte-compile', y)
|
self.warn('Failed to byte-compile', y)
|
||||||
|
|
||||||
|
|
||||||
def run_builder(self, cmd, verbose=True):
|
def run_builder(self, cmd, verbose=True):
|
||||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
@ -256,7 +254,6 @@ class LinuxFreeze(Command):
|
|||||||
self.info('Archive %s created: %.2f MB'%(dist,
|
self.info('Archive %s created: %.2f MB'%(dist,
|
||||||
os.stat(dist).st_size/(1024.**2)))
|
os.stat(dist).st_size/(1024.**2)))
|
||||||
|
|
||||||
|
|
||||||
def build_launchers(self):
|
def build_launchers(self):
|
||||||
self.obj_dir = self.j(self.src_root, 'build', 'launcher')
|
self.obj_dir = self.j(self.src_root, 'build', 'launcher')
|
||||||
if not os.path.exists(self.obj_dir):
|
if not os.path.exists(self.obj_dir):
|
||||||
@ -268,7 +265,8 @@ class LinuxFreeze(Command):
|
|||||||
cflags = '-fno-strict-aliasing -W -Wall -c -O2 -pipe -DPYTHON_VER="python%s"'%self.py_ver
|
cflags = '-fno-strict-aliasing -W -Wall -c -O2 -pipe -DPYTHON_VER="python%s"'%self.py_ver
|
||||||
cflags = cflags.split() + ['-I/usr/include/python'+self.py_ver]
|
cflags = cflags.split() + ['-I/usr/include/python'+self.py_ver]
|
||||||
for src, obj in zip(sources, objects):
|
for src, obj in zip(sources, objects):
|
||||||
if not self.newer(obj, headers+[src, __file__]): continue
|
if not self.newer(obj, headers+[src, __file__]):
|
||||||
|
continue
|
||||||
cmd = ['gcc'] + cflags + ['-fPIC', '-o', obj, src]
|
cmd = ['gcc'] + cflags + ['-fPIC', '-o', obj, src]
|
||||||
self.run_builder(cmd)
|
self.run_builder(cmd)
|
||||||
|
|
||||||
@ -330,7 +328,6 @@ class LinuxFreeze(Command):
|
|||||||
|
|
||||||
self.run_builder(cmd, verbose=False)
|
self.run_builder(cmd, verbose=False)
|
||||||
|
|
||||||
|
|
||||||
def create_site_py(self): # {{{
|
def create_site_py(self): # {{{
|
||||||
with open(self.j(self.py_dir, 'site.py'), 'wb') as f:
|
with open(self.j(self.py_dir, 'site.py'), 'wb') as f:
|
||||||
f.write(textwrap.dedent('''\
|
f.write(textwrap.dedent('''\
|
||||||
|
@ -37,7 +37,6 @@ class OSX32_Freeze(Command):
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='Only build launchers')
|
help='Only build launchers')
|
||||||
|
|
||||||
|
|
||||||
def run(self, opts):
|
def run(self, opts):
|
||||||
global info, warn
|
global info, warn
|
||||||
info, warn = self.info, self.warn
|
info, warn = self.info, self.warn
|
||||||
@ -332,7 +331,7 @@ class Py2App(object):
|
|||||||
def create_plist(self):
|
def create_plist(self):
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
env = dict(**ENV)
|
env = dict(**ENV)
|
||||||
env['CALIBRE_LAUNCHED_FROM_BUNDLE']='1';
|
env['CALIBRE_LAUNCHED_FROM_BUNDLE']='1'
|
||||||
docs = [{'CFBundleTypeName':'E-book',
|
docs = [{'CFBundleTypeName':'E-book',
|
||||||
'CFBundleTypeExtensions':list(BOOK_EXTENSIONS),
|
'CFBundleTypeExtensions':list(BOOK_EXTENSIONS),
|
||||||
'CFBundleTypeRole':'Viewer',
|
'CFBundleTypeRole':'Viewer',
|
||||||
@ -395,12 +394,11 @@ class Py2App(object):
|
|||||||
self.install_dylib(os.path.join(SW, 'lib', 'libpng12.0.dylib'))
|
self.install_dylib(os.path.join(SW, 'lib', 'libpng12.0.dylib'))
|
||||||
self.install_dylib(os.path.join(SW, 'lib', 'libpng.3.dylib'))
|
self.install_dylib(os.path.join(SW, 'lib', 'libpng.3.dylib'))
|
||||||
|
|
||||||
|
|
||||||
@flush
|
@flush
|
||||||
def add_fontconfig(self):
|
def add_fontconfig(self):
|
||||||
info('\nAdding fontconfig')
|
info('\nAdding fontconfig')
|
||||||
for x in ('fontconfig.1', 'freetype.6', 'expat.1',
|
for x in ('fontconfig.1', 'freetype.6', 'expat.1',
|
||||||
'plist.1', 'usbmuxd.2', 'imobiledevice.3'):
|
'plist.1', 'usbmuxd.2', 'imobiledevice.4'):
|
||||||
src = os.path.join(SW, 'lib', 'lib'+x+'.dylib')
|
src = os.path.join(SW, 'lib', 'lib'+x+'.dylib')
|
||||||
self.install_dylib(src)
|
self.install_dylib(src)
|
||||||
dst = os.path.join(self.resources_dir, 'fonts')
|
dst = os.path.join(self.resources_dir, 'fonts')
|
||||||
@ -607,7 +605,6 @@ class Py2App(object):
|
|||||||
shutil.copy2(join(base, 'site.py'), join(self.resources_dir, 'Python',
|
shutil.copy2(join(base, 'site.py'), join(self.resources_dir, 'Python',
|
||||||
'lib', 'python'+self.version_info))
|
'lib', 'python'+self.version_info))
|
||||||
|
|
||||||
|
|
||||||
@flush
|
@flush
|
||||||
def makedmg(self, d, volname,
|
def makedmg(self, d, volname,
|
||||||
destdir='dist',
|
destdir='dist',
|
||||||
|
@ -10253,7 +10253,7 @@ msgstr ""
|
|||||||
|
|
||||||
#. name for inh
|
#. name for inh
|
||||||
msgid "Ingush"
|
msgid "Ingush"
|
||||||
msgstr "Engelsk"
|
msgstr "Ingush"
|
||||||
|
|
||||||
#. name for inj
|
#. name for inj
|
||||||
msgid "Inga; Jungle"
|
msgid "Inga; Jungle"
|
||||||
|
@ -1448,7 +1448,6 @@ class StoreGoogleBooksStore(StoreBase):
|
|||||||
|
|
||||||
headquarters = 'US'
|
headquarters = 'US'
|
||||||
formats = ['EPUB', 'PDF', 'TXT']
|
formats = ['EPUB', 'PDF', 'TXT']
|
||||||
affiliate = True
|
|
||||||
|
|
||||||
class StoreGutenbergStore(StoreBase):
|
class StoreGutenbergStore(StoreBase):
|
||||||
name = 'Project Gutenberg'
|
name = 'Project Gutenberg'
|
||||||
|
@ -114,6 +114,19 @@ class Cache(object):
|
|||||||
if self.dirtied_cache:
|
if self.dirtied_cache:
|
||||||
self.dirtied_sequence = max(self.dirtied_cache.itervalues())+1
|
self.dirtied_sequence = max(self.dirtied_cache.itervalues())+1
|
||||||
|
|
||||||
|
@write_api
|
||||||
|
def initialize_template_cache(self):
|
||||||
|
self.formatter_template_cache = {}
|
||||||
|
|
||||||
|
@write_api
|
||||||
|
def refresh(self):
|
||||||
|
self._initialize_template_cache()
|
||||||
|
for field in self.fields.itervalues():
|
||||||
|
if hasattr(field, 'clear_cache'):
|
||||||
|
field.clear_cache() # Clear the composite cache
|
||||||
|
if hasattr(field, 'table'):
|
||||||
|
field.table.read(self.backend) # Reread data from metadata.db
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def field_metadata(self):
|
def field_metadata(self):
|
||||||
return self.backend.field_metadata
|
return self.backend.field_metadata
|
||||||
|
@ -6,12 +6,13 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import os
|
import os, traceback
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from calibre.db.backend import DB
|
from calibre.db.backend import DB
|
||||||
from calibre.db.cache import Cache
|
from calibre.db.cache import Cache
|
||||||
from calibre.db.view import View
|
from calibre.db.view import View
|
||||||
|
from calibre.utils.date import utcnow
|
||||||
|
|
||||||
class LibraryDatabase(object):
|
class LibraryDatabase(object):
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ class LibraryDatabase(object):
|
|||||||
progress_callback=lambda x, y:True, restore_all_prefs=False):
|
progress_callback=lambda x, y:True, restore_all_prefs=False):
|
||||||
|
|
||||||
self.is_second_db = is_second_db # TODO: Use is_second_db
|
self.is_second_db = is_second_db # TODO: Use is_second_db
|
||||||
|
self.listeners = set([])
|
||||||
|
|
||||||
backend = self.backend = DB(library_path, default_prefs=default_prefs,
|
backend = self.backend = DB(library_path, default_prefs=default_prefs,
|
||||||
read_only=read_only, restore_all_prefs=restore_all_prefs,
|
read_only=read_only, restore_all_prefs=restore_all_prefs,
|
||||||
@ -50,6 +52,8 @@ class LibraryDatabase(object):
|
|||||||
setattr(self, prop, partial(self.get_property,
|
setattr(self, prop, partial(self.get_property,
|
||||||
loc=self.FIELD_MAP[fm]))
|
loc=self.FIELD_MAP[fm]))
|
||||||
|
|
||||||
|
self.last_update_check = self.last_modified()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.backend.close()
|
self.backend.close()
|
||||||
|
|
||||||
@ -71,9 +75,22 @@ class LibraryDatabase(object):
|
|||||||
def library_id(self):
|
def library_id(self):
|
||||||
return self.backend.library_id
|
return self.backend.library_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def library_path(self):
|
||||||
|
return self.backend.library_path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dbpath(self):
|
||||||
|
return self.backend.dbpath
|
||||||
|
|
||||||
def last_modified(self):
|
def last_modified(self):
|
||||||
return self.backend.last_modified()
|
return self.backend.last_modified()
|
||||||
|
|
||||||
|
def check_if_modified(self):
|
||||||
|
if self.last_modified() > self.last_update_check:
|
||||||
|
self.refresh()
|
||||||
|
self.last_update_check = utcnow()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def custom_column_num_map(self):
|
def custom_column_num_map(self):
|
||||||
return self.backend.custom_column_num_map
|
return self.backend.custom_column_num_map
|
||||||
@ -86,9 +103,48 @@ class LibraryDatabase(object):
|
|||||||
def FIELD_MAP(self):
|
def FIELD_MAP(self):
|
||||||
return self.backend.FIELD_MAP
|
return self.backend.FIELD_MAP
|
||||||
|
|
||||||
|
@property
|
||||||
|
def formatter_template_cache(self):
|
||||||
|
return self.data.cache.formatter_template_cache
|
||||||
|
|
||||||
|
def initialize_template_cache(self):
|
||||||
|
self.data.cache.initialize_template_cache()
|
||||||
|
|
||||||
def all_ids(self):
|
def all_ids(self):
|
||||||
for book_id in self.data.cache.all_book_ids():
|
for book_id in self.data.cache.all_book_ids():
|
||||||
yield book_id
|
yield book_id
|
||||||
|
|
||||||
|
def refresh(self, field=None, ascending=True):
|
||||||
|
self.data.cache.refresh()
|
||||||
|
self.data.refresh(field=field, ascending=ascending)
|
||||||
|
|
||||||
|
def add_listener(self, listener):
|
||||||
|
'''
|
||||||
|
Add a listener. Will be called on change events with two arguments.
|
||||||
|
Event name and list of affected ids.
|
||||||
|
'''
|
||||||
|
self.listeners.add(listener)
|
||||||
|
|
||||||
|
def notify(self, event, ids=[]):
|
||||||
|
'Notify all listeners'
|
||||||
|
for listener in self.listeners:
|
||||||
|
try:
|
||||||
|
listener(event, ids)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
continue
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
def path(self, index, index_is_id=False):
|
||||||
|
'Return the relative path to the directory containing this books files as a unicode string.'
|
||||||
|
book_id = index if index_is_id else self.data.index_to_id(index)
|
||||||
|
return self.data.cache.field_for('path', book_id).replace('/', os.sep)
|
||||||
|
|
||||||
|
def abspath(self, index, index_is_id=False, create_dirs=True):
|
||||||
|
'Return the absolute path to the directory containing this books files as a unicode string.'
|
||||||
|
path = os.path.join(self.library_path, self.path(index, index_is_id=index_is_id))
|
||||||
|
if create_dirs and not os.path.exists(path):
|
||||||
|
os.makedirs(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class LegacyTest(BaseTest):
|
|||||||
'Test library wide properties'
|
'Test library wide properties'
|
||||||
def get_props(db):
|
def get_props(db):
|
||||||
props = ('user_version', 'is_second_db', 'library_id', 'field_metadata',
|
props = ('user_version', 'is_second_db', 'library_id', 'field_metadata',
|
||||||
'custom_column_label_map', 'custom_column_num_map')
|
'custom_column_label_map', 'custom_column_num_map', 'library_path', 'dbpath')
|
||||||
fprops = ('last_modified', )
|
fprops = ('last_modified', )
|
||||||
ans = {x:getattr(db, x) for x in props}
|
ans = {x:getattr(db, x) for x in props}
|
||||||
ans.update({x:getattr(db, x)() for x in fprops})
|
ans.update({x:getattr(db, x)() for x in fprops})
|
||||||
@ -51,6 +51,11 @@ class LegacyTest(BaseTest):
|
|||||||
if label in {'tags', 'formats'}:
|
if label in {'tags', 'formats'}:
|
||||||
# Order is random in the old db for these
|
# Order is random in the old db for these
|
||||||
ans[label] = tuple(set(x.split(',')) if x else x for x in ans[label])
|
ans[label] = tuple(set(x.split(',')) if x else x for x in ans[label])
|
||||||
|
if label == 'series_sort':
|
||||||
|
# The old db code did not take book language into account
|
||||||
|
# when generating series_sort values (the first book has
|
||||||
|
# lang=deu)
|
||||||
|
ans[label] = ans[label][1:]
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
old = self.init_old()
|
old = self.init_old()
|
||||||
@ -64,3 +69,31 @@ class LegacyTest(BaseTest):
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
def test_refresh(self): # {{{
|
||||||
|
' Test refreshing the view after a change to metadata.db '
|
||||||
|
db = self.init_legacy()
|
||||||
|
db2 = self.init_legacy()
|
||||||
|
self.assertEqual(db2.data.cache.set_field('title', {1:'xxx'}), set([1]))
|
||||||
|
db2.close()
|
||||||
|
del db2
|
||||||
|
self.assertNotEqual(db.title(1, index_is_id=True), 'xxx')
|
||||||
|
db.check_if_modified()
|
||||||
|
self.assertEqual(db.title(1, index_is_id=True), 'xxx')
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def test_legacy_getters(self): # {{{
|
||||||
|
old = self.init_old()
|
||||||
|
getters = ('path', 'abspath', 'title', 'authors', 'series',
|
||||||
|
'publisher', 'author_sort', 'authors', 'comments',
|
||||||
|
'comment', 'publisher', 'rating', 'series_index', 'tags',
|
||||||
|
'timestamp', 'uuid', 'pubdate', 'ondevice',
|
||||||
|
'metadata_last_modified', 'languages')
|
||||||
|
oldvals = {g:tuple(getattr(old, g)(x) for x in xrange(3)) + tuple(getattr(old, g)(x, True) for x in (1,2,3)) for g in getters}
|
||||||
|
old.close()
|
||||||
|
db = self.init_legacy()
|
||||||
|
newvals = {g:tuple(getattr(db, g)(x) for x in xrange(3)) + tuple(getattr(db, g)(x, True) for x in (1,2,3)) for g in getters}
|
||||||
|
for x in (oldvals, newvals):
|
||||||
|
x['tags'] = tuple(set(y.split(',')) if y else y for y in x['tags'])
|
||||||
|
self.assertEqual(oldvals, newvals)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
@ -294,3 +294,11 @@ class View(object):
|
|||||||
self.marked_ids = dict(izip(id_dict.iterkeys(), imap(unicode,
|
self.marked_ids = dict(izip(id_dict.iterkeys(), imap(unicode,
|
||||||
id_dict.itervalues())))
|
id_dict.itervalues())))
|
||||||
|
|
||||||
|
def refresh(self, field=None, ascending=True):
|
||||||
|
self._map = tuple(self.cache.all_book_ids())
|
||||||
|
self._map_filtered = tuple(self._map)
|
||||||
|
if field is not None:
|
||||||
|
self.sort(field, ascending)
|
||||||
|
if self.search_restriction or self.base_restriction:
|
||||||
|
self.search('', return_matches=False)
|
||||||
|
|
||||||
|
@ -71,6 +71,7 @@ class ANDROID(USBMS):
|
|||||||
0x42f7 : [0x216],
|
0x42f7 : [0x216],
|
||||||
0x4365 : [0x216],
|
0x4365 : [0x216],
|
||||||
0x4366 : [0x216],
|
0x4366 : [0x216],
|
||||||
|
0x4371 : [0x216],
|
||||||
},
|
},
|
||||||
# Freescale
|
# Freescale
|
||||||
0x15a2 : {
|
0x15a2 : {
|
||||||
@ -239,7 +240,7 @@ class ANDROID(USBMS):
|
|||||||
'ADVANCED', 'SGH-I727', 'USB_FLASH_DRIVER', 'ANDROID',
|
'ADVANCED', 'SGH-I727', 'USB_FLASH_DRIVER', 'ANDROID',
|
||||||
'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E',
|
'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E',
|
||||||
'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS',
|
'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS',
|
||||||
'ICS', 'E400', '__FILE-STOR_GADG', 'ST80208-1', 'GT-S5660M_CARD']
|
'ICS', 'E400', '__FILE-STOR_GADG', 'ST80208-1', 'GT-S5660M_CARD', 'XT894']
|
||||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||||
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||||
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
|
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
|
||||||
@ -250,7 +251,7 @@ class ANDROID(USBMS):
|
|||||||
'FILE-CD_GADGET', 'GT-I9001_CARD', 'USB_2.0', 'XT875',
|
'FILE-CD_GADGET', 'GT-I9001_CARD', 'USB_2.0', 'XT875',
|
||||||
'UMS_COMPOSITE', 'PRO', '.KOBO_VOX', 'SGH-T989_CARD', 'SGH-I727',
|
'UMS_COMPOSITE', 'PRO', '.KOBO_VOX', 'SGH-T989_CARD', 'SGH-I727',
|
||||||
'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E',
|
'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E',
|
||||||
'NOVO7', 'ADVANCED', 'TABLET_PC', 'F', 'E400_SD_CARD', 'ST80208-1']
|
'NOVO7', 'ADVANCED', 'TABLET_PC', 'F', 'E400_SD_CARD', 'ST80208-1', 'XT894']
|
||||||
|
|
||||||
OSX_MAIN_MEM = 'Android Device Main Memory'
|
OSX_MAIN_MEM = 'Android Device Main Memory'
|
||||||
|
|
||||||
|
@ -35,11 +35,11 @@ class KOBO(USBMS):
|
|||||||
gui_name = 'Kobo Reader'
|
gui_name = 'Kobo Reader'
|
||||||
description = _('Communicate with the Kobo Reader')
|
description = _('Communicate with the Kobo Reader')
|
||||||
author = 'Timothy Legge and David Forrester'
|
author = 'Timothy Legge and David Forrester'
|
||||||
version = (2, 0, 7)
|
version = (2, 0, 8)
|
||||||
|
|
||||||
dbversion = 0
|
dbversion = 0
|
||||||
fwversion = 0
|
fwversion = 0
|
||||||
supported_dbversion = 75
|
supported_dbversion = 80
|
||||||
has_kepubs = False
|
has_kepubs = False
|
||||||
|
|
||||||
supported_platforms = ['windows', 'osx', 'linux']
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
@ -419,7 +419,7 @@ class KOBO(USBMS):
|
|||||||
# If all this succeeds we need to delete the images files via the ImageID
|
# If all this succeeds we need to delete the images files via the ImageID
|
||||||
return ImageID
|
return ImageID
|
||||||
|
|
||||||
def delete_images(self, ImageID):
|
def delete_images(self, ImageID, book_path):
|
||||||
if ImageID != None:
|
if ImageID != None:
|
||||||
path_prefix = '.kobo/images/'
|
path_prefix = '.kobo/images/'
|
||||||
path = self._main_prefix + path_prefix + ImageID
|
path = self._main_prefix + path_prefix + ImageID
|
||||||
@ -449,7 +449,7 @@ class KOBO(USBMS):
|
|||||||
|
|
||||||
ImageID = self.delete_via_sql(ContentID, ContentType)
|
ImageID = self.delete_via_sql(ContentID, ContentType)
|
||||||
#print " We would now delete the Images for" + ImageID
|
#print " We would now delete the Images for" + ImageID
|
||||||
self.delete_images(ImageID)
|
self.delete_images(ImageID, path)
|
||||||
|
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
# Delete the ebook
|
# Delete the ebook
|
||||||
@ -1199,15 +1199,21 @@ class KOBO(USBMS):
|
|||||||
|
|
||||||
class KOBOTOUCH(KOBO):
|
class KOBOTOUCH(KOBO):
|
||||||
name = 'KoboTouch'
|
name = 'KoboTouch'
|
||||||
gui_name = 'Kobo Touch'
|
gui_name = 'Kobo Touch/Glo/Mini/Aura HD'
|
||||||
author = 'David Forrester'
|
author = 'David Forrester'
|
||||||
description = 'Communicate with the Kobo Touch, Glo and Mini firmware. Based on the existing Kobo driver by %s.' % (KOBO.author)
|
description = 'Communicate with the Kobo Touch, Glo, Mini and Aura HD ereaders. Based on the existing Kobo driver by %s.' % (KOBO.author)
|
||||||
# icon = I('devices/kobotouch.jpg')
|
# icon = I('devices/kobotouch.jpg')
|
||||||
|
|
||||||
supported_dbversion = 75
|
supported_dbversion = 80
|
||||||
min_supported_dbversion = 53
|
min_supported_dbversion = 53
|
||||||
min_dbversion_series = 65
|
min_dbversion_series = 65
|
||||||
min_dbversion_archive = 71
|
min_dbversion_archive = 71
|
||||||
|
min_dbversion_images_on_sdcard = 77
|
||||||
|
|
||||||
|
max_supported_fwversion = (2,5,1)
|
||||||
|
min_fwversion_images_on_sdcard = (2,4,1)
|
||||||
|
|
||||||
|
has_kepubs = True
|
||||||
|
|
||||||
booklist_class = KTCollectionsBookList
|
booklist_class = KTCollectionsBookList
|
||||||
book_class = Book
|
book_class = Book
|
||||||
@ -1291,10 +1297,11 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
TIMESTAMP_STRING = "%Y-%m-%dT%H:%M:%SZ"
|
TIMESTAMP_STRING = "%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
|
||||||
|
AURA_HD_PRODUCT_ID = [0x4193]
|
||||||
GLO_PRODUCT_ID = [0x4173]
|
GLO_PRODUCT_ID = [0x4173]
|
||||||
MINI_PRODUCT_ID = [0x4183]
|
MINI_PRODUCT_ID = [0x4183]
|
||||||
TOUCH_PRODUCT_ID = [0x4163]
|
TOUCH_PRODUCT_ID = [0x4163]
|
||||||
PRODUCT_ID = GLO_PRODUCT_ID + MINI_PRODUCT_ID + TOUCH_PRODUCT_ID
|
PRODUCT_ID = AURA_HD_PRODUCT_ID + GLO_PRODUCT_ID + MINI_PRODUCT_ID + TOUCH_PRODUCT_ID
|
||||||
|
|
||||||
BCD = [0x0110, 0x0326]
|
BCD = [0x0110, 0x0326]
|
||||||
|
|
||||||
@ -1313,6 +1320,11 @@ class KOBOTOUCH(KOBO):
|
|||||||
# ' - N3_LIBRARY_LIST.parsed':[(60,90),0, 53,],
|
# ' - N3_LIBRARY_LIST.parsed':[(60,90),0, 53,],
|
||||||
# ' - N3_LIBRARY_SHELF.parsed': [(40,60),0, 52,],
|
# ' - N3_LIBRARY_SHELF.parsed': [(40,60),0, 52,],
|
||||||
}
|
}
|
||||||
|
AURA_HD_COVER_FILE_ENDINGS = {
|
||||||
|
' - N3_FULL.parsed': [(1080,1440), 0, 99,True,], # Used for screensaver, home screen
|
||||||
|
' - N3_LIBRARY_FULL.parsed':[(355, 471), 0, 99,False,], # Used for Details screen
|
||||||
|
' - N3_LIBRARY_GRID.parsed':[(149, 198), 0, 99,False,], # Used for library lists
|
||||||
|
}
|
||||||
#Following are the sizes used with pre2.1.4 firmware
|
#Following are the sizes used with pre2.1.4 firmware
|
||||||
# COVER_FILE_ENDINGS = {
|
# COVER_FILE_ENDINGS = {
|
||||||
# ' - N3_LIBRARY_FULL.parsed':[(355,530),0, 99,], # Used for Details screen
|
# ' - N3_LIBRARY_FULL.parsed':[(355,530),0, 99,], # Used for Details screen
|
||||||
@ -1328,6 +1340,10 @@ class KOBOTOUCH(KOBO):
|
|||||||
super(KOBOTOUCH, self).initialize()
|
super(KOBOTOUCH, self).initialize()
|
||||||
self.bookshelvelist = []
|
self.bookshelvelist = []
|
||||||
|
|
||||||
|
def get_device_information(self, end_session=True):
|
||||||
|
self.set_device_name()
|
||||||
|
return super(KOBOTOUCH, self).get_device_information(end_session)
|
||||||
|
|
||||||
def books(self, oncard=None, end_session=True):
|
def books(self, oncard=None, end_session=True):
|
||||||
debug_print("KoboTouch:books - oncard='%s'"%oncard)
|
debug_print("KoboTouch:books - oncard='%s'"%oncard)
|
||||||
from calibre.ebooks.metadata.meta import path_to_ext
|
from calibre.ebooks.metadata.meta import path_to_ext
|
||||||
@ -1354,14 +1370,13 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
# Determine the firmware version
|
# Determine the firmware version
|
||||||
try:
|
try:
|
||||||
with open(self.normalize_path(self._main_prefix + '.kobo/version'),
|
with open(self.normalize_path(self._main_prefix + '.kobo/version'), 'rb') as f:
|
||||||
'rb') as f:
|
|
||||||
self.fwversion = f.readline().split(',')[2]
|
self.fwversion = f.readline().split(',')[2]
|
||||||
|
self.fwversion = tuple((int(x) for x in self.fwversion.split('.')))
|
||||||
except:
|
except:
|
||||||
self.fwversion = 'unknown'
|
self.fwversion = (0,0,0)
|
||||||
|
|
||||||
if self.fwversion != '1.0' and self.fwversion != '1.4':
|
debug_print('Kobo device: %s' % self.gui_name)
|
||||||
self.has_kepubs = True
|
|
||||||
debug_print('Version of driver:', self.version, 'Has kepubs:', self.has_kepubs)
|
debug_print('Version of driver:', self.version, 'Has kepubs:', self.has_kepubs)
|
||||||
debug_print('Version of firmware:', self.fwversion, 'Has kepubs:', self.has_kepubs)
|
debug_print('Version of firmware:', self.fwversion, 'Has kepubs:', self.has_kepubs)
|
||||||
|
|
||||||
@ -1374,7 +1389,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print(opts.extra_customization)
|
debug_print(opts.extra_customization)
|
||||||
if opts.extra_customization:
|
if opts.extra_customization:
|
||||||
debugging_title = opts.extra_customization[self.OPT_DEBUGGING_TITLE]
|
debugging_title = opts.extra_customization[self.OPT_DEBUGGING_TITLE]
|
||||||
debug_print("KoboTouch:books - set_debugging_title to", debugging_title )
|
debug_print("KoboTouch:books - set_debugging_title to '%s'" % debugging_title )
|
||||||
bl.set_debugging_title(debugging_title)
|
bl.set_debugging_title(debugging_title)
|
||||||
debug_print("KoboTouch:books - length bl=%d"%len(bl))
|
debug_print("KoboTouch:books - length bl=%d"%len(bl))
|
||||||
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
|
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
|
||||||
@ -1466,6 +1481,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
if show_debug:
|
if show_debug:
|
||||||
self.debug_index = idx
|
self.debug_index = idx
|
||||||
debug_print("KoboTouch:update_booklist - idx=%d"%idx)
|
debug_print("KoboTouch:update_booklist - idx=%d"%idx)
|
||||||
|
debug_print("KoboTouch:update_booklist - lpath=%s"%lpath)
|
||||||
debug_print('KoboTouch:update_booklist - bl[idx].device_collections=', bl[idx].device_collections)
|
debug_print('KoboTouch:update_booklist - bl[idx].device_collections=', bl[idx].device_collections)
|
||||||
debug_print('KoboTouch:update_booklist - playlist_map=', playlist_map)
|
debug_print('KoboTouch:update_booklist - playlist_map=', playlist_map)
|
||||||
debug_print('KoboTouch:update_booklist - bookshelves=', bookshelves)
|
debug_print('KoboTouch:update_booklist - bookshelves=', bookshelves)
|
||||||
@ -1477,7 +1493,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
bl_cache[lpath] = None
|
bl_cache[lpath] = None
|
||||||
|
|
||||||
if ImageID is not None:
|
if ImageID is not None:
|
||||||
imagename = self.imagefilename_from_imageID(ImageID)
|
imagename = self.imagefilename_from_imageID(prefix, ImageID)
|
||||||
if imagename is not None:
|
if imagename is not None:
|
||||||
bl[idx].thumbnail = ImageWrapper(imagename)
|
bl[idx].thumbnail = ImageWrapper(imagename)
|
||||||
if (ContentType == '6' and MimeType != 'application/x-kobo-epub+zip'):
|
if (ContentType == '6' and MimeType != 'application/x-kobo-epub+zip'):
|
||||||
@ -1717,12 +1733,14 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print("KoboTouch:books - end - oncard='%s'"%oncard)
|
debug_print("KoboTouch:books - end - oncard='%s'"%oncard)
|
||||||
return bl
|
return bl
|
||||||
|
|
||||||
def imagefilename_from_imageID(self, ImageID):
|
def imagefilename_from_imageID(self, prefix, ImageID):
|
||||||
show_debug = self.is_debugging_title(ImageID)
|
show_debug = self.is_debugging_title(ImageID)
|
||||||
|
|
||||||
|
path = self.images_path(prefix)
|
||||||
|
path = self.normalize_path(path.replace('/', os.sep))
|
||||||
|
|
||||||
for ending, cover_options in self.cover_file_endings().items():
|
for ending, cover_options in self.cover_file_endings().items():
|
||||||
fpath = self._main_prefix + '.kobo/images/' + ImageID + ending
|
fpath = path + ImageID + ending
|
||||||
fpath = self.normalize_path(fpath.replace('/', os.sep))
|
|
||||||
if os.path.exists(fpath):
|
if os.path.exists(fpath):
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:imagefilename_from_imageID - have cover image fpath=%s" % (fpath))
|
debug_print("KoboTouch:imagefilename_from_imageID - have cover image fpath=%s" % (fpath))
|
||||||
@ -1764,7 +1782,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
if not self.copying_covers():
|
if not self.copying_covers():
|
||||||
imageID = self.imageid_from_contentid(contentID)
|
imageID = self.imageid_from_contentid(contentID)
|
||||||
self.delete_images(imageID)
|
self.delete_images(imageID, fname)
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
@ -1821,11 +1839,11 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
return imageId
|
return imageId
|
||||||
|
|
||||||
def delete_images(self, ImageID):
|
def delete_images(self, ImageID, book_path):
|
||||||
debug_print("KoboTouch:delete_images - ImageID=", ImageID)
|
debug_print("KoboTouch:delete_images - ImageID=", ImageID)
|
||||||
if ImageID != None:
|
if ImageID != None:
|
||||||
path_prefix = '.kobo/images/'
|
path = self.images_path(book_path)
|
||||||
path = self._main_prefix + path_prefix + ImageID
|
path = path + ImageID
|
||||||
|
|
||||||
for ending in self.cover_file_endings().keys():
|
for ending in self.cover_file_endings().keys():
|
||||||
fpath = path + ending
|
fpath = path + ending
|
||||||
@ -1872,12 +1890,14 @@ class KOBOTOUCH(KOBO):
|
|||||||
def get_content_type_from_extension(self, extension):
|
def get_content_type_from_extension(self, extension):
|
||||||
debug_print("KoboTouch:get_content_type_from_extension - start")
|
debug_print("KoboTouch:get_content_type_from_extension - start")
|
||||||
# With new firmware, ContentType appears to be 6 for all types of sideloaded books.
|
# With new firmware, ContentType appears to be 6 for all types of sideloaded books.
|
||||||
if self.fwversion.startswith('2.'):
|
if self.fwversion >= (1,9,17) or extension == '.kobo' or extension == '.mobi':
|
||||||
debug_print("KoboTouch:get_content_type_from_extension - V2 firmware")
|
debug_print("KoboTouch:get_content_type_from_extension - V2 firmware")
|
||||||
ContentType = 6
|
ContentType = 6
|
||||||
|
# For older firmware, it depends on the type of file.
|
||||||
|
elif extension == '.kobo' or extension == '.mobi':
|
||||||
|
ContentType = 6
|
||||||
else:
|
else:
|
||||||
debug_print("KoboTouch:get_content_type_from_extension - calling super")
|
ContentType = 901
|
||||||
ContentType = super(KOBOTOUCH, self).get_content_type_from_extension(extension)
|
|
||||||
return ContentType
|
return ContentType
|
||||||
|
|
||||||
def update_device_database_collections(self, booklists, collections_attributes, oncard):
|
def update_device_database_collections(self, booklists, collections_attributes, oncard):
|
||||||
@ -1920,7 +1940,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
delete_empty_shelves = opts.extra_customization[self.OPT_DELETE_BOOKSHELVES] and self.supports_bookshelves()
|
delete_empty_shelves = opts.extra_customization[self.OPT_DELETE_BOOKSHELVES] and self.supports_bookshelves()
|
||||||
update_series_details = opts.extra_customization[self.OPT_UPDATE_SERIES_DETAILS] and self.supports_series()
|
update_series_details = opts.extra_customization[self.OPT_UPDATE_SERIES_DETAILS] and self.supports_series()
|
||||||
debugging_title = opts.extra_customization[self.OPT_DEBUGGING_TITLE]
|
debugging_title = opts.extra_customization[self.OPT_DEBUGGING_TITLE]
|
||||||
debug_print("KoboTouch:update_device_database_collections - set_debugging_title to", debugging_title )
|
debug_print("KoboTouch:update_device_database_collections - set_debugging_title to '%s'" % debugging_title )
|
||||||
booklists.set_debugging_title(debugging_title)
|
booklists.set_debugging_title(debugging_title)
|
||||||
else:
|
else:
|
||||||
delete_empty_shelves = False
|
delete_empty_shelves = False
|
||||||
@ -2088,8 +2108,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
# debug_print('KoboTouch: not uploading cover')
|
# debug_print('KoboTouch: not uploading cover')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Don't upload covers if book is on the SD card
|
# Only upload covers to SD card if that is supported
|
||||||
if self._card_a_prefix and path.startswith(self._card_a_prefix):
|
if self._card_a_prefix and path.startswith(self._card_a_prefix) and not self.supports_covers_on_sdcard():
|
||||||
return
|
return
|
||||||
|
|
||||||
if not opts.extra_customization[self.OPT_UPLOAD_GRAYSCALE_COVERS]:
|
if not opts.extra_customization[self.OPT_UPLOAD_GRAYSCALE_COVERS]:
|
||||||
@ -2111,6 +2131,16 @@ class KOBOTOUCH(KOBO):
|
|||||||
ImageID = ImageID.replace('.', '_')
|
ImageID = ImageID.replace('.', '_')
|
||||||
return ImageID
|
return ImageID
|
||||||
|
|
||||||
|
|
||||||
|
def images_path(self, path):
|
||||||
|
if self._card_a_prefix and path.startswith(self._card_a_prefix) and self.supports_covers_on_sdcard():
|
||||||
|
path_prefix = 'koboExtStorage/images/'
|
||||||
|
path = self._card_a_prefix + path_prefix
|
||||||
|
else:
|
||||||
|
path_prefix = '.kobo/images/'
|
||||||
|
path = self._main_prefix + path_prefix
|
||||||
|
return path
|
||||||
|
|
||||||
def _upload_cover(self, path, filename, metadata, filepath, uploadgrayscale, keep_cover_aspect=False):
|
def _upload_cover(self, path, filename, metadata, filepath, uploadgrayscale, keep_cover_aspect=False):
|
||||||
from calibre.utils.magick.draw import save_cover_data_to, identify_data
|
from calibre.utils.magick.draw import save_cover_data_to, identify_data
|
||||||
debug_print("KoboTouch:_upload_cover - filename='%s' uploadgrayscale='%s' "%(filename, uploadgrayscale))
|
debug_print("KoboTouch:_upload_cover - filename='%s' uploadgrayscale='%s' "%(filename, uploadgrayscale))
|
||||||
@ -2151,8 +2181,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
if ImageID != None:
|
if ImageID != None:
|
||||||
path_prefix = '.kobo/images/'
|
path = self.images_path(path) + ImageID
|
||||||
path = self._main_prefix + path_prefix + ImageID
|
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:_upload_cover - About to loop over cover endings")
|
debug_print("KoboTouch:_upload_cover - About to loop over cover endings")
|
||||||
|
|
||||||
@ -2496,6 +2526,8 @@ class KOBOTOUCH(KOBO):
|
|||||||
return opts
|
return opts
|
||||||
|
|
||||||
|
|
||||||
|
def isAuraHD(self):
|
||||||
|
return self.detected_device.idProduct in self.AURA_HD_PRODUCT_ID
|
||||||
def isGlo(self):
|
def isGlo(self):
|
||||||
return self.detected_device.idProduct in self.GLO_PRODUCT_ID
|
return self.detected_device.idProduct in self.GLO_PRODUCT_ID
|
||||||
def isMini(self):
|
def isMini(self):
|
||||||
@ -2504,7 +2536,21 @@ class KOBOTOUCH(KOBO):
|
|||||||
return self.detected_device.idProduct in self.TOUCH_PRODUCT_ID
|
return self.detected_device.idProduct in self.TOUCH_PRODUCT_ID
|
||||||
|
|
||||||
def cover_file_endings(self):
|
def cover_file_endings(self):
|
||||||
return self.GLO_COVER_FILE_ENDINGS if self.isGlo() else self.COVER_FILE_ENDINGS
|
return self.GLO_COVER_FILE_ENDINGS if self.isGlo() else self.AURA_HD_COVER_FILE_ENDINGS if self.isAuraHD() else self.COVER_FILE_ENDINGS
|
||||||
|
|
||||||
|
def set_device_name(self):
|
||||||
|
device_name = self.gui_name
|
||||||
|
if self.isAuraHD():
|
||||||
|
device_name = 'Kobo Aura HD'
|
||||||
|
elif self.isGlo():
|
||||||
|
device_name = 'Kobo Glo'
|
||||||
|
elif self.isMini():
|
||||||
|
device_name = 'Kobo Mini'
|
||||||
|
elif self.isTouch():
|
||||||
|
device_name = 'Kobo Touch'
|
||||||
|
self.__class__.gui_name = device_name
|
||||||
|
return device_name
|
||||||
|
|
||||||
|
|
||||||
def copying_covers(self):
|
def copying_covers(self):
|
||||||
opts = self.settings()
|
opts = self.settings()
|
||||||
@ -2524,6 +2570,44 @@ class KOBOTOUCH(KOBO):
|
|||||||
def supports_kobo_archive(self):
|
def supports_kobo_archive(self):
|
||||||
return self.dbversion >= self.min_dbversion_archive
|
return self.dbversion >= self.min_dbversion_archive
|
||||||
|
|
||||||
|
def supports_covers_on_sdcard(self):
|
||||||
|
return self.dbversion >= 77 and self.fwversion >= self.min_fwversion_images_on_sdcard
|
||||||
|
|
||||||
|
def modify_database_check(self, function):
|
||||||
|
# Checks to see whether the database version is supported
|
||||||
|
# and whether the user has chosen to support the firmware version
|
||||||
|
# debug_print("KoboTouch:modify_database_check - self.fwversion <= self.max_supported_fwversion=", self.fwversion > self.max_supported_fwversion)
|
||||||
|
if self.dbversion > self.supported_dbversion or self.fwversion > self.max_supported_fwversion:
|
||||||
|
# Unsupported database
|
||||||
|
opts = self.settings()
|
||||||
|
if not opts.extra_customization[self.OPT_SUPPORT_NEWER_FIRMWARE]:
|
||||||
|
debug_print('The database has been upgraded past supported version')
|
||||||
|
self.report_progress(1.0, _('Removing books from device...'))
|
||||||
|
from calibre.devices.errors import UserFeedback
|
||||||
|
raise UserFeedback(_("Kobo database version unsupported - See details"),
|
||||||
|
_('Your Kobo is running an updated firmware/database version.'
|
||||||
|
' As calibre does not know about this updated firmware,'
|
||||||
|
' database editing is disabled, to prevent corruption.'
|
||||||
|
' You can still send books to your Kobo with calibre, '
|
||||||
|
' but deleting books and managing collections is disabled.'
|
||||||
|
' If you are willing to experiment and know how to reset'
|
||||||
|
' your Kobo to Factory defaults, you can override this'
|
||||||
|
' check by right clicking the device icon in calibre and'
|
||||||
|
' selecting "Configure this device" and then the '
|
||||||
|
' "Attempt to support newer firmware" option.'
|
||||||
|
' Doing so may require you to perform a factory reset of'
|
||||||
|
' your Kobo.'
|
||||||
|
),
|
||||||
|
UserFeedback.WARN)
|
||||||
|
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# The user chose to edit the database anyway
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# Supported database version
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_debugging_title(cls, title):
|
def is_debugging_title(cls, title):
|
||||||
|
@ -95,7 +95,6 @@ class PDNOVEL(USBMS):
|
|||||||
SUPPORTS_SUB_DIRS = False
|
SUPPORTS_SUB_DIRS = False
|
||||||
DELETE_EXTS = ['.jpg', '.jpeg', '.png']
|
DELETE_EXTS = ['.jpg', '.jpeg', '.png']
|
||||||
|
|
||||||
|
|
||||||
def upload_cover(self, path, filename, metadata, filepath):
|
def upload_cover(self, path, filename, metadata, filepath):
|
||||||
coverdata = getattr(metadata, 'thumbnail', None)
|
coverdata = getattr(metadata, 'thumbnail', None)
|
||||||
if coverdata and coverdata[2]:
|
if coverdata and coverdata[2]:
|
||||||
@ -427,8 +426,8 @@ class WAYTEQ(USBMS):
|
|||||||
EBOOK_DIR_MAIN = 'Documents'
|
EBOOK_DIR_MAIN = 'Documents'
|
||||||
SCAN_FROM_ROOT = True
|
SCAN_FROM_ROOT = True
|
||||||
|
|
||||||
VENDOR_NAME = 'ROCKCHIP'
|
VENDOR_NAME = ['ROCKCHIP', 'CBR']
|
||||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'RK28_SDK_DEMO'
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['RK28_SDK_DEMO', 'EINK_EBOOK_READE']
|
||||||
SUPPORTS_SUB_DIRS = True
|
SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
def get_gui_name(self):
|
def get_gui_name(self):
|
||||||
@ -445,7 +444,8 @@ class WAYTEQ(USBMS):
|
|||||||
return self.EBOOK_DIR_CARD_A
|
return self.EBOOK_DIR_CARD_A
|
||||||
|
|
||||||
def windows_sort_drives(self, drives):
|
def windows_sort_drives(self, drives):
|
||||||
if len(drives) < 2: return drives
|
if len(drives) < 2:
|
||||||
|
return drives
|
||||||
main = drives.get('main', None)
|
main = drives.get('main', None)
|
||||||
carda = drives.get('carda', None)
|
carda = drives.get('carda', None)
|
||||||
if main and carda:
|
if main and carda:
|
||||||
@ -455,7 +455,8 @@ class WAYTEQ(USBMS):
|
|||||||
|
|
||||||
def linux_swap_drives(self, drives):
|
def linux_swap_drives(self, drives):
|
||||||
# See https://bugs.launchpad.net/bugs/1151901
|
# See https://bugs.launchpad.net/bugs/1151901
|
||||||
if len(drives) < 2 or not drives[1] or not drives[2]: return drives
|
if len(drives) < 2 or not drives[1] or not drives[2]:
|
||||||
|
return drives
|
||||||
drives = list(drives)
|
drives = list(drives)
|
||||||
t = drives[0]
|
t = drives[0]
|
||||||
drives[0] = drives[1]
|
drives[0] = drives[1]
|
||||||
@ -463,7 +464,8 @@ class WAYTEQ(USBMS):
|
|||||||
return tuple(drives)
|
return tuple(drives)
|
||||||
|
|
||||||
def osx_sort_names(self, names):
|
def osx_sort_names(self, names):
|
||||||
if len(names) < 2: return names
|
if len(names) < 2:
|
||||||
|
return names
|
||||||
main = names.get('main', None)
|
main = names.get('main', None)
|
||||||
card = names.get('carda', None)
|
card = names.get('carda', None)
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ class PICO(NEWSMY):
|
|||||||
gui_name = 'Pico'
|
gui_name = 'Pico'
|
||||||
description = _('Communicate with the Pico reader.')
|
description = _('Communicate with the Pico reader.')
|
||||||
|
|
||||||
VENDOR_NAME = ['TECLAST', 'IMAGIN', 'LASER-', '']
|
VENDOR_NAME = ['TECLAST', 'IMAGIN', 'LASER-', 'LASER', '']
|
||||||
WINDOWS_MAIN_MEM = ['USBDISK__USER', 'EB720']
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['USBDISK__USER', 'EB720', 'EBOOK-EB720']
|
||||||
EBOOK_DIR_MAIN = 'Books'
|
EBOOK_DIR_MAIN = 'Books'
|
||||||
FORMATS = ['EPUB', 'FB2', 'TXT', 'LRC', 'PDB', 'PDF', 'HTML', 'WTXT']
|
FORMATS = ['EPUB', 'FB2', 'TXT', 'LRC', 'PDB', 'PDF', 'HTML', 'WTXT']
|
||||||
SCAN_FROM_ROOT = True
|
SCAN_FROM_ROOT = True
|
||||||
|
@ -188,7 +188,6 @@ class EPUBInput(InputFormatPlugin):
|
|||||||
raise DRMError(os.path.basename(path))
|
raise DRMError(os.path.basename(path))
|
||||||
self.encrypted_fonts = self._encrypted_font_uris
|
self.encrypted_fonts = self._encrypted_font_uris
|
||||||
|
|
||||||
|
|
||||||
if len(parts) > 1 and parts[0]:
|
if len(parts) > 1 and parts[0]:
|
||||||
delta = '/'.join(parts[:-1])+'/'
|
delta = '/'.join(parts[:-1])+'/'
|
||||||
for elem in opf.itermanifest():
|
for elem in opf.itermanifest():
|
||||||
|
@ -4,12 +4,15 @@ __copyright__ = '2010, Fabian Grassl <fg@jusmeum.de>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, re, shutil
|
import os, re, shutil
|
||||||
from os.path import dirname, abspath, relpath, exists, basename
|
from os.path import dirname, abspath, relpath as _relpath, exists, basename
|
||||||
|
|
||||||
from calibre.customize.conversion import OutputFormatPlugin, OptionRecommendation
|
from calibre.customize.conversion import OutputFormatPlugin, OptionRecommendation
|
||||||
from calibre import CurrentDir
|
from calibre import CurrentDir
|
||||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
|
|
||||||
|
def relpath(*args):
|
||||||
|
return _relpath(*args).replace(os.sep, '/')
|
||||||
|
|
||||||
class HTMLOutput(OutputFormatPlugin):
|
class HTMLOutput(OutputFormatPlugin):
|
||||||
|
|
||||||
name = 'HTML Output'
|
name = 'HTML Output'
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
'''
|
'''
|
||||||
Basic support for manipulating OEB 1.x/2.0 content and metadata.
|
Basic support for manipulating OEB 1.x/2.0 content and metadata.
|
||||||
'''
|
'''
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
|
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
|
||||||
@ -11,7 +10,7 @@ import os, re, uuid, logging
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from itertools import count
|
from itertools import count
|
||||||
from urlparse import urldefrag, urlparse, urlunparse, urljoin
|
from urlparse import urldefrag, urlparse, urlunparse, urljoin
|
||||||
from urllib import unquote as urlunquote
|
from urllib import unquote
|
||||||
|
|
||||||
from lxml import etree, html
|
from lxml import etree, html
|
||||||
from calibre.constants import filesystem_encoding, __version__
|
from calibre.constants import filesystem_encoding, __version__
|
||||||
@ -142,7 +141,6 @@ def iterlinks(root, find_links_in_css=True):
|
|||||||
if attr in link_attrs:
|
if attr in link_attrs:
|
||||||
yield (el, attr, attribs[attr], 0)
|
yield (el, attr, attribs[attr], 0)
|
||||||
|
|
||||||
|
|
||||||
if not find_links_in_css:
|
if not find_links_in_css:
|
||||||
continue
|
continue
|
||||||
if tag == XHTML('style') and el.text:
|
if tag == XHTML('style') and el.text:
|
||||||
@ -363,7 +361,9 @@ URL_SAFE = set('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|||||||
URL_UNSAFE = [ASCII_CHARS - URL_SAFE, UNIBYTE_CHARS - URL_SAFE]
|
URL_UNSAFE = [ASCII_CHARS - URL_SAFE, UNIBYTE_CHARS - URL_SAFE]
|
||||||
|
|
||||||
def urlquote(href):
|
def urlquote(href):
|
||||||
"""Quote URL-unsafe characters, allowing IRI-safe characters."""
|
""" Quote URL-unsafe characters, allowing IRI-safe characters.
|
||||||
|
That is, this function returns valid IRIs not valid URIs. In particular,
|
||||||
|
IRIs can contain non-ascii characters. """
|
||||||
result = []
|
result = []
|
||||||
unsafe = 0 if isinstance(href, unicode) else 1
|
unsafe = 0 if isinstance(href, unicode) else 1
|
||||||
unsafe = URL_UNSAFE[unsafe]
|
unsafe = URL_UNSAFE[unsafe]
|
||||||
@ -373,6 +373,19 @@ def urlquote(href):
|
|||||||
result.append(char)
|
result.append(char)
|
||||||
return ''.join(result)
|
return ''.join(result)
|
||||||
|
|
||||||
|
def urlunquote(href):
|
||||||
|
# unquote must run on a bytestring and will return a bytestring
|
||||||
|
# If it runs on a unicode object, it returns a double encoded unicode
|
||||||
|
# string: unquote(u'%C3%A4') != unquote(b'%C3%A4').decode('utf-8')
|
||||||
|
# and the latter is correct
|
||||||
|
want_unicode = isinstance(href, unicode)
|
||||||
|
if want_unicode:
|
||||||
|
href = href.encode('utf-8')
|
||||||
|
href = unquote(href)
|
||||||
|
if want_unicode:
|
||||||
|
href = href.decode('utf-8')
|
||||||
|
return href
|
||||||
|
|
||||||
def urlnormalize(href):
|
def urlnormalize(href):
|
||||||
"""Convert a URL into normalized form, with all and only URL-unsafe
|
"""Convert a URL into normalized form, with all and only URL-unsafe
|
||||||
characters URL quoted.
|
characters URL quoted.
|
||||||
@ -469,7 +482,7 @@ class DirContainer(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _unquote(self, path):
|
def _unquote(self, path):
|
||||||
# urlunquote must run on a bytestring and will return a bytestring
|
# unquote must run on a bytestring and will return a bytestring
|
||||||
# If it runs on a unicode object, it returns a double encoded unicode
|
# If it runs on a unicode object, it returns a double encoded unicode
|
||||||
# string: unquote(u'%C3%A4') != unquote(b'%C3%A4').decode('utf-8')
|
# string: unquote(u'%C3%A4') != unquote(b'%C3%A4').decode('utf-8')
|
||||||
# and the latter is correct
|
# and the latter is correct
|
||||||
@ -577,12 +590,13 @@ class Metadata(object):
|
|||||||
allowed = self.allowed
|
allowed = self.allowed
|
||||||
if allowed is not None and term not in allowed:
|
if allowed is not None and term not in allowed:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
'attribute %r not valid for metadata term %r' \
|
'attribute %r not valid for metadata term %r'
|
||||||
% (self.attr(term), barename(obj.term)))
|
% (self.attr(term), barename(obj.term)))
|
||||||
return self.attr(term)
|
return self.attr(term)
|
||||||
|
|
||||||
def __get__(self, obj, cls):
|
def __get__(self, obj, cls):
|
||||||
if obj is None: return None
|
if obj is None:
|
||||||
|
return None
|
||||||
return obj.attrib.get(self.term_attr(obj), '')
|
return obj.attrib.get(self.term_attr(obj), '')
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
def __set__(self, obj, value):
|
||||||
@ -628,7 +642,7 @@ class Metadata(object):
|
|||||||
self.value = value
|
self.value = value
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
scheme = Attribute(lambda term: 'scheme' if \
|
scheme = Attribute(lambda term: 'scheme' if
|
||||||
term == OPF('meta') else OPF('scheme'),
|
term == OPF('meta') else OPF('scheme'),
|
||||||
[DC('identifier'), OPF('meta')])
|
[DC('identifier'), OPF('meta')])
|
||||||
file_as = Attribute(OPF('file-as'), [DC('creator'), DC('contributor'),
|
file_as = Attribute(OPF('file-as'), [DC('creator'), DC('contributor'),
|
||||||
@ -882,7 +896,6 @@ class Manifest(object):
|
|||||||
|
|
||||||
return self._parse_xhtml(convert_markdown(data, title=title))
|
return self._parse_xhtml(convert_markdown(data, title=title))
|
||||||
|
|
||||||
|
|
||||||
def _parse_css(self, data):
|
def _parse_css(self, data):
|
||||||
from cssutils import CSSParser, log, resolveImports
|
from cssutils import CSSParser, log, resolveImports
|
||||||
log.setLevel(logging.WARN)
|
log.setLevel(logging.WARN)
|
||||||
@ -1022,7 +1035,8 @@ class Manifest(object):
|
|||||||
target, frag = urldefrag(href)
|
target, frag = urldefrag(href)
|
||||||
target = target.split('/')
|
target = target.split('/')
|
||||||
for index in xrange(min(len(base), len(target))):
|
for index in xrange(min(len(base), len(target))):
|
||||||
if base[index] != target[index]: break
|
if base[index] != target[index]:
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
index += 1
|
index += 1
|
||||||
relhref = (['..'] * (len(base) - index)) + target[index:]
|
relhref = (['..'] * (len(base) - index)) + target[index:]
|
||||||
|
@ -46,7 +46,8 @@ def is_raster_image(media_type):
|
|||||||
return media_type and media_type.lower() in {
|
return media_type and media_type.lower() in {
|
||||||
'image/png', 'image/jpeg', 'image/jpg', 'image/gif'}
|
'image/png', 'image/jpeg', 'image/jpg', 'image/gif'}
|
||||||
|
|
||||||
COVER_TYPES = { 'coverimagestandard', 'other.ms-coverimage-standard',
|
COVER_TYPES = {
|
||||||
|
'coverimagestandard', 'other.ms-coverimage-standard',
|
||||||
'other.ms-titleimage-standard', 'other.ms-titleimage',
|
'other.ms-titleimage-standard', 'other.ms-titleimage',
|
||||||
'other.ms-coverimage', 'other.ms-thumbimage-standard',
|
'other.ms-coverimage', 'other.ms-thumbimage-standard',
|
||||||
'other.ms-thumbimage', 'thumbimagestandard', 'cover'}
|
'other.ms-thumbimage', 'thumbimagestandard', 'cover'}
|
||||||
@ -92,7 +93,8 @@ def find_cover_page(container):
|
|||||||
def find_cover_image_in_page(container, cover_page):
|
def find_cover_image_in_page(container, cover_page):
|
||||||
root = container.parsed(cover_page)
|
root = container.parsed(cover_page)
|
||||||
body = XPath('//h:body')(root)
|
body = XPath('//h:body')(root)
|
||||||
if len(body) != 1: return
|
if len(body) != 1:
|
||||||
|
return
|
||||||
body = body[0]
|
body = body[0]
|
||||||
images = []
|
images = []
|
||||||
for img in XPath('descendant::h:img[@src]|descendant::svg:svg/descendant::svg:image')(body):
|
for img in XPath('descendant::h:img[@src]|descendant::svg:svg/descendant::svg:image')(body):
|
||||||
@ -179,7 +181,7 @@ def create_epub_cover(container, cover_path):
|
|||||||
guide = container.opf_get_or_create('guide')
|
guide = container.opf_get_or_create('guide')
|
||||||
container.insert_into_xml(guide, guide.makeelement(
|
container.insert_into_xml(guide, guide.makeelement(
|
||||||
OPF('reference'), type='cover', title=_('Cover'),
|
OPF('reference'), type='cover', title=_('Cover'),
|
||||||
href=container.name_to_href(titlepage)))
|
href=container.name_to_href(titlepage, base=container.opf_name)))
|
||||||
metadata = container.opf_get_or_create('metadata')
|
metadata = container.opf_get_or_create('metadata')
|
||||||
meta = metadata.makeelement(OPF('meta'), name='cover')
|
meta = metadata.makeelement(OPF('meta'), name='cover')
|
||||||
meta.set('content', raster_cover_item.get('id'))
|
meta.set('content', raster_cover_item.get('id'))
|
||||||
|
@ -148,7 +148,6 @@ class OEBReader(object):
|
|||||||
if not has_aut:
|
if not has_aut:
|
||||||
m.add('creator', self.oeb.translate(__('Unknown')), role='aut')
|
m.add('creator', self.oeb.translate(__('Unknown')), role='aut')
|
||||||
|
|
||||||
|
|
||||||
def _manifest_prune_invalid(self):
|
def _manifest_prune_invalid(self):
|
||||||
'''
|
'''
|
||||||
Remove items from manifest that contain invalid data. This prevents
|
Remove items from manifest that contain invalid data. This prevents
|
||||||
@ -197,6 +196,8 @@ class OEBReader(object):
|
|||||||
item.media_type[-4:] in ('/xml', '+xml')):
|
item.media_type[-4:] in ('/xml', '+xml')):
|
||||||
hrefs = [r[2] for r in iterlinks(data)]
|
hrefs = [r[2] for r in iterlinks(data)]
|
||||||
for href in hrefs:
|
for href in hrefs:
|
||||||
|
if isinstance(href, bytes):
|
||||||
|
href = href.decode('utf-8')
|
||||||
href, _ = urldefrag(href)
|
href, _ = urldefrag(href)
|
||||||
if not href:
|
if not href:
|
||||||
continue
|
continue
|
||||||
@ -497,7 +498,8 @@ class OEBReader(object):
|
|||||||
titles = []
|
titles = []
|
||||||
headers = []
|
headers = []
|
||||||
for item in self.oeb.spine:
|
for item in self.oeb.spine:
|
||||||
if not item.linear: continue
|
if not item.linear:
|
||||||
|
continue
|
||||||
html = item.data
|
html = item.data
|
||||||
title = ''.join(xpath(html, '/h:html/h:head/h:title/text()'))
|
title = ''.join(xpath(html, '/h:html/h:head/h:title/text()'))
|
||||||
title = COLLAPSE_RE.sub(' ', title.strip())
|
title = COLLAPSE_RE.sub(' ', title.strip())
|
||||||
@ -515,17 +517,21 @@ class OEBReader(object):
|
|||||||
if len(titles) > len(set(titles)):
|
if len(titles) > len(set(titles)):
|
||||||
use = headers
|
use = headers
|
||||||
for title, item in izip(use, self.oeb.spine):
|
for title, item in izip(use, self.oeb.spine):
|
||||||
if not item.linear: continue
|
if not item.linear:
|
||||||
|
continue
|
||||||
toc.add(title, item.href)
|
toc.add(title, item.href)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _toc_from_opf(self, opf, item):
|
def _toc_from_opf(self, opf, item):
|
||||||
self.oeb.auto_generated_toc = False
|
self.oeb.auto_generated_toc = False
|
||||||
if self._toc_from_ncx(item): return
|
if self._toc_from_ncx(item):
|
||||||
|
return
|
||||||
# Prefer HTML to tour based TOC, since several LIT files
|
# Prefer HTML to tour based TOC, since several LIT files
|
||||||
# have good HTML TOCs but bad tour based TOCs
|
# have good HTML TOCs but bad tour based TOCs
|
||||||
if self._toc_from_html(opf): return
|
if self._toc_from_html(opf):
|
||||||
if self._toc_from_tour(opf): return
|
return
|
||||||
|
if self._toc_from_tour(opf):
|
||||||
|
return
|
||||||
self._toc_from_spine(opf)
|
self._toc_from_spine(opf)
|
||||||
self.oeb.auto_generated_toc = True
|
self.oeb.auto_generated_toc = True
|
||||||
|
|
||||||
@ -589,8 +595,10 @@ class OEBReader(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _pages_from_opf(self, opf, item):
|
def _pages_from_opf(self, opf, item):
|
||||||
if self._pages_from_ncx(opf, item): return
|
if self._pages_from_ncx(opf, item):
|
||||||
if self._pages_from_page_map(opf): return
|
return
|
||||||
|
if self._pages_from_page_map(opf):
|
||||||
|
return
|
||||||
return
|
return
|
||||||
|
|
||||||
def _cover_from_html(self, hcover):
|
def _cover_from_html(self, hcover):
|
||||||
|
@ -47,6 +47,8 @@ class ManifestTrimmer(object):
|
|||||||
item.data is not None:
|
item.data is not None:
|
||||||
hrefs = [r[2] for r in iterlinks(item.data)]
|
hrefs = [r[2] for r in iterlinks(item.data)]
|
||||||
for href in hrefs:
|
for href in hrefs:
|
||||||
|
if isinstance(href, bytes):
|
||||||
|
href = href.decode('utf-8')
|
||||||
try:
|
try:
|
||||||
href = item.abshref(urlnormalize(href))
|
href = item.abshref(urlnormalize(href))
|
||||||
except:
|
except:
|
||||||
|
@ -51,7 +51,7 @@ class Links(object):
|
|||||||
for link in self.links:
|
for link in self.links:
|
||||||
path, href, frag = link[0]
|
path, href, frag = link[0]
|
||||||
page, rect = link[1:]
|
page, rect = link[1:]
|
||||||
combined_path = os.path.abspath(os.path.join(os.path.dirname(path), *href.split('/')))
|
combined_path = os.path.abspath(os.path.join(os.path.dirname(path), *unquote(href).split('/')))
|
||||||
is_local = not href or combined_path in self.anchors
|
is_local = not href or combined_path in self.anchors
|
||||||
annot = Dictionary({
|
annot = Dictionary({
|
||||||
'Type':Name('Annot'), 'Subtype':Name('Link'),
|
'Type':Name('Annot'), 'Subtype':Name('Link'),
|
||||||
|
@ -406,6 +406,7 @@ class BookInfo(QWebView):
|
|||||||
remove_format = pyqtSignal(int, object)
|
remove_format = pyqtSignal(int, object)
|
||||||
save_format = pyqtSignal(int, object)
|
save_format = pyqtSignal(int, object)
|
||||||
restore_format = pyqtSignal(int, object)
|
restore_format = pyqtSignal(int, object)
|
||||||
|
copy_link = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, vertical, parent=None):
|
def __init__(self, vertical, parent=None):
|
||||||
QWebView.__init__(self, parent)
|
QWebView.__init__(self, parent)
|
||||||
@ -419,26 +420,33 @@ class BookInfo(QWebView):
|
|||||||
palette.setBrush(QPalette.Base, Qt.transparent)
|
palette.setBrush(QPalette.Base, Qt.transparent)
|
||||||
self.page().setPalette(palette)
|
self.page().setPalette(palette)
|
||||||
self.css = P('templates/book_details.css', data=True).decode('utf-8')
|
self.css = P('templates/book_details.css', data=True).decode('utf-8')
|
||||||
for x, icon in [('remove', 'trash.png'), ('save', 'save.png'), ('restore', 'edit-undo.png')]:
|
for x, icon in [('remove_format', 'trash.png'), ('save_format', 'save.png'), ('restore_format', 'edit-undo.png'), ('copy_link','edit-copy.png')]:
|
||||||
ac = QAction(QIcon(I(icon)), '', self)
|
ac = QAction(QIcon(I(icon)), '', self)
|
||||||
ac.current_fmt = None
|
ac.current_fmt = None
|
||||||
ac.triggered.connect(getattr(self, '%s_format_triggerred'%x))
|
ac.current_url = None
|
||||||
setattr(self, '%s_format_action'%x, ac)
|
ac.triggered.connect(getattr(self, '%s_triggerred'%x))
|
||||||
|
setattr(self, '%s_action'%x, ac)
|
||||||
|
|
||||||
def context_action_triggered(self, which):
|
def context_action_triggered(self, which):
|
||||||
f = getattr(self, '%s_format_action'%which).current_fmt
|
f = getattr(self, '%s_action'%which).current_fmt
|
||||||
if f:
|
url = getattr(self, '%s_action'%which).current_url
|
||||||
|
if f and 'format' in which:
|
||||||
book_id, fmt = f
|
book_id, fmt = f
|
||||||
getattr(self, '%s_format'%which).emit(book_id, fmt)
|
getattr(self, which).emit(book_id, fmt)
|
||||||
|
if url and 'link' in which:
|
||||||
|
getattr(self, which).emit(url)
|
||||||
|
|
||||||
def remove_format_triggerred(self):
|
def remove_format_triggerred(self):
|
||||||
self.context_action_triggered('remove')
|
self.context_action_triggered('remove_format')
|
||||||
|
|
||||||
def save_format_triggerred(self):
|
def save_format_triggerred(self):
|
||||||
self.context_action_triggered('save')
|
self.context_action_triggered('save_format')
|
||||||
|
|
||||||
def restore_format_triggerred(self):
|
def restore_format_triggerred(self):
|
||||||
self.context_action_triggered('restore')
|
self.context_action_triggered('restore_format')
|
||||||
|
|
||||||
|
def copy_link_triggerred(self):
|
||||||
|
self.context_action_triggered('copy_link')
|
||||||
|
|
||||||
def link_activated(self, link):
|
def link_activated(self, link):
|
||||||
self._link_clicked = True
|
self._link_clicked = True
|
||||||
@ -474,7 +482,16 @@ class BookInfo(QWebView):
|
|||||||
for action in list(menu.actions()):
|
for action in list(menu.actions()):
|
||||||
if action is not ca:
|
if action is not ca:
|
||||||
menu.removeAction(action)
|
menu.removeAction(action)
|
||||||
if not r.isNull() and url.startswith('format:'):
|
if not r.isNull():
|
||||||
|
if url.startswith('http'):
|
||||||
|
for a, t in [('copy', _('&Copy Link')),
|
||||||
|
]:
|
||||||
|
ac = getattr(self, '%s_link_action'%a)
|
||||||
|
ac.current_url = url
|
||||||
|
ac.setText(t)
|
||||||
|
menu.addAction(ac)
|
||||||
|
|
||||||
|
if url.startswith('format:'):
|
||||||
parts = url.split(':')
|
parts = url.split(':')
|
||||||
try:
|
try:
|
||||||
book_id, fmt = int(parts[1]), parts[2]
|
book_id, fmt = int(parts[1]), parts[2]
|
||||||
@ -594,6 +611,7 @@ class BookDetails(QWidget): # {{{
|
|||||||
remove_specific_format = pyqtSignal(int, object)
|
remove_specific_format = pyqtSignal(int, object)
|
||||||
save_specific_format = pyqtSignal(int, object)
|
save_specific_format = pyqtSignal(int, object)
|
||||||
restore_specific_format = pyqtSignal(int, object)
|
restore_specific_format = pyqtSignal(int, object)
|
||||||
|
copy_link = pyqtSignal(object)
|
||||||
remote_file_dropped = pyqtSignal(object, object)
|
remote_file_dropped = pyqtSignal(object, object)
|
||||||
files_dropped = pyqtSignal(object, object)
|
files_dropped = pyqtSignal(object, object)
|
||||||
cover_changed = pyqtSignal(object, object)
|
cover_changed = pyqtSignal(object, object)
|
||||||
@ -664,6 +682,7 @@ class BookDetails(QWidget): # {{{
|
|||||||
self.book_info.remove_format.connect(self.remove_specific_format)
|
self.book_info.remove_format.connect(self.remove_specific_format)
|
||||||
self.book_info.save_format.connect(self.save_specific_format)
|
self.book_info.save_format.connect(self.save_specific_format)
|
||||||
self.book_info.restore_format.connect(self.restore_specific_format)
|
self.book_info.restore_format.connect(self.restore_specific_format)
|
||||||
|
self.book_info.copy_link.connect(self.copy_link)
|
||||||
self.setCursor(Qt.PointingHandCursor)
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
|
|
||||||
def handle_click(self, link):
|
def handle_click(self, link):
|
||||||
|
@ -147,6 +147,7 @@ class Config(ResizableDialog, Ui_Dialog):
|
|||||||
self.connect(self.groups, SIGNAL('entered(QModelIndex)'),
|
self.connect(self.groups, SIGNAL('entered(QModelIndex)'),
|
||||||
self.show_group_help)
|
self.show_group_help)
|
||||||
rb = self.buttonBox.button(self.buttonBox.RestoreDefaults)
|
rb = self.buttonBox.button(self.buttonBox.RestoreDefaults)
|
||||||
|
rb.setText(_('Restore &Defaults'))
|
||||||
self.connect(rb, SIGNAL('clicked()'), self.restore_defaults)
|
self.connect(rb, SIGNAL('clicked()'), self.restore_defaults)
|
||||||
self.groups.setMouseTracking(True)
|
self.groups.setMouseTracking(True)
|
||||||
geom = gprefs.get('convert_single_dialog_geom', None)
|
geom = gprefs.get('convert_single_dialog_geom', None)
|
||||||
@ -188,7 +189,6 @@ class Config(ResizableDialog, Ui_Dialog):
|
|||||||
return cls(self.stack, self.plumber.get_option_by_name,
|
return cls(self.stack, self.plumber.get_option_by_name,
|
||||||
self.plumber.get_option_help, self.db, self.book_id)
|
self.plumber.get_option_help, self.db, self.book_id)
|
||||||
|
|
||||||
|
|
||||||
self.mw = widget_factory(MetadataWidget)
|
self.mw = widget_factory(MetadataWidget)
|
||||||
self.setWindowTitle(_('Convert')+ ' ' + unicode(self.mw.title.text()))
|
self.setWindowTitle(_('Convert')+ ' ' + unicode(self.mw.title.text()))
|
||||||
lf = widget_factory(LookAndFeelWidget)
|
lf = widget_factory(LookAndFeelWidget)
|
||||||
@ -209,7 +209,8 @@ class Config(ResizableDialog, Ui_Dialog):
|
|||||||
self.plumber.get_option_help, self.db, self.book_id)
|
self.plumber.get_option_help, self.db, self.book_id)
|
||||||
while True:
|
while True:
|
||||||
c = self.stack.currentWidget()
|
c = self.stack.currentWidget()
|
||||||
if not c: break
|
if not c:
|
||||||
|
break
|
||||||
self.stack.removeWidget(c)
|
self.stack.removeWidget(c)
|
||||||
|
|
||||||
widgets = [self.mw, lf, hw, ps, sd, toc, sr]
|
widgets = [self.mw, lf, hw, ps, sd, toc, sr]
|
||||||
@ -234,7 +235,6 @@ class Config(ResizableDialog, Ui_Dialog):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def setup_input_output_formats(self, db, book_id, preferred_input_format,
|
def setup_input_output_formats(self, db, book_id, preferred_input_format,
|
||||||
preferred_output_format):
|
preferred_output_format):
|
||||||
if preferred_output_format:
|
if preferred_output_format:
|
||||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from PyQt4.Qt import (Qt, QStackedWidget, QMenu, QTimer,
|
from PyQt4.Qt import (Qt, QApplication, QStackedWidget, QMenu, QTimer,
|
||||||
QSize, QSizePolicy, QStatusBar, QLabel, QFont)
|
QSize, QSizePolicy, QStatusBar, QLabel, QFont)
|
||||||
|
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
@ -274,6 +274,8 @@ class LayoutMixin(object): # {{{
|
|||||||
self.iactions['Save To Disk'].save_library_format_by_ids)
|
self.iactions['Save To Disk'].save_library_format_by_ids)
|
||||||
self.book_details.restore_specific_format.connect(
|
self.book_details.restore_specific_format.connect(
|
||||||
self.iactions['Remove Books'].restore_format)
|
self.iactions['Remove Books'].restore_format)
|
||||||
|
self.book_details.copy_link.connect(self.bd_copy_link,
|
||||||
|
type=Qt.QueuedConnection)
|
||||||
self.book_details.view_device_book.connect(
|
self.book_details.view_device_book.connect(
|
||||||
self.iactions['View'].view_device_book)
|
self.iactions['View'].view_device_book)
|
||||||
|
|
||||||
@ -295,6 +297,10 @@ class LayoutMixin(object): # {{{
|
|||||||
if self.cover_flow:
|
if self.cover_flow:
|
||||||
self.cover_flow.dataChanged()
|
self.cover_flow.dataChanged()
|
||||||
|
|
||||||
|
def bd_copy_link(self, url):
|
||||||
|
if url:
|
||||||
|
QApplication.clipboard().setText(url)
|
||||||
|
|
||||||
def save_layout_state(self):
|
def save_layout_state(self):
|
||||||
for x in ('library', 'memory', 'card_a', 'card_b'):
|
for x in ('library', 'memory', 'card_a', 'card_b'):
|
||||||
getattr(self, x+'_view').save_state()
|
getattr(self, x+'_view').save_state()
|
||||||
|
@ -21,7 +21,7 @@ from PyQt4.Qt import (
|
|||||||
QDialog, QVBoxLayout, QLabel, QDialogButtonBox, QStyle, QStackedWidget,
|
QDialog, QVBoxLayout, QLabel, QDialogButtonBox, QStyle, QStackedWidget,
|
||||||
QWidget, QTableView, QGridLayout, QFontInfo, QPalette, QTimer, pyqtSignal,
|
QWidget, QTableView, QGridLayout, QFontInfo, QPalette, QTimer, pyqtSignal,
|
||||||
QAbstractTableModel, QVariant, QSize, QListView, QPixmap, QModelIndex,
|
QAbstractTableModel, QVariant, QSize, QListView, QPixmap, QModelIndex,
|
||||||
QAbstractListModel, QColor, QRect, QTextBrowser, QStringListModel)
|
QAbstractListModel, QColor, QRect, QTextBrowser, QStringListModel, QMenu, QCursor)
|
||||||
from PyQt4.QtWebKit import QWebView
|
from PyQt4.QtWebKit import QWebView
|
||||||
|
|
||||||
from calibre.customize.ui import metadata_plugins
|
from calibre.customize.ui import metadata_plugins
|
||||||
@ -182,7 +182,6 @@ class ResultsModel(QAbstractTableModel): # {{{
|
|||||||
p = book.publisher if book.publisher else ''
|
p = book.publisher if book.publisher else ''
|
||||||
return '<b>%s</b><br><i>%s</i>' % (d, p)
|
return '<b>%s</b><br><i>%s</i>' % (d, p)
|
||||||
|
|
||||||
|
|
||||||
def data(self, index, role):
|
def data(self, index, role):
|
||||||
row, col = index.row(), index.column()
|
row, col = index.row(), index.column()
|
||||||
try:
|
try:
|
||||||
@ -552,7 +551,6 @@ class IdentifyWidget(QWidget): # {{{
|
|||||||
self.results_view.show_results(self.worker.results)
|
self.results_view.show_results(self.worker.results)
|
||||||
self.results_found.emit()
|
self.results_found.emit()
|
||||||
|
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
self.abort.set()
|
self.abort.set()
|
||||||
# }}}
|
# }}}
|
||||||
@ -609,7 +607,8 @@ class CoverWorker(Thread): # {{{
|
|||||||
|
|
||||||
def scan_once(self, tdir, seen):
|
def scan_once(self, tdir, seen):
|
||||||
for x in list(os.listdir(tdir)):
|
for x in list(os.listdir(tdir)):
|
||||||
if x in seen: continue
|
if x in seen:
|
||||||
|
continue
|
||||||
if x.endswith('.cover') and os.path.exists(os.path.join(tdir,
|
if x.endswith('.cover') and os.path.exists(os.path.join(tdir,
|
||||||
x+'.done')):
|
x+'.done')):
|
||||||
name = x.rpartition('.')[0]
|
name = x.rpartition('.')[0]
|
||||||
@ -793,6 +792,8 @@ class CoversView(QListView): # {{{
|
|||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
|
|
||||||
self.doubleClicked.connect(self.chosen, type=Qt.QueuedConnection)
|
self.doubleClicked.connect(self.chosen, type=Qt.QueuedConnection)
|
||||||
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
self.customContextMenuRequested.connect(self.show_context_menu)
|
||||||
|
|
||||||
def select(self, num):
|
def select(self, num):
|
||||||
current = self.model().index(num)
|
current = self.model().index(num)
|
||||||
@ -814,6 +815,21 @@ class CoversView(QListView): # {{{
|
|||||||
else:
|
else:
|
||||||
self.select(self.m.index_from_pointer(pointer).row())
|
self.select(self.m.index_from_pointer(pointer).row())
|
||||||
|
|
||||||
|
def show_context_menu(self, point):
|
||||||
|
idx = self.currentIndex()
|
||||||
|
if idx and idx.isValid() and not idx.data(Qt.UserRole).toPyObject():
|
||||||
|
m = QMenu()
|
||||||
|
m.addAction(QIcon(I('view.png')), _('View this cover at full size'), self.show_cover)
|
||||||
|
m.exec_(QCursor.pos())
|
||||||
|
|
||||||
|
def show_cover(self):
|
||||||
|
idx = self.currentIndex()
|
||||||
|
pmap = self.model().cover_pixmap(idx)
|
||||||
|
if pmap is not None:
|
||||||
|
from calibre.gui2.viewer.image_popup import ImageView
|
||||||
|
d = ImageView(self, pmap, unicode(idx.data(Qt.DisplayRole).toString()), geom_name='metadata_download_cover_popup_geom')
|
||||||
|
d(use_exec=True)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class CoversWidget(QWidget): # {{{
|
class CoversWidget(QWidget): # {{{
|
||||||
|
@ -164,7 +164,7 @@ Author matching is exact.</string>
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Ignore files with the following extensions when automatically adding </string>
|
<string><b>Ignore</b> files with the following extensions when automatically adding </string>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -129,7 +129,7 @@
|
|||||||
<item row="6" column="0">
|
<item row="6" column="0">
|
||||||
<widget class="QLabel" name="label_16">
|
<widget class="QLabel" name="label_16">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Max. OPDS &ungrouped items:</string>
|
<string>Max. &ungrouped items:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>opt_max_opds_ungrouped_items</cstring>
|
<cstring>opt_max_opds_ungrouped_items</cstring>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
store_version = 1 # Needed for dynamic plugin loading
|
store_version = 2 # Needed for dynamic plugin loading
|
||||||
|
|
||||||
__license__ = 'GPL 3'
|
__license__ = 'GPL 3'
|
||||||
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
@ -25,25 +25,7 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
|||||||
class GoogleBooksStore(BasicStoreConfig, StorePlugin):
|
class GoogleBooksStore(BasicStoreConfig, StorePlugin):
|
||||||
|
|
||||||
def open(self, parent=None, detail_item=None, external=False):
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
aff_id = {
|
url = 'http://books.google.com/books'
|
||||||
'lid': '41000000033185143',
|
|
||||||
'pubid': '21000000000352219',
|
|
||||||
'ganpub': 'k352219',
|
|
||||||
'ganclk': 'GOOG_1335334761',
|
|
||||||
}
|
|
||||||
# Use Kovid's affiliate id 30% of the time.
|
|
||||||
if random.randint(1, 10) in (1, 2, 3):
|
|
||||||
aff_id = {
|
|
||||||
'lid': '41000000031855266',
|
|
||||||
'pubid': '21000000000352583',
|
|
||||||
'ganpub': 'k352583',
|
|
||||||
'ganclk': 'GOOG_1335335464',
|
|
||||||
}
|
|
||||||
|
|
||||||
url = 'http://gan.doubleclick.net/gan_click?lid=%(lid)s&pubid=%(pubid)s' % aff_id
|
|
||||||
if detail_item:
|
|
||||||
detail_item += '&ganpub=%(ganpub)s&ganclk=%(ganclk)s' % aff_id
|
|
||||||
|
|
||||||
if external or self.config.get('open_external', False):
|
if external or self.config.get('open_external', False):
|
||||||
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
|
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
|
||||||
else:
|
else:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
store_version = 1 # Needed for dynamic plugin loading
|
store_version = 2 # Needed for dynamic plugin loading
|
||||||
|
|
||||||
__license__ = 'GPL 3'
|
__license__ = 'GPL 3'
|
||||||
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
@ -31,10 +31,10 @@ class KoboStore(BasicStoreConfig, StorePlugin):
|
|||||||
if random.randint(1, 10) in (1, 2, 3):
|
if random.randint(1, 10) in (1, 2, 3):
|
||||||
pub_id = '0dsO3kDu/AU'
|
pub_id = '0dsO3kDu/AU'
|
||||||
|
|
||||||
murl = 'http://click.linksynergy.com/fs-bin/click?id=%s&offerid=268429.4&type=3&subid=0' % pub_id
|
murl = 'http://click.linksynergy.com/fs-bin/click?id=%s&subid=&offerid=280046.1&type=10&tmpid=9310&RD_PARM1=http%%3A%%2F%%2Fkobo.com' % pub_id
|
||||||
|
|
||||||
if detail_item:
|
if detail_item:
|
||||||
purl = 'http://click.linksynergy.com/link?id=%s&offerid=268429&type=2&murl=%s' % (pub_id, urllib.quote_plus(detail_item))
|
purl = 'http://click.linksynergy.com/link?id=%s&offerid=280046&type=2&murl=%s' % (pub_id, urllib.quote_plus(detail_item))
|
||||||
url = purl
|
url = purl
|
||||||
else:
|
else:
|
||||||
purl = None
|
purl = None
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
store_version = 1 # Needed for dynamic plugin loading
|
store_version = 2 # Needed for dynamic plugin loading
|
||||||
|
|
||||||
__license__ = 'GPL 3'
|
__license__ = 'GPL 3'
|
||||||
__copyright__ = '2012, John Schember <john@nachtimwald.com>'
|
__copyright__ = '2012, John Schember <john@nachtimwald.com>'
|
||||||
@ -25,11 +25,19 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
|||||||
class NookUKStore(BasicStoreConfig, StorePlugin):
|
class NookUKStore(BasicStoreConfig, StorePlugin):
|
||||||
|
|
||||||
def open(self, parent=None, detail_item=None, external=False):
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
url = "http://uk.nook.com"
|
url = 'http://www.awin1.com/awclick.php?mid=5266&id=120917'
|
||||||
|
detail_url = 'http://www.awin1.com/cread.php?awinmid=5266&awinaffid=120917&clickref=&p='
|
||||||
|
|
||||||
if external or self.config.get('open_external', False):
|
if external or self.config.get('open_external', False):
|
||||||
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
|
if detail_item:
|
||||||
|
url = detail_url + detail_item
|
||||||
|
|
||||||
|
open_url(QUrl(url_slash_cleaner(url)))
|
||||||
else:
|
else:
|
||||||
|
if detail_item:
|
||||||
|
detail_url = detail_url + detail_item
|
||||||
|
else:
|
||||||
|
detail_url = None
|
||||||
d = WebStoreDialog(self.gui, url, parent, detail_item)
|
d = WebStoreDialog(self.gui, url, parent, detail_item)
|
||||||
d.setWindowTitle(self.name)
|
d.setWindowTitle(self.name)
|
||||||
d.set_tags(self.config.get('tags', ''))
|
d.set_tags(self.config.get('tags', ''))
|
||||||
|
@ -15,16 +15,17 @@ from calibre.gui2 import choose_save_file, gprefs
|
|||||||
|
|
||||||
class ImageView(QDialog):
|
class ImageView(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent, current_img, current_url):
|
def __init__(self, parent, current_img, current_url, geom_name='viewer_image_popup_geometry'):
|
||||||
QDialog.__init__(self)
|
QDialog.__init__(self)
|
||||||
dw = QApplication.instance().desktop()
|
dw = QApplication.instance().desktop()
|
||||||
self.avail_geom = dw.availableGeometry(parent)
|
self.avail_geom = dw.availableGeometry(parent)
|
||||||
self.current_img = current_img
|
self.current_img = current_img
|
||||||
self.current_url = current_url
|
self.current_url = current_url
|
||||||
self.factor = 1.0
|
self.factor = 1.0
|
||||||
|
self.geom_name = geom_name
|
||||||
|
|
||||||
self.label = l = QLabel()
|
self.label = l = QLabel()
|
||||||
l.setBackgroundRole(QPalette.Base);
|
l.setBackgroundRole(QPalette.Base)
|
||||||
l.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
|
l.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
|
||||||
l.setScaledContents(True)
|
l.setScaledContents(True)
|
||||||
|
|
||||||
@ -88,21 +89,27 @@ class ImageView(QDialog):
|
|||||||
self.label.setPixmap(pm)
|
self.label.setPixmap(pm)
|
||||||
self.label.adjustSize()
|
self.label.adjustSize()
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self, use_exec=False):
|
||||||
geom = self.avail_geom
|
geom = self.avail_geom
|
||||||
self.label.setPixmap(self.current_img)
|
self.label.setPixmap(self.current_img)
|
||||||
self.label.adjustSize()
|
self.label.adjustSize()
|
||||||
self.resize(QSize(int(geom.width()/2.5), geom.height()-50))
|
self.resize(QSize(int(geom.width()/2.5), geom.height()-50))
|
||||||
geom = gprefs.get('viewer_image_popup_geometry', None)
|
geom = gprefs.get(self.geom_name, None)
|
||||||
if geom is not None:
|
if geom is not None:
|
||||||
self.restoreGeometry(geom)
|
self.restoreGeometry(geom)
|
||||||
|
try:
|
||||||
self.current_image_name = unicode(self.current_url.toString()).rpartition('/')[-1]
|
self.current_image_name = unicode(self.current_url.toString()).rpartition('/')[-1]
|
||||||
|
except AttributeError:
|
||||||
|
self.current_image_name = self.current_url
|
||||||
title = _('View Image: %s')%self.current_image_name
|
title = _('View Image: %s')%self.current_image_name
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
|
if use_exec:
|
||||||
|
self.exec_()
|
||||||
|
else:
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def done(self, e):
|
def done(self, e):
|
||||||
gprefs['viewer_image_popup_geometry'] = bytearray(self.saveGeometry())
|
gprefs[self.geom_name] = bytearray(self.saveGeometry())
|
||||||
return QDialog.done(self, e)
|
return QDialog.done(self, e)
|
||||||
|
|
||||||
def wheelEvent(self, event):
|
def wheelEvent(self, event):
|
||||||
|
@ -493,7 +493,6 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
return matches
|
return matches
|
||||||
|
|
||||||
def get_keypair_matches(self, location, query, candidates):
|
def get_keypair_matches(self, location, query, candidates):
|
||||||
print query
|
|
||||||
matches = set([])
|
matches = set([])
|
||||||
if query.find(':') >= 0:
|
if query.find(':') >= 0:
|
||||||
q = [q.strip() for q in query.split(':')]
|
q = [q.strip() for q in query.split(':')]
|
||||||
|
11
src/calibre/utils/pyparsing.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
# Dummy file for backwards compatibility with older plugins
|
||||||
|
from calibre.utils.search_query_parser import ParseException # noqa
|
||||||
|
|