mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
GwR wip
This commit is contained in:
commit
b58656ed2e
3191
resources/images/default_cover.svg
Normal file
3191
resources/images/default_cover.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 105 KiB |
@ -2,7 +2,7 @@ from __future__ import with_statement
|
|||||||
__license__ = 'GPL 3'
|
__license__ = 'GPL 3'
|
||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
|
||||||
import re
|
import time
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class TheHindu(BasicNewsRecipe):
|
class TheHindu(BasicNewsRecipe):
|
||||||
@ -10,45 +10,41 @@ class TheHindu(BasicNewsRecipe):
|
|||||||
language = 'en_IN'
|
language = 'en_IN'
|
||||||
|
|
||||||
oldest_article = 7
|
oldest_article = 7
|
||||||
__author__ = 'Kovid Goyal and Sujata Raman'
|
__author__ = 'Kovid Goyal'
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
|
|
||||||
remove_tags_before = {'name':'font', 'class':'storyhead'}
|
keep_only_tags = [dict(id='content')]
|
||||||
preprocess_regexps = [
|
remove_tags = [dict(attrs={'class':['article-links', 'breadcr']}),
|
||||||
(re.compile(r'<!-- story ends -->.*', re.DOTALL),
|
dict(id=['email-section', 'right-column', 'printfooter'])]
|
||||||
lambda match: '</body></html>'),
|
|
||||||
]
|
extra_css = '.photo-caption { font-size: smaller }'
|
||||||
extra_css = '''
|
|
||||||
.storyhead{font-family:Arial,Helvetica,sans-serif; font-size:large; color:#000099;}
|
|
||||||
body{font-family:Verdana,Arial,Helvetica,sans-serif; font-size:x-small; text-align:left;}
|
|
||||||
'''
|
|
||||||
feeds = [
|
|
||||||
(u'Main - Front Page', u'http://www.hindu.com/rss/01hdline.xml'),
|
|
||||||
(u'Main - National', u'http://www.hindu.com/rss/02hdline.xml'),
|
|
||||||
(u'Main - International', u'http://www.hindu.com/rss/03hdline.xml'),
|
|
||||||
(u'Main - Opinion', u'http://www.hindu.com/rss/05hdline.xml'),
|
|
||||||
(u'Main - Business', u'http://www.hindu.com/rss/06hdline.xml'),
|
|
||||||
(u'Main - Sport', u'http://www.hindu.com/rss/07hdline.xml'),
|
|
||||||
(u'Main - Weather / Religion / Crossword / Cartoon',
|
|
||||||
u'http://www.hindu.com/rss/10hdline.xml'),
|
|
||||||
(u'Main - Engagements', u'http://www.hindu.com/rss/26hdline.xml'),
|
|
||||||
(u'Supplement - Literary Review',
|
|
||||||
u'http://www.hindu.com/rss/lrhdline.xml'),
|
|
||||||
(u'Supplement - Sunday Magazine',
|
|
||||||
u'http://www.hindu.com/rss/maghdline.xml'),
|
|
||||||
(u'Supplement - Open Page', u'http://www.hindu.com/rss/ophdline.xml'),
|
|
||||||
(u'Supplement - Business Review',
|
|
||||||
u'http://www.hindu.com/rss/bizhdline.xml'),
|
|
||||||
(u'Supplement - Book Review',
|
|
||||||
u'http://www.hindu.com/rss/brhdline.xml'),
|
|
||||||
(u'Supplement - Science & Technology',
|
|
||||||
u'http://www.hindu.com/rss/setahdline.xml')
|
|
||||||
]
|
|
||||||
|
|
||||||
def postprocess_html(self, soup, first_fetch):
|
def postprocess_html(self, soup, first_fetch):
|
||||||
for t in soup.findAll(['table', 'tr', 'td','center']):
|
for t in soup.findAll(['table', 'tr', 'td','center']):
|
||||||
t.name = 'div'
|
t.name = 'div'
|
||||||
|
|
||||||
|
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
|
def parse_index(self):
|
||||||
|
today = time.strftime('%Y-%m-%d')
|
||||||
|
soup = self.index_to_soup(
|
||||||
|
'http://www.thehindu.com/todays-paper/tp-index/?date=' + today)
|
||||||
|
div = soup.find(id='left-column')
|
||||||
|
feeds = []
|
||||||
|
current_section = None
|
||||||
|
current_articles = []
|
||||||
|
for x in div.findAll(['h3', 'div']):
|
||||||
|
if current_section and x.get('class', '') == 'tpaper':
|
||||||
|
a = x.find('a', href=True)
|
||||||
|
if a is not None:
|
||||||
|
current_articles.append({'url':a['href']+'?css=print',
|
||||||
|
'title':self.tag_to_string(a), 'date': '',
|
||||||
|
'description':''})
|
||||||
|
if x.name == 'h3':
|
||||||
|
if current_section and current_articles:
|
||||||
|
feeds.append((current_section, current_articles))
|
||||||
|
current_section = self.tag_to_string(x)
|
||||||
|
current_articles = []
|
||||||
|
return feeds
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
|
||||||
|
|
||||||
class TimesOfIndia(BasicNewsRecipe):
|
class TimesOfIndia(BasicNewsRecipe):
|
||||||
title = u'Times of India'
|
title = u'Times of India'
|
||||||
language = 'en_IN'
|
language = 'en_IN'
|
||||||
__author__ = 'Krittika Goyal'
|
__author__ = 'Kovid Goyal'
|
||||||
oldest_article = 1 #days
|
oldest_article = 1 #days
|
||||||
max_articles_per_feed = 25
|
max_articles_per_feed = 25
|
||||||
|
|
||||||
remove_stylesheets = True
|
no_stylesheets = True
|
||||||
|
keep_only_tags = [dict(attrs={'class':'prttabl'})]
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name='iframe'),
|
dict(style=lambda x: x and 'float' in x)
|
||||||
dict(name='td', attrs={'class':'newptool1'}),
|
|
||||||
dict(name='div', attrs={'id':'newptool'}),
|
|
||||||
dict(name='ul', attrs={'class':'newtabcontent_tabs_new'}),
|
|
||||||
dict(name='b', text='Topics'),
|
|
||||||
dict(name='span', text=':'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
@ -42,13 +37,8 @@ class TimesOfIndia(BasicNewsRecipe):
|
|||||||
('Most Read',
|
('Most Read',
|
||||||
'http://timesofindia.indiatimes.com/rssfeedmostread.cms')
|
'http://timesofindia.indiatimes.com/rssfeedmostread.cms')
|
||||||
]
|
]
|
||||||
|
def print_version(self, url):
|
||||||
|
return url + '?prtpage=1'
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
heading = soup.find(name='h1', attrs={'class':'heading'})
|
|
||||||
td = heading.findParent(name='td')
|
|
||||||
td.extract()
|
|
||||||
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
|
|
||||||
body = soup.find(name='body')
|
|
||||||
body.insert(0, td)
|
|
||||||
td.name = 'div'
|
|
||||||
return soup
|
return soup
|
||||||
|
35
resources/recipes/winnipeg_sun.recipe
Normal file
35
resources/recipes/winnipeg_sun.recipe
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1277647803(BasicNewsRecipe):
|
||||||
|
title = u'Winnipeg Sun'
|
||||||
|
__author__ = 'rty'
|
||||||
|
__version__ = '1.0'
|
||||||
|
oldest_article = 2
|
||||||
|
pubisher = 'www.winnipegsun.com'
|
||||||
|
description = 'Winnipeg Newspaper'
|
||||||
|
category = 'News, Winnipeg, Canada'
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
encoding = 'UTF-8'
|
||||||
|
remove_javascript = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'en_CA'
|
||||||
|
feeds = [
|
||||||
|
(u'News', u'http://www.winnipegsun.com/news/rss.xml'),
|
||||||
|
(u'Columnists', u'http://www.winnipegsun.com/columnists/rss.xml'),
|
||||||
|
(u'Editorial', u'http://www.winnipegsun.com/comment/editorial/rss.xml'),
|
||||||
|
(u'Entertainments', u'http://www.winnipegsun.com/entertainment/rss.xml'),
|
||||||
|
(u'Life', u'http://www.winnipegsun.com/life/rss.xml'),
|
||||||
|
(u'Money', u'http://www.winnipegsun.com/money/rss.xml')
|
||||||
|
]
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'id':'article'}),
|
||||||
|
]
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'class':['leftBox','bottomBox clear']}),
|
||||||
|
dict(name='ul', attrs={'class':'tabs dl contentSwap'}),
|
||||||
|
dict(name='div', attrs={'id':'commentsBottom'}),
|
||||||
|
]
|
||||||
|
remove_tags_after = [
|
||||||
|
dict(name='div', attrs={'class':'bottomBox clear'})
|
||||||
|
]
|
@ -453,7 +453,7 @@ from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK
|
|||||||
from calibre.devices.edge.driver import EDGE
|
from calibre.devices.edge.driver import EDGE
|
||||||
from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS
|
from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS
|
||||||
from calibre.devices.sne.driver import SNE
|
from calibre.devices.sne.driver import SNE
|
||||||
from calibre.devices.misc import PALMPRE, AVANT
|
from calibre.devices.misc import PALMPRE, AVANT, SWEEX
|
||||||
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
|
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
|
||||||
from calibre.devices.kobo.driver import KOBO
|
from calibre.devices.kobo.driver import KOBO
|
||||||
|
|
||||||
@ -499,7 +499,6 @@ plugins += [
|
|||||||
]
|
]
|
||||||
# Order here matters. The first matched device is the one used.
|
# Order here matters. The first matched device is the one used.
|
||||||
plugins += [
|
plugins += [
|
||||||
ITUNES,
|
|
||||||
HANLINV3,
|
HANLINV3,
|
||||||
HANLINV5,
|
HANLINV5,
|
||||||
BLACKBERRY,
|
BLACKBERRY,
|
||||||
@ -551,6 +550,8 @@ plugins += [
|
|||||||
FOLDER_DEVICE_FOR_CONFIG,
|
FOLDER_DEVICE_FOR_CONFIG,
|
||||||
AVANT,
|
AVANT,
|
||||||
MENTOR,
|
MENTOR,
|
||||||
|
SWEEX,
|
||||||
|
ITUNES,
|
||||||
]
|
]
|
||||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||||
x.__name__.endswith('MetadataReader')]
|
x.__name__.endswith('MetadataReader')]
|
||||||
|
@ -279,6 +279,7 @@ class iPadOutput(OutputProfile):
|
|||||||
width:18%;
|
width:18%;
|
||||||
}
|
}
|
||||||
.article_link {
|
.article_link {
|
||||||
|
color: #593f29;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.article_next {
|
.article_next {
|
||||||
@ -310,7 +311,7 @@ class iPadOutput(OutputProfile):
|
|||||||
}
|
}
|
||||||
|
|
||||||
.touchscreen_navbar {
|
.touchscreen_navbar {
|
||||||
background:#ccc;
|
background:#c3bab2;
|
||||||
border:#ccc 0px solid;
|
border:#ccc 0px solid;
|
||||||
border-collapse:separate;
|
border-collapse:separate;
|
||||||
border-spacing:1px;
|
border-spacing:1px;
|
||||||
@ -322,11 +323,17 @@ class iPadOutput(OutputProfile):
|
|||||||
.touchscreen_navbar td {
|
.touchscreen_navbar td {
|
||||||
background:#fff;
|
background:#fff;
|
||||||
font-family:Helvetica;
|
font-family:Helvetica;
|
||||||
font-size:90%;
|
font-size:80%;
|
||||||
padding: 5px;
|
/* UI touchboxes use 8px padding */
|
||||||
|
padding: 6px;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.touchscreen_navbar td a:link {
|
||||||
|
color: #593f29;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Index formatting */
|
/* Index formatting */
|
||||||
.publish_date {
|
.publish_date {
|
||||||
text-align:center;
|
text-align:center;
|
||||||
|
@ -51,8 +51,8 @@ class ANDROID(USBMS):
|
|||||||
'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX']
|
'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX']
|
||||||
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
||||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD',
|
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD',
|
||||||
'PR OD_GT-I9000', 'FILE-STOR_GADGET']
|
'GT-I9000', 'FILE-STOR_GADGET']
|
||||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'PR OD_GT-I9000_CARD',
|
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD',
|
||||||
'FILE-STOR_GADGET']
|
'FILE-STOR_GADGET']
|
||||||
|
|
||||||
OSX_MAIN_MEM = 'HTC Android Phone Media'
|
OSX_MAIN_MEM = 'HTC Android Phone Media'
|
||||||
|
@ -38,6 +38,7 @@ if iswindows:
|
|||||||
class DriverBase(DeviceConfig, DevicePlugin):
|
class DriverBase(DeviceConfig, DevicePlugin):
|
||||||
# Needed for config_widget to work
|
# Needed for config_widget to work
|
||||||
FORMATS = ['epub', 'pdf']
|
FORMATS = ['epub', 'pdf']
|
||||||
|
#SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _config_base_name(cls):
|
def _config_base_name(cls):
|
||||||
@ -87,7 +88,7 @@ class ITUNES(DriverBase):
|
|||||||
supported_platforms = ['osx','windows']
|
supported_platforms = ['osx','windows']
|
||||||
author = 'GRiker'
|
author = 'GRiker'
|
||||||
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
||||||
version = (0,8,0)
|
version = (0,9,0)
|
||||||
|
|
||||||
OPEN_FEEDBACK_MESSAGE = _(
|
OPEN_FEEDBACK_MESSAGE = _(
|
||||||
'Apple device detected, launching iTunes, please wait ...')
|
'Apple device detected, launching iTunes, please wait ...')
|
||||||
@ -106,53 +107,55 @@ class ITUNES(DriverBase):
|
|||||||
BCD = [0x01]
|
BCD = [0x01]
|
||||||
|
|
||||||
# iTunes enumerations
|
# iTunes enumerations
|
||||||
Sources = [
|
Audiobooks = [
|
||||||
'Unknown',
|
'Audible file',
|
||||||
'Library',
|
'MPEG audio file',
|
||||||
'iPod',
|
'Protected AAC audio file'
|
||||||
'AudioCD',
|
]
|
||||||
'MP3CD',
|
|
||||||
'Device',
|
|
||||||
'RadioTuner',
|
|
||||||
'SharedLibrary']
|
|
||||||
|
|
||||||
ArtworkFormat = [
|
ArtworkFormat = [
|
||||||
'Unknown',
|
'Unknown',
|
||||||
'JPEG',
|
'JPEG',
|
||||||
'PNG',
|
'PNG',
|
||||||
'BMP'
|
'BMP'
|
||||||
]
|
]
|
||||||
|
|
||||||
PlaylistKind = [
|
PlaylistKind = [
|
||||||
'Unknown',
|
'Unknown',
|
||||||
'Library',
|
'Library',
|
||||||
'User',
|
'User',
|
||||||
'CD',
|
'CD',
|
||||||
'Device',
|
'Device',
|
||||||
'Radio Tuner'
|
'Radio Tuner'
|
||||||
]
|
]
|
||||||
|
|
||||||
PlaylistSpecialKind = [
|
PlaylistSpecialKind = [
|
||||||
'Unknown',
|
'Unknown',
|
||||||
'Purchased Music',
|
'Purchased Music',
|
||||||
'Party Shuffle',
|
'Party Shuffle',
|
||||||
'Podcasts',
|
'Podcasts',
|
||||||
'Folder',
|
'Folder',
|
||||||
'Video',
|
'Video',
|
||||||
'Music',
|
'Music',
|
||||||
'Movies',
|
'Movies',
|
||||||
'TV Shows',
|
'TV Shows',
|
||||||
'Books',
|
'Books',
|
||||||
]
|
]
|
||||||
|
|
||||||
SearchField = [
|
SearchField = [
|
||||||
'All',
|
'All',
|
||||||
'Visible',
|
'Visible',
|
||||||
'Artists',
|
'Artists',
|
||||||
'Albums',
|
'Albums',
|
||||||
'Composers',
|
'Composers',
|
||||||
'SongNames',
|
'SongNames',
|
||||||
]
|
]
|
||||||
|
Sources = [
|
||||||
|
'Unknown',
|
||||||
|
'Library',
|
||||||
|
'iPod',
|
||||||
|
'AudioCD',
|
||||||
|
'MP3CD',
|
||||||
|
'Device',
|
||||||
|
'RadioTuner',
|
||||||
|
'SharedLibrary'
|
||||||
|
]
|
||||||
|
|
||||||
# Cover art size limits
|
# Cover art size limits
|
||||||
MAX_COVER_WIDTH = 510
|
MAX_COVER_WIDTH = 510
|
||||||
@ -532,8 +535,11 @@ class ITUNES(DriverBase):
|
|||||||
# Turn off the Save template
|
# Turn off the Save template
|
||||||
cw.opt_save_template.setVisible(False)
|
cw.opt_save_template.setVisible(False)
|
||||||
cw.label.setVisible(False)
|
cw.label.setVisible(False)
|
||||||
# Repurpose the checkbox
|
# Repurpose the metadata checkbox
|
||||||
cw.opt_read_metadata.setText(_("Use Series as Category in iTunes/iBooks"))
|
cw.opt_read_metadata.setText(_("Use Series as Category in iTunes/iBooks"))
|
||||||
|
# Repurpose the use_subdirs checkbox
|
||||||
|
# cw.opt_use_subdirs.setText(_("Do not display books in iTunes/iBooks database\n"
|
||||||
|
# "(shortens load time with very large collections)."))
|
||||||
return cw
|
return cw
|
||||||
|
|
||||||
def delete_books(self, paths, end_session=True):
|
def delete_books(self, paths, end_session=True):
|
||||||
@ -1766,7 +1772,7 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
for book in books:
|
for book in books:
|
||||||
# This may need additional entries for international iTunes users
|
# This may need additional entries for international iTunes users
|
||||||
if book.kind() in ['MPEG audio file']:
|
if book.kind() in self.Audiobooks:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
|
self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
|
||||||
else:
|
else:
|
||||||
@ -1798,7 +1804,7 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
for book in dev_books:
|
for book in dev_books:
|
||||||
# This may need additional entries for international iTunes users
|
# This may need additional entries for international iTunes users
|
||||||
if book.KindAsString in ['MPEG audio file']:
|
if book.KindAsString in self.Audiobooks:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString))
|
self.log.info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString))
|
||||||
else:
|
else:
|
||||||
@ -1899,7 +1905,7 @@ class ITUNES(DriverBase):
|
|||||||
lib_books = pl.file_tracks()
|
lib_books = pl.file_tracks()
|
||||||
for book in lib_books:
|
for book in lib_books:
|
||||||
# This may need additional entries for international iTunes users
|
# This may need additional entries for international iTunes users
|
||||||
if book.kind() in ['MPEG audio file']:
|
if book.kind() in self.Audiobooks:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
|
self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
|
||||||
else:
|
else:
|
||||||
@ -1955,7 +1961,7 @@ class ITUNES(DriverBase):
|
|||||||
try:
|
try:
|
||||||
for book in lib_books:
|
for book in lib_books:
|
||||||
# This may need additional entries for international iTunes users
|
# This may need additional entries for international iTunes users
|
||||||
if book.KindAsString in ['MPEG audio file']:
|
if book.KindAsString in self.Audiobooks:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString))
|
self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString))
|
||||||
else:
|
else:
|
||||||
|
@ -13,7 +13,7 @@ from calibre import isbytestring
|
|||||||
|
|
||||||
class Book(MetaInformation):
|
class Book(MetaInformation):
|
||||||
|
|
||||||
BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections']
|
BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections', '_new_book']
|
||||||
|
|
||||||
JSON_ATTRS = [
|
JSON_ATTRS = [
|
||||||
'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort',
|
'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort',
|
||||||
@ -27,6 +27,7 @@ class Book(MetaInformation):
|
|||||||
|
|
||||||
MetaInformation.__init__(self, '')
|
MetaInformation.__init__(self, '')
|
||||||
self.device_collections = []
|
self.device_collections = []
|
||||||
|
self._new_book = False
|
||||||
|
|
||||||
self.path = os.path.join(prefix, lpath)
|
self.path = os.path.join(prefix, lpath)
|
||||||
if os.sep == '\\':
|
if os.sep == '\\':
|
||||||
|
@ -49,3 +49,23 @@ class AVANT(USBMS):
|
|||||||
EBOOK_DIR_MAIN = ''
|
EBOOK_DIR_MAIN = ''
|
||||||
SUPPORTS_SUB_DIRS = True
|
SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
|
class SWEEX(USBMS):
|
||||||
|
name = 'Sweex Device Interface'
|
||||||
|
gui_name = 'Sweex'
|
||||||
|
description = _('Communicate with the Sweex MM300')
|
||||||
|
author = 'Kovid Goyal'
|
||||||
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
|
||||||
|
# Ordered list of supported formats
|
||||||
|
FORMATS = ['epub', 'prc', 'fb2', 'html', 'rtf', 'chm', 'pdf', 'txt']
|
||||||
|
|
||||||
|
VENDOR_ID = [0x0525]
|
||||||
|
PRODUCT_ID = [0xa4a5]
|
||||||
|
BCD = [0x0319]
|
||||||
|
|
||||||
|
VENDOR_NAME = 'SWEEX'
|
||||||
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOKREADER'
|
||||||
|
|
||||||
|
EBOOK_DIR_MAIN = ''
|
||||||
|
SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from calibre.utils.config import prefs
|
|||||||
|
|
||||||
class Book(MetaInformation):
|
class Book(MetaInformation):
|
||||||
|
|
||||||
BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections']
|
BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections', '_new_book']
|
||||||
|
|
||||||
JSON_ATTRS = [
|
JSON_ATTRS = [
|
||||||
'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort',
|
'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort',
|
||||||
@ -30,6 +30,7 @@ class Book(MetaInformation):
|
|||||||
|
|
||||||
MetaInformation.__init__(self, '')
|
MetaInformation.__init__(self, '')
|
||||||
|
|
||||||
|
self._new_book = False
|
||||||
self.device_collections = []
|
self.device_collections = []
|
||||||
self.path = os.path.join(prefix, lpath)
|
self.path = os.path.join(prefix, lpath)
|
||||||
if os.sep == '\\':
|
if os.sep == '\\':
|
||||||
@ -133,12 +134,21 @@ class CollectionsBookList(BookList):
|
|||||||
def get_collections(self, collection_attributes):
|
def get_collections(self, collection_attributes):
|
||||||
collections = {}
|
collections = {}
|
||||||
series_categories = set([])
|
series_categories = set([])
|
||||||
collection_attributes = list(collection_attributes)
|
for book in self:
|
||||||
if prefs['preserve_user_collections']:
|
# The default: leave the book in all existing collections. Do not
|
||||||
collection_attributes += ['device_collections']
|
# add any new ones.
|
||||||
for attr in collection_attributes:
|
attrs = ['device_collections']
|
||||||
attr = attr.strip()
|
if getattr(book, '_new_book', False):
|
||||||
for book in self:
|
if prefs['preserve_user_collections']:
|
||||||
|
# Ensure that the book is in all the book's existing
|
||||||
|
# collections plus all metadata collections
|
||||||
|
attrs += collection_attributes
|
||||||
|
else:
|
||||||
|
# The book's existing collections are ignored. Put the book
|
||||||
|
# in collections defined by its metadata.
|
||||||
|
attrs = collection_attributes
|
||||||
|
for attr in attrs:
|
||||||
|
attr = attr.strip()
|
||||||
val = getattr(book, attr, None)
|
val = getattr(book, attr, None)
|
||||||
if not val: continue
|
if not val: continue
|
||||||
if isbytestring(val):
|
if isbytestring(val):
|
||||||
|
@ -233,6 +233,7 @@ class USBMS(CLI, Device):
|
|||||||
book = self.book_class(prefix, lpath, other=info)
|
book = self.book_class(prefix, lpath, other=info)
|
||||||
if book.size is None:
|
if book.size is None:
|
||||||
book.size = os.stat(self.normalize_path(path)).st_size
|
book.size = os.stat(self.normalize_path(path)).st_size
|
||||||
|
book._new_book = True # Must be before add_book
|
||||||
booklists[blist].add_book(book, replace_metadata=True)
|
booklists[blist].add_book(book, replace_metadata=True)
|
||||||
self.report_progress(1.0, _('Adding books to device metadata listing...'))
|
self.report_progress(1.0, _('Adding books to device metadata listing...'))
|
||||||
debug_print('USBMS: finished adding metadata')
|
debug_print('USBMS: finished adding metadata')
|
||||||
@ -273,6 +274,9 @@ class USBMS(CLI, Device):
|
|||||||
self.report_progress(1.0, _('Removing books from device metadata listing...'))
|
self.report_progress(1.0, _('Removing books from device metadata listing...'))
|
||||||
debug_print('USBMS: finished removing metadata for %d books'%(len(paths)))
|
debug_print('USBMS: finished removing metadata for %d books'%(len(paths)))
|
||||||
|
|
||||||
|
# If you override this method and you use book._new_book, then you must
|
||||||
|
# complete the processing before you call this method. The flag is cleared
|
||||||
|
# at the end just before the return
|
||||||
def sync_booklists(self, booklists, end_session=True):
|
def sync_booklists(self, booklists, end_session=True):
|
||||||
debug_print('USBMS: starting sync_booklists')
|
debug_print('USBMS: starting sync_booklists')
|
||||||
|
|
||||||
@ -291,6 +295,12 @@ class USBMS(CLI, Device):
|
|||||||
write_prefix(self._card_a_prefix, 1)
|
write_prefix(self._card_a_prefix, 1)
|
||||||
write_prefix(self._card_b_prefix, 2)
|
write_prefix(self._card_b_prefix, 2)
|
||||||
|
|
||||||
|
# Clear the _new_book indication, as we are supposed to be done with
|
||||||
|
# adding books at this point
|
||||||
|
for blist in booklists:
|
||||||
|
for book in blist:
|
||||||
|
book._new_book = False
|
||||||
|
|
||||||
self.report_progress(1.0, _('Sending metadata to device...'))
|
self.report_progress(1.0, _('Sending metadata to device...'))
|
||||||
debug_print('USBMS: finished sync_booklists')
|
debug_print('USBMS: finished sync_booklists')
|
||||||
|
|
||||||
|
@ -273,10 +273,11 @@ def filter_metadata_results(item):
|
|||||||
|
|
||||||
def do_cover_check(item):
|
def do_cover_check(item):
|
||||||
item.has_cover = False
|
item.has_cover = False
|
||||||
try:
|
if item.isbn:
|
||||||
item.has_cover = check_for_cover(item.isbn)
|
try:
|
||||||
except:
|
item.has_cover = check_for_cover(item.isbn)
|
||||||
pass # Cover not found
|
except:
|
||||||
|
pass # Cover not found
|
||||||
|
|
||||||
def check_for_covers(items):
|
def check_for_covers(items):
|
||||||
threads = [Thread(target=do_cover_check, args=(item,)) for item in items]
|
threads = [Thread(target=do_cover_check, args=(item,)) for item in items]
|
||||||
|
@ -34,7 +34,8 @@ def fetch_metadata(url, max=100, timeout=5.):
|
|||||||
errmsg = soup.find('errormessage').string
|
errmsg = soup.find('errormessage').string
|
||||||
raise ISBNDBError('Error fetching metadata: '+errmsg)
|
raise ISBNDBError('Error fetching metadata: '+errmsg)
|
||||||
total_results = int(book_list['total_results'])
|
total_results = int(book_list['total_results'])
|
||||||
np = '&page_number=%s&'%(page_number+1)
|
page_number += 1
|
||||||
|
np = '&page_number=%s&'%page_number
|
||||||
url = re.sub(r'\&page_number=\d+\&', np, url)
|
url = re.sub(r'\&page_number=\d+\&', np, url)
|
||||||
books.extend(book_list.findAll('bookdata'))
|
books.extend(book_list.findAll('bookdata'))
|
||||||
max -= 1
|
max -= 1
|
||||||
|
@ -7,6 +7,7 @@ Fetch cover from LibraryThing.com based on ISBN number.
|
|||||||
import sys, socket, os, re
|
import sys, socket, os, re
|
||||||
|
|
||||||
from lxml import html
|
from lxml import html
|
||||||
|
import mechanize
|
||||||
|
|
||||||
from calibre import browser, prints
|
from calibre import browser, prints
|
||||||
from calibre.utils.config import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
@ -14,11 +15,17 @@ from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
|||||||
|
|
||||||
OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false'
|
OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false'
|
||||||
|
|
||||||
|
class HeadRequest(mechanize.Request):
|
||||||
|
|
||||||
|
def get_method(self):
|
||||||
|
return 'HEAD'
|
||||||
|
|
||||||
def check_for_cover(isbn, timeout=5.):
|
def check_for_cover(isbn, timeout=5.):
|
||||||
br = browser()
|
br = browser()
|
||||||
br.set_handle_redirect(False)
|
br.set_handle_redirect(False)
|
||||||
try:
|
try:
|
||||||
br.open_novisit(OPENLIBRARY%isbn, timeout=timeout)
|
br.open_novisit(HeadRequest(OPENLIBRARY%isbn), timeout=timeout)
|
||||||
|
return True
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
if callable(getattr(e, 'getcode', None)) and e.getcode() == 302:
|
if callable(getattr(e, 'getcode', None)) and e.getcode() == 302:
|
||||||
return True
|
return True
|
||||||
|
@ -7,7 +7,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
|
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, re, uuid, logging, functools
|
import os, re, uuid, logging
|
||||||
from mimetypes import types_map
|
from mimetypes import types_map
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from itertools import count
|
from itertools import count
|
||||||
@ -808,17 +808,17 @@ class Manifest(object):
|
|||||||
pat = re.compile(r'&(%s);'%('|'.join(user_entities.keys())))
|
pat = re.compile(r'&(%s);'%('|'.join(user_entities.keys())))
|
||||||
data = pat.sub(lambda m:user_entities[m.group(1)], data)
|
data = pat.sub(lambda m:user_entities[m.group(1)], data)
|
||||||
|
|
||||||
fromstring = functools.partial(etree.fromstring, parser=RECOVER_PARSER)
|
parser = etree.XMLParser(no_network=True, huge_tree=True)
|
||||||
# Try with more & more drastic measures to parse
|
# Try with more & more drastic measures to parse
|
||||||
def first_pass(data):
|
def first_pass(data):
|
||||||
try:
|
try:
|
||||||
data = fromstring(data)
|
data = etree.fromstring(data, parser=parser)
|
||||||
except etree.XMLSyntaxError, err:
|
except etree.XMLSyntaxError, err:
|
||||||
self.oeb.log.exception('Initial parse failed:')
|
self.oeb.log.exception('Initial parse failed:')
|
||||||
repl = lambda m: ENTITYDEFS.get(m.group(1), m.group(0))
|
repl = lambda m: ENTITYDEFS.get(m.group(1), m.group(0))
|
||||||
data = ENTITY_RE.sub(repl, data)
|
data = ENTITY_RE.sub(repl, data)
|
||||||
try:
|
try:
|
||||||
data = fromstring(data)
|
data = etree.fromstring(data, parser=parser)
|
||||||
except etree.XMLSyntaxError, err:
|
except etree.XMLSyntaxError, err:
|
||||||
self.oeb.logger.warn('Parsing file %r as HTML' % self.href)
|
self.oeb.logger.warn('Parsing file %r as HTML' % self.href)
|
||||||
if err.args and err.args[0].startswith('Excessive depth'):
|
if err.args and err.args[0].startswith('Excessive depth'):
|
||||||
@ -832,9 +832,9 @@ class Manifest(object):
|
|||||||
elem.text = elem.text.strip('-')
|
elem.text = elem.text.strip('-')
|
||||||
data = etree.tostring(data, encoding=unicode)
|
data = etree.tostring(data, encoding=unicode)
|
||||||
try:
|
try:
|
||||||
data = fromstring(data)
|
data = etree.fromstring(data, parser=parser)
|
||||||
except etree.XMLSyntaxError:
|
except etree.XMLSyntaxError:
|
||||||
data = fromstring(data)
|
data = etree.fromstring(data, parser=RECOVER_PARSER)
|
||||||
return data
|
return data
|
||||||
data = first_pass(data)
|
data = first_pass(data)
|
||||||
|
|
||||||
@ -866,12 +866,12 @@ class Manifest(object):
|
|||||||
data = etree.tostring(data, encoding=unicode)
|
data = etree.tostring(data, encoding=unicode)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = fromstring(data)
|
data = etree.fromstring(data, parser=parser)
|
||||||
except:
|
except:
|
||||||
data = data.replace(':=', '=').replace(':>', '>')
|
data = data.replace(':=', '=').replace(':>', '>')
|
||||||
data = data.replace('<http:/>', '')
|
data = data.replace('<http:/>', '')
|
||||||
try:
|
try:
|
||||||
data = fromstring(data)
|
data = etree.fromstring(data, parser=parser)
|
||||||
except etree.XMLSyntaxError:
|
except etree.XMLSyntaxError:
|
||||||
self.oeb.logger.warn('Stripping comments and meta tags from %s'%
|
self.oeb.logger.warn('Stripping comments and meta tags from %s'%
|
||||||
self.href)
|
self.href)
|
||||||
@ -882,7 +882,7 @@ class Manifest(object):
|
|||||||
"<?xml version='1.0' encoding='utf-8'?><o:p></o:p>",
|
"<?xml version='1.0' encoding='utf-8'?><o:p></o:p>",
|
||||||
'')
|
'')
|
||||||
data = data.replace("<?xml version='1.0' encoding='utf-8'??>", '')
|
data = data.replace("<?xml version='1.0' encoding='utf-8'??>", '')
|
||||||
data = fromstring(data)
|
data = etree.fromstring(data)
|
||||||
elif namespace(data.tag) != XHTML_NS:
|
elif namespace(data.tag) != XHTML_NS:
|
||||||
# OEB_DOC_NS, but possibly others
|
# OEB_DOC_NS, but possibly others
|
||||||
ns = namespace(data.tag)
|
ns = namespace(data.tag)
|
||||||
|
@ -248,7 +248,7 @@ def info_dialog(parent, title, msg, det_msg='', show=False):
|
|||||||
|
|
||||||
class Dispatcher(QObject):
|
class Dispatcher(QObject):
|
||||||
'''Convenience class to ensure that a function call always happens in the
|
'''Convenience class to ensure that a function call always happens in the
|
||||||
thread the reciver was created in.'''
|
thread the receiver was created in.'''
|
||||||
dispatch_signal = pyqtSignal(object, object)
|
dispatch_signal = pyqtSignal(object, object)
|
||||||
|
|
||||||
def __init__(self, func):
|
def __init__(self, func):
|
||||||
@ -507,7 +507,7 @@ def pixmap_to_data(pixmap, format='JPEG'):
|
|||||||
buf = QBuffer(ba)
|
buf = QBuffer(ba)
|
||||||
buf.open(QBuffer.WriteOnly)
|
buf.open(QBuffer.WriteOnly)
|
||||||
pixmap.save(buf, format)
|
pixmap.save(buf, format)
|
||||||
return str(ba.data())
|
return bytes(ba.data())
|
||||||
|
|
||||||
class ResizableDialog(QDialog):
|
class ResizableDialog(QDialog):
|
||||||
|
|
||||||
|
@ -173,6 +173,7 @@ class Label(QLabel):
|
|||||||
self.setTextFormat(Qt.RichText)
|
self.setTextFormat(Qt.RichText)
|
||||||
self.setText('')
|
self.setText('')
|
||||||
self.setWordWrap(True)
|
self.setWordWrap(True)
|
||||||
|
self.setAlignment(Qt.AlignTop)
|
||||||
self.linkActivated.connect(self.link_activated)
|
self.linkActivated.connect(self.link_activated)
|
||||||
self._link_clicked = False
|
self._link_clicked = False
|
||||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
@ -205,15 +206,15 @@ class BookInfo(QScrollArea):
|
|||||||
rows = render_rows(data)
|
rows = render_rows(data)
|
||||||
rows = u'\n'.join([u'<tr><td valign="top"><b>%s:</b></td><td valign="top">%s</td></tr>'%(k,t) for
|
rows = u'\n'.join([u'<tr><td valign="top"><b>%s:</b></td><td valign="top">%s</td></tr>'%(k,t) for
|
||||||
k, t in rows])
|
k, t in rows])
|
||||||
|
comments = ''
|
||||||
|
if data.get(_('Comments'), '') not in ('', u'None'):
|
||||||
|
comments = data[_('Comments')]
|
||||||
|
comments = comments_to_html(comments)
|
||||||
if self.vertical:
|
if self.vertical:
|
||||||
if _('Comments') in data and data[_('Comments')]:
|
if comments:
|
||||||
comments = comments_to_html(data[_('Comments')])
|
|
||||||
rows += u'<tr><td colspan="2">%s</td></tr>'%comments
|
rows += u'<tr><td colspan="2">%s</td></tr>'%comments
|
||||||
self.label.setText(u'<table>%s</table>'%rows)
|
self.label.setText(u'<table>%s</table>'%rows)
|
||||||
else:
|
else:
|
||||||
comments = ''
|
|
||||||
if _('Comments') in data:
|
|
||||||
comments = comments_to_html(data[_('Comments')])
|
|
||||||
left_pane = u'<table>%s</table>'%rows
|
left_pane = u'<table>%s</table>'%rows
|
||||||
right_pane = u'<div>%s</div>'%comments
|
right_pane = u'<div>%s</div>'%comments
|
||||||
self.label.setText(u'<table><tr><td valign="top" '
|
self.label.setText(u'<table><tr><td valign="top" '
|
||||||
|
@ -325,7 +325,7 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
|
|||||||
# Avoid problems with multi-line widgets
|
# Avoid problems with multi-line widgets
|
||||||
turnover_point = count_non_comment + 1000
|
turnover_point = count_non_comment + 1000
|
||||||
ans = []
|
ans = []
|
||||||
column = row = 0
|
column = row = comments_row = 0
|
||||||
for col in cols:
|
for col in cols:
|
||||||
dt = x[col]['datatype']
|
dt = x[col]['datatype']
|
||||||
if dt == 'comments':
|
if dt == 'comments':
|
||||||
@ -337,11 +337,13 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
|
|||||||
layout.addWidget(w.widgets[c], row, column)
|
layout.addWidget(w.widgets[c], row, column)
|
||||||
layout.addWidget(w.widgets[c+1], row, column+1)
|
layout.addWidget(w.widgets[c+1], row, column+1)
|
||||||
row += 1
|
row += 1
|
||||||
|
comments_row = max(comments_row, row)
|
||||||
if row >= turnover_point:
|
if row >= turnover_point:
|
||||||
column += 2
|
column += 2
|
||||||
turnover_point = count_non_comment + 1000
|
turnover_point = count_non_comment + 1000
|
||||||
row = 0
|
row = 0
|
||||||
if not bulk: # Add the comments fields
|
if not bulk: # Add the comments fields
|
||||||
|
row = comments_row
|
||||||
column = 0
|
column = 0
|
||||||
for col in cols:
|
for col in cols:
|
||||||
dt = x[col]['datatype']
|
dt = x[col]['datatype']
|
||||||
|
@ -90,7 +90,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
COVER_FETCH_TIMEOUT = 240 # seconds
|
COVER_FETCH_TIMEOUT = 240 # seconds
|
||||||
|
|
||||||
def do_reset_cover(self, *args):
|
def do_reset_cover(self, *args):
|
||||||
pix = QPixmap(I('book.svg'))
|
pix = QPixmap(I('default_cover.svg'))
|
||||||
self.cover.setPixmap(pix)
|
self.cover.setPixmap(pix)
|
||||||
self.cover_changed = True
|
self.cover_changed = True
|
||||||
self.cover_data = None
|
self.cover_data = None
|
||||||
@ -408,7 +408,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
if cover:
|
if cover:
|
||||||
pm.loadFromData(cover)
|
pm.loadFromData(cover)
|
||||||
if pm.isNull():
|
if pm.isNull():
|
||||||
pm = QPixmap(I('book.svg'))
|
pm = QPixmap(I('default_cover.svg'))
|
||||||
else:
|
else:
|
||||||
self.cover_data = cover
|
self.cover_data = cover
|
||||||
self.cover.setPixmap(pm)
|
self.cover.setPixmap(pm)
|
||||||
|
@ -56,7 +56,8 @@ class ToolbarMixin(object): # {{{
|
|||||||
partial(self.edit_metadata, False, bulk=True))
|
partial(self.edit_metadata, False, bulk=True))
|
||||||
md.addSeparator()
|
md.addSeparator()
|
||||||
md.addAction(_('Download metadata and covers'),
|
md.addAction(_('Download metadata and covers'),
|
||||||
partial(self.download_metadata, False, covers=True))
|
partial(self.download_metadata, False, covers=True),
|
||||||
|
Qt.ControlModifier+Qt.Key_D)
|
||||||
md.addAction(_('Download only metadata'),
|
md.addAction(_('Download only metadata'),
|
||||||
partial(self.download_metadata, False, covers=False))
|
partial(self.download_metadata, False, covers=False))
|
||||||
md.addAction(_('Download only covers'),
|
md.addAction(_('Download only covers'),
|
||||||
|
@ -43,6 +43,14 @@ class FormatPath(unicode):
|
|||||||
ans.deleted_after_upload = False
|
ans.deleted_after_upload = False
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
_default_image = None
|
||||||
|
|
||||||
|
def default_image():
|
||||||
|
global _default_image
|
||||||
|
if _default_image is None:
|
||||||
|
_default_image = QImage(I('default_cover.svg'))
|
||||||
|
return _default_image
|
||||||
|
|
||||||
class BooksModel(QAbstractTableModel): # {{{
|
class BooksModel(QAbstractTableModel): # {{{
|
||||||
|
|
||||||
about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted')
|
about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted')
|
||||||
@ -71,7 +79,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
self.book_on_device = None
|
self.book_on_device = None
|
||||||
self.editable_cols = ['title', 'authors', 'rating', 'publisher',
|
self.editable_cols = ['title', 'authors', 'rating', 'publisher',
|
||||||
'tags', 'series', 'timestamp', 'pubdate']
|
'tags', 'series', 'timestamp', 'pubdate']
|
||||||
self.default_image = QImage(I('book.svg'))
|
self.default_image = default_image()
|
||||||
self.sorted_on = DEFAULT_SORT
|
self.sorted_on = DEFAULT_SORT
|
||||||
self.sort_history = [self.sorted_on]
|
self.sort_history = [self.sorted_on]
|
||||||
self.last_search = '' # The last search performed on this model
|
self.last_search = '' # The last search performed on this model
|
||||||
|
@ -56,6 +56,8 @@ def init_qt(args):
|
|||||||
|
|
||||||
def get_default_library_path():
|
def get_default_library_path():
|
||||||
fname = _('Calibre Library')
|
fname = _('Calibre Library')
|
||||||
|
if iswindows:
|
||||||
|
fname = 'Calibre Library'
|
||||||
if isinstance(fname, unicode):
|
if isinstance(fname, unicode):
|
||||||
try:
|
try:
|
||||||
fname = fname.encode(filesystem_encoding)
|
fname = fname.encode(filesystem_encoding)
|
||||||
|
@ -579,7 +579,7 @@ void PictureFlowPrivate::resetSlides()
|
|||||||
static QImage prepareSurface(QImage img, int w, int h)
|
static QImage prepareSurface(QImage img, int w, int h)
|
||||||
{
|
{
|
||||||
Qt::TransformationMode mode = Qt::SmoothTransformation;
|
Qt::TransformationMode mode = Qt::SmoothTransformation;
|
||||||
img = img.scaled(w, h, Qt::KeepAspectRatioByExpanding, mode);
|
img = img.scaled(w, h, Qt::IgnoreAspectRatio, mode);
|
||||||
|
|
||||||
// slightly larger, to accommodate for the reflection
|
// slightly larger, to accommodate for the reflection
|
||||||
int hs = int(h * REFLECTION_FACTOR);
|
int hs = int(h * REFLECTION_FACTOR);
|
||||||
|
@ -39,6 +39,11 @@ def comments_to_html(comments):
|
|||||||
if not isinstance(comments, unicode):
|
if not isinstance(comments, unicode):
|
||||||
comments = comments.decode(preferred_encoding, 'replace')
|
comments = comments.decode(preferred_encoding, 'replace')
|
||||||
|
|
||||||
|
if '<' not in comments:
|
||||||
|
comments = prepare_string_for_xml(comments)
|
||||||
|
comments = comments.replace(u'\n', u'<br />')
|
||||||
|
return u'<p>%s</p>'%comments
|
||||||
|
|
||||||
# Hackish - ignoring sentences ending or beginning in numbers to avoid
|
# Hackish - ignoring sentences ending or beginning in numbers to avoid
|
||||||
# confusion with decimal points.
|
# confusion with decimal points.
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.dbpath = os.path.join(library_path, 'metadata.db')
|
self.dbpath = os.path.join(library_path, 'metadata.db')
|
||||||
self.dbpath = os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH',
|
self.dbpath = os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH',
|
||||||
self.dbpath)
|
self.dbpath)
|
||||||
if isinstance(self.dbpath, unicode):
|
if isinstance(self.dbpath, unicode) and not iswindows:
|
||||||
self.dbpath = self.dbpath.encode(filesystem_encoding)
|
self.dbpath = self.dbpath.encode(filesystem_encoding)
|
||||||
|
|
||||||
self.connect()
|
self.connect()
|
||||||
|
@ -539,17 +539,20 @@ MIME = '''\
|
|||||||
</mime-info>
|
</mime-info>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def render_svg(image, dest):
|
def render_svg(image, dest, width=128, height=128):
|
||||||
from PyQt4.QtGui import QPainter, QImage
|
from PyQt4.QtGui import QPainter, QImage
|
||||||
from PyQt4.QtSvg import QSvgRenderer
|
from PyQt4.QtSvg import QSvgRenderer
|
||||||
svg = QSvgRenderer(image.readAll())
|
image = image.readAll() if hasattr(image, 'readAll') else image
|
||||||
|
svg = QSvgRenderer(image)
|
||||||
painter = QPainter()
|
painter = QPainter()
|
||||||
image = QImage(128,128,QImage.Format_ARGB32_Premultiplied)
|
image = QImage(width, height, QImage.Format_ARGB32)
|
||||||
painter.begin(image)
|
painter.begin(image)
|
||||||
painter.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform|QPainter.HighQualityAntialiasing)
|
painter.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform|QPainter.HighQualityAntialiasing)
|
||||||
painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
|
painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
|
||||||
svg.render(painter)
|
svg.render(painter)
|
||||||
painter.end()
|
painter.end()
|
||||||
|
if dest is None:
|
||||||
|
return image
|
||||||
image.save(dest)
|
image.save(dest)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -15,19 +15,23 @@ if _dev_path is not None:
|
|||||||
if not os.path.exists(_dev_path):
|
if not os.path.exists(_dev_path):
|
||||||
_dev_path = None
|
_dev_path = None
|
||||||
|
|
||||||
|
_path_cache = {}
|
||||||
|
|
||||||
def get_path(path, data=False):
|
def get_path(path, data=False):
|
||||||
global _dev_path
|
global _dev_path
|
||||||
path = path.replace(os.sep, '/')
|
path = path.replace(os.sep, '/')
|
||||||
base = None
|
base = sys.resources_location
|
||||||
if _dev_path is not None:
|
if _dev_path is not None:
|
||||||
|
if path in _path_cache:
|
||||||
|
return _path_cache[path]
|
||||||
if os.path.exists(os.path.join(_dev_path, *path.split('/'))):
|
if os.path.exists(os.path.join(_dev_path, *path.split('/'))):
|
||||||
base = _dev_path
|
base = _dev_path
|
||||||
if base is None:
|
fpath = os.path.join(base, *path.split('/'))
|
||||||
base = sys.resources_location
|
if _dev_path is not None:
|
||||||
path = os.path.join(base, *path.split('/'))
|
_path_cache[path] = fpath
|
||||||
if data:
|
if data:
|
||||||
return open(path, 'rb').read()
|
return open(fpath, 'rb').read()
|
||||||
return path
|
return fpath
|
||||||
|
|
||||||
def get_image_path(path, data=False):
|
def get_image_path(path, data=False):
|
||||||
return get_path('images/'+path, data=data)
|
return get_path('images/'+path, data=data)
|
||||||
|
@ -96,19 +96,21 @@ class NewsItem(NewsTreeItem):
|
|||||||
builtin, custom, scheduler_config, parent):
|
builtin, custom, scheduler_config, parent):
|
||||||
NewsTreeItem.__init__(self, builtin, custom, scheduler_config, parent)
|
NewsTreeItem.__init__(self, builtin, custom, scheduler_config, parent)
|
||||||
self.urn, self.title = urn, title
|
self.urn, self.title = urn, title
|
||||||
|
self.icon = self.default_icon = None
|
||||||
|
self.default_icon = default_icon
|
||||||
if 'custom:' in self.urn:
|
if 'custom:' in self.urn:
|
||||||
self.icon = custom_icon
|
self.icon = custom_icon
|
||||||
else:
|
|
||||||
icon = I('news/%s.png'%self.urn[8:])
|
|
||||||
if os.path.exists(icon):
|
|
||||||
self.icon = QVariant(QIcon(icon))
|
|
||||||
else:
|
|
||||||
self.icon = default_icon
|
|
||||||
|
|
||||||
def data(self, role):
|
def data(self, role):
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
return QVariant(self.title)
|
return QVariant(self.title)
|
||||||
if role == Qt.DecorationRole:
|
if role == Qt.DecorationRole:
|
||||||
|
if self.icon is None:
|
||||||
|
icon = I('news/%s.png'%self.urn[8:])
|
||||||
|
if os.path.exists(icon):
|
||||||
|
self.icon = QVariant(QIcon(icon))
|
||||||
|
else:
|
||||||
|
self.icon = self.default_icon
|
||||||
return self.icon
|
return self.icon
|
||||||
return NONE
|
return NONE
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ class RecursiveFetcher(object):
|
|||||||
|
|
||||||
replace = self.prepreprocess_html_ext(soup)
|
replace = self.prepreprocess_html_ext(soup)
|
||||||
if replace is not None:
|
if replace is not None:
|
||||||
soup = BeautifulSoup(xml_to_unicode(src, self.verbose, strip_encoding_pats=True)[0], markupMassage=nmassage)
|
soup = BeautifulSoup(xml_to_unicode(replace, self.verbose, strip_encoding_pats=True)[0], markupMassage=nmassage)
|
||||||
|
|
||||||
if self.keep_only_tags:
|
if self.keep_only_tags:
|
||||||
body = Tag(soup, 'body')
|
body = Tag(soup, 'body')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user