Merge from trunk

This commit is contained in:
Charles Haley 2010-06-12 10:58:43 +01:00
commit 615f9b8e8f
43 changed files with 25037 additions and 11605 deletions

View File

@ -4,6 +4,65 @@
# for important features/bug fixes.
# Also, each release can have new and improved recipes.
- version: 0.7.2
date: 2010-06-11
new features:
- title: "The Cover Browser can now be freely resized."
description: >
"You can now resize the Cover Browser just like the other areas of the user interface by dragging the edge. The Cover Browser now
also emphasizes the cetral book cover, making it larger than the others. Also on widescreen monitors the cover browser is now automatically placed
to the side of the book list instead of below it."
- title: "Added tweak to control how titles and series names are sorted"
- title: "Clicking on row numbers no longer open the viewer. Instead you have to double click"
bug fixes:
- title: "SONY driver: The regression causing slow perfomance has been corrected. Various bug fixes to deal with corner cases"
- title: "iPad driver: Various bugfixes, should now work much more seamlessly."
- title: "Fix regression causing calibre to not start if the library path is invalid, because say a drive has been removed"
tickets: [5787]
- title: "Fix regression in 0.7.1 that broke searching in the e-book viewers and for news sources"
- title: "Fix regressions that caused the Publisher to change when updating Series and floating point custom columns to be rounded to integers."
tickets: [5788]
- title: "Fix regression that broke check database integrity in the presence of custom coulmns or user categories"
tickets: [5779]
- title: "Fix regresison that broke the Email to submenu in the send to device menu"
- title: "Fix Tag browser re-opening closed tree after editing metadata"
tickets: [5744]
- title: "Conversion pipeline: Handle missing/obsolete input/output profiles gracefully"
- title: "Fix Adding/Deleting Search does not refresh Left Pane Correctly"
tickets: [5751]
- title: "Content server: Fix serving of CBZ/CBR files to stanza"
new recipes:
- title: Repantes, Haaretz
author: Darko Miletic
- title: CBC Canada
author: rty
improved recipes:
- The Sun
- The Economist
- Boston Globe
- Honolulu Star Advertiser
- SMH
- Sueddeutsche
- Our Daily Bread
- version: 0.7.1
date: 2010-06-04

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 B

After

Width:  |  Height:  |  Size: 983 B

View File

@ -1,5 +1,5 @@
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
'''
www.boston.com
'''
@ -7,10 +7,10 @@ www.boston.com
from calibre.web.feeds.recipes import BasicNewsRecipe
class BusinessStandard(BasicNewsRecipe):
title = 'Boston'
title = 'The Boston Globe'
__author__ = 'Darko Miletic'
description = 'News from Boston'
oldest_article = 7
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
delay = 1
@ -19,6 +19,9 @@ class BusinessStandard(BasicNewsRecipe):
publisher = 'Boston'
category = 'news, boston, usa, world'
language = 'en'
publication_type = 'newspaper'
masthead_url = 'http://cache.boston.com/images/globe/grslider/the_boston_globe.gif'
extra_css = ' body{font-family: Georgia, serif} div#articleBodyTop{display:block} '
conversion_options = {
'comments' : description
@ -27,8 +30,11 @@ class BusinessStandard(BasicNewsRecipe):
,'publisher' : publisher
}
keep_only_tags = [dict(name='div', attrs={'class':'story'})]
remove_tags = [dict(name=['object','link','script','iframe'])]
keep_only_tags = [dict(attrs={'id':['INDblogEntry','blogEntry','articleHeader','articleGraphs','galleryShell']})]
remove_tags = [
dict(name=['object','link','script','iframe'])
,dict(attrs={'id':['blogheadTools','bdc_emailWidget','tools','relatedContent']})
]
feeds = [
(u'Top Stories' , u'http://feeds.boston.com/boston/topstories' )
@ -38,12 +44,9 @@ class BusinessStandard(BasicNewsRecipe):
]
def print_version(self, url):
return url + '?mode=PF'
return url + '?page=full'
def get_article_url(self, article):
rawarticle = article.get('pheedo_origlink', None)
artls, sep, rsep = rawarticle.rpartition('/?')
if artls == '':
artls = rawarticle.rpartition('?')[0]
return artls
rawarticle = article.get('guid', None)
return rawarticle.rpartition('?')[0]

View File

@ -88,7 +88,9 @@ class Economist(BasicNewsRecipe):
continue
a = tag.find('a', href=True)
if a is not None:
url=a['href'].split('?')[0]+'/print'
url=a['href']
id_ = re.search(r'story_id=(\d+)', url).group(1)
url = 'http://www.economist.com/node/%s/print'%id_
if url.startswith('Printer'):
url = '/'+url
if url.startswith('/'):

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python
# -*- coding: cp1252 -*-
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
honoluluadvertiser.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Honoluluadvertiser(BasicNewsRecipe):
title = 'Honolulu Advertiser'
__author__ = 'Darko Miletic and Sujata Raman'
description = "Latest national and local Hawaii sports news from The Honolulu Advertiser."
publisher = 'Honolulu Advertiser'
category = 'news, Honolulu, Hawaii'
oldest_article = 2
language = 'en'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
remove_javascript = True
cover_url = 'http://www.honoluluadvertiser.com/graphics/frontpage/frontpage.jpg'
html2lrf_options = [
'--comment' , description
, '--category' , category
, '--publisher' , publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
keep_only_tags = [dict(name='div', attrs={'class':["hon_article_top","article-bodytext","hon_article_photo","storyphoto","article"]}),
dict(name='div', attrs={'id':["storycontentleft","article"]})
]
remove_tags = [dict(name=['object','link','embed']),
dict(name='div', attrs={'class':["article-tools","titleBar","invisiblespacer","articleflex-container","hon_newslist","categoryheader","columnframe","subHeadline","poster-container"]}),
dict(name='div', attrs={'align':["right"]}),
dict(name='div', attrs={'id':["pluckcomments"]}),
dict(name='td', attrs={'class':["prepsfacts"]}),
dict(name='img', attrs={'height':["1"]}),
dict(name='img', attrs={'alt':["Advertisement"]}),
dict(name='img', attrs={'src':["/gcicommonfiles/sr/graphics/common/adlabel_horz.gif","/gcicommonfiles/sr/graphics/common/icon_whatsthis.gif",]}),
]
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-size:large; color:#000000; }
.hon_article_timestamp{font-family:Arial,Helvetica,sans-serif; font-size:70%; }
.postedStoryDate{font-family:Arial,Helvetica,sans-serif; font-size:30%; }
.postedDate{font-family:Arial,Helvetica,sans-serif; font-size:30%; }
.credit{font-family:Arial,Helvetica,sans-serif; font-size:30%; }
.hon_article_top{font-family:Arial,Helvetica,sans-serif; color:#666666; font-size:30%; font-weight:bold;}
.grayBackground{font-family:Arial,Helvetica,sans-serif; color:#666666; font-size:30%;}
.hon_photocaption{font-family:Arial,Helvetica,sans-serif; font-size:30%; }
.photoCaption{font-family:Arial,Helvetica,sans-serif; font-size:30%; }
.hon_photocredit{font-family:Arial,Helvetica,sans-serif; font-size:30%; color:#666666;}
.storyphoto{font-family:Arial,Helvetica,sans-serif; font-size:30%; color:#666666;}
.article-bodytext{font-family:Arial,Helvetica,sans-serif; font-size:xx-small; }
.storycontentleft{font-family:Arial,Helvetica,sans-serif; font-size:xx-small; }
#article{font-family:Arial,Helvetica,sans-serif; font-size:xx-small; }
.contentarea{font-family:Arial,Helvetica,sans-serif; font-size:xx-small; }
.storytext{font-family:Verdana,Arial,Helvetica,sans-serif; font-size:xx-small;}
.storyHeadline{font-family:Arial,Helvetica,sans-serif; font-size:large; color:#000000; font-weight:bold;}
.source{font-family:Arial,Helvetica,sans-serif; color:#333333; font-style: italic; font-weight:bold; }
'''
feeds = [
(u'Breaking news', u'http://www.honoluluadvertiser.com/apps/pbcs.dll/section?Category=RSS01&MIME=XML' )
,(u'Local news', u'http://www.honoluluadvertiser.com/apps/pbcs.dll/section?Category=RSS02&MIME=XML' )
,(u'Sports', u'http://www.honoluluadvertiser.com/apps/pbcs.dll/section?Category=RSS03&MIME=XML' )
,(u'Island life', u'http://www.honoluluadvertiser.com/apps/pbcs.dll/section?Category=RSS05&MIME=XML' )
,(u'Entertainment', u'http://www.honoluluadvertiser.com/apps/pbcs.dll/section?Category=RSS06&MIME=XML' )
,(u'Business', u'http://www.honoluluadvertiser.com/apps/pbcs.dll/section?Category=RSS04&MIME=XML' )
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
mtag = '\n<meta http-equiv="Content-Language" content="en"/>\n'
soup.head.insert(0,mtag)
for tag in soup.findAll(name=['span','table','font']):
tag.name = 'div'
return soup
# def print_version(self, url):
# ubody, sep, rest = url.rpartition('/-1/')
# root, sep2, article_id = ubody.partition('/article/')
# return u'http://www.honoluluadvertiser.com/apps/pbcs.dll/article?AID=/' + article_id + '&template=printart'

View File

@ -51,6 +51,7 @@ class LeMondeDiplomatiqueEn(BasicNewsRecipe):
, dict(name='div',attrs={'class':'notes surlignable'})
]
remove_tags = [dict(name=['object','link','script','iframe','base'])]
remove_attributes = ['height','width']
def parse_index(self):
articles = []
@ -72,5 +73,5 @@ class LeMondeDiplomatiqueEn(BasicNewsRecipe):
,'url' :url
,'description':description
})
return [(soup.head.title.string, articles)]
return [(self.title, articles)]

View File

@ -0,0 +1,47 @@
__license__ = 'GPL v3'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
'''
staradvertiser.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Starbulletin(BasicNewsRecipe):
title = 'Honolulu Star Advertiser'
__author__ = 'Darko Miletic'
description = "Latest national and local Hawaii sports news"
publisher = 'Honolulu Star-Advertiser'
category = 'news, Honolulu, Hawaii'
oldest_article = 2
max_articles_per_feed = 100
language = 'en'
no_stylesheets = True
use_embedded_content = False
encoding = 'utf8'
publication_type = 'newspaper'
extra_css = ' body{font-family: Verdana,Arial,Helvetica,sans-serif} h1,.brown,.postCredit{color: #663300} .storyDeck{font-size: 1.2em; font-weight: bold} '
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
, 'linearize_tables' : True
}
remove_tags_before = dict(attrs={'id':'storyTitle'})
remove_tags_after = dict(name='div', attrs={'class':'storytext'})
remove_tags = [
dict(name=['object','link'])
,dict(attrs={'class':'insideStoryImage'})
]
feeds = [
(u'Headlines' , u'http://www.staradvertiser.com/staradvertiser_headlines.rss' )
,(u'News' , u'http://www.staradvertiser.com/news/index.rss' )
,(u'Sports' , u'http://www.staradvertiser.com/sports/index.rss' )
,(u'Features' , u'http://www.staradvertiser.com/features/index.rss' )
,(u'Editorials', u'http://www.staradvertiser.com/editorials/index.rss' )
,(u'Business' , u'http://www.staradvertiser.com/business/index.rss' )
,(u'Travel' , u'http://www.staradvertiser.com/travel/index.rss' )
]

View File

@ -1,60 +0,0 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
starbulletin.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Starbulletin(BasicNewsRecipe):
title = 'Honolulu Star-Bulletin'
__author__ = 'Darko Miletic'
description = "Latest national and local Hawaii sports news"
publisher = 'Honolulu Star-Bulletin'
category = 'news, Honolulu, Hawaii'
oldest_article = 2
max_articles_per_feed = 100
language = 'en'
no_stylesheets = True
use_embedded_content = False
encoding = 'utf8'
remove_javascript = True
cover_url = 'http://media.starbulletin.com/designimages/spacer.gif'
html2lrf_options = [
'--comment' , description
, '--category' , category
, '--publisher' , publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
keep_only_tags = [ dict(name='div', attrs={'id':'storyColoumn'}) ]
remove_tags = [
dict(name=['object','link'])
,dict(name='span', attrs={'id':'printdesc'})
,dict(name='div' , attrs={'class':'lightGreyBox storyTools clearAll'})
,dict(name='div' , attrs={'id':'breadcrumbs'})
]
feeds = [
(u'Headlines', u'http://www.starbulletin.com/starbulletin_headlines.rss' )
,(u'News', u'http://www.starbulletin.com/news/index.rss' )
,(u'Sports', u'http://www.starbulletin.com/sports/index.rss' )
,(u'Features', u'http://www.starbulletin.com/features/index.rss' )
,(u'Editorials', u'http://www.starbulletin.com/editorials/index.rss' )
,(u'Business', u'http://www.starbulletin.com/business/index.rss' )
,(u'Travel', u'http://www.starbulletin.com/travel/index.rss' )
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
mtag = '\n<meta http-equiv="Content-Language" content="en"/>\n'
soup.head.insert(0,mtag)
return soup

View File

@ -1,5 +1,6 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag
class AdvancedUserRecipe1268409464(BasicNewsRecipe):
title = u'The Sun'
@ -14,24 +15,27 @@ class AdvancedUserRecipe1268409464(BasicNewsRecipe):
remove_javascript = True
keep_only_tags = [
dict(name='div', attrs={'class':'medium-centered'})
,dict(name='div', attrs={'class':'article'})
,dict(name='div', attrs={'class':'clear-left'})
,dict(name='div', attrs={'class':'text-center'})
dict(id='column-print')
]
remove_tags = [
dict(name='div', attrs={'class':'slideshow'})
,dict(name='div', attrs={'class':'float-left'})
,dict(name='div', attrs={'class':'ltbx-slideshow ltbx-btn-ss'})
,dict(name='a', attrs={'class':'add_a_comment'})
,dict(name='div', attrs={'id':'vxFlashPlayerContent'})
,dict(name='div', attrs={'id':'k1006094r1c1t5w380h529'})
,dict(name='div', attrs={'id':'tum_login_form_container'})
,dict(name='div', attrs={'class':'discHeader'})
,dict(name='div', attrs={'class':'margin-bottom-neg-2'})
dict(name='div', attrs={'class':[
'clear text-center small padding-left-right-5 text-999 padding-top-5 padding-bottom-10 grey-solid-line',
'clear width-625 bg-fff padding-top-10'
]}),
dict(name='video'),
]
def preprocess_html(self, soup):
h1 = soup.find('h1')
if h1 is not None:
text = self.tag_to_string(h1)
nh = Tag(soup, 'h1')
nh.insert(0, text)
h1.replaceWith(nh)
return soup
feeds = [(u'News', u'http://www.thesun.co.uk/sol/homepage/feeds/rss/article312900.ece')
,(u'Sport', u'http://www.thesun.co.uk/sol/homepage/feeds/rss/article247732.ece')

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
__version__ = '0.7.1'
__version__ = '0.7.2'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re

View File

@ -445,7 +445,7 @@ from calibre.devices.nook.driver import NOOK
from calibre.devices.prs505.driver import PRS505
from calibre.devices.android.driver import ANDROID, S60
from calibre.devices.nokia.driver import N770, N810, E71X
from calibre.devices.eslick.driver import ESLICK
from calibre.devices.eslick.driver import ESLICK, EBK52
from calibre.devices.nuut2.driver import NUUT2
from calibre.devices.iriver.driver import IRIVER_STORY
from calibre.devices.binatone.driver import README
@ -519,6 +519,7 @@ plugins += [
N810,
COOL_ER,
ESLICK,
EBK52,
NUUT2,
IRIVER_STORY,
GER2,

View File

@ -14,7 +14,6 @@ from calibre.devices.interface import DevicePlugin
from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.ebooks.metadata import MetaInformation
from calibre.library.server.utils import strftime
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import Config, config_dir
from calibre.utils.date import parse_date
from calibre.utils.logging import Log
@ -147,6 +146,7 @@ class ITUNES(DevicePlugin):
ejected = False
iTunes= None
iTunes_media = None
library_orphans = None
log = Log()
manual_sync_mode = False
path_template = 'iTunes/%s - %s.epub'
@ -244,14 +244,13 @@ class ITUNES(DevicePlugin):
# Fetch a list of books from iPod device connected to iTunes
# Fetch Library|Books
library_books = self._get_library_books()
if 'iPod' in self.sources:
booklist = BookList(self.log)
cached_books = {}
if isosx:
library_books = self._get_library_books()
device_books = self._get_device_books()
book_count = float(len(device_books))
for (i,book) in enumerate(device_books):
@ -281,11 +280,13 @@ class ITUNES(DevicePlugin):
if self.report_progress is not None:
self.report_progress(i+1/book_count, _('%d of %d') % (i+1, book_count))
self._purge_orphans(cached_books)
elif iswindows:
try:
pythoncom.CoInitialize()
self.iTunes = win32com.client.Dispatch("iTunes.Application")
library_books = self._get_library_books()
device_books = self._get_device_books()
book_count = float(len(device_books))
for (i,book) in enumerate(device_books):
@ -315,6 +316,7 @@ class ITUNES(DevicePlugin):
if self.report_progress is not None:
self.report_progress(i+1/book_count,
_('%d of %d') % (i+1, book_count))
self._purge_orphans(cached_books)
finally:
pythoncom.CoUninitialize()
@ -985,7 +987,6 @@ class ITUNES(DevicePlugin):
connected_device = self.sources['iPod']
device = self.iTunes.sources.ItemByName(connected_device)
dev_books = None
added = None
for pl in device.Playlists:
if pl.Kind == self.PlaylistKind.index('User') and \
@ -1638,7 +1639,6 @@ class ITUNES(DevicePlugin):
connected_device = self.sources['iPod']
device = self.iTunes.sources.ItemByName(connected_device)
dev_books = None
for pl in device.Playlists:
if pl.Kind == self.PlaylistKind.index('User') and \
pl.SpecialKind == self.PlaylistSpecialKind.index('Books'):
@ -1671,11 +1671,13 @@ class ITUNES(DevicePlugin):
def _get_library_books(self):
'''
Populate a dict of paths from iTunes Library|Books
Windows assumes pythoncom wrapper
'''
if DEBUG:
self.log.info("\n ITUNES._get_library_books()")
library_books = {}
library_orphans = {}
lib = None
if isosx:
@ -1708,15 +1710,14 @@ class ITUNES(DevicePlugin):
if DEBUG:
self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
else:
# Remove calibre orphans
# Collect calibre orphans - remnants of recipe uploads
path = self.path_template % (book.name(), book.artist())
if str(book.description()).startswith(self.description_prefix):
if book.location() == appscript.k.missing_value:
library_orphans[path] = book
if DEBUG:
self.log.info(" found calibre orphan '%s' in Library|Books" % book.name())
#book.delete()
#continue
path = self.path_template % (book.name(), book.artist())
library_books[path] = book
if DEBUG:
self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind()))
@ -1729,59 +1730,59 @@ class ITUNES(DevicePlugin):
elif iswindows:
lib = None
try:
pythoncom.CoInitialize()
self.iTunes = win32com.client.Dispatch("iTunes.Application")
for source in self.iTunes.sources:
if source.Kind == self.Sources.index('Library'):
lib = source
self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind]))
break
else:
self.log.error(" Library source not found")
# try:
# pythoncom.CoInitialize()
# self.iTunes = win32com.client.Dispatch("iTunes.Application")
for source in self.iTunes.sources:
if source.Kind == self.Sources.index('Library'):
lib = source
self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind]))
break
else:
self.log.error(" Library source not found")
if lib is not None:
lib_books = None
if lib.Playlists is not None:
for pl in lib.Playlists:
if pl.Kind == self.PlaylistKind.index('User') and \
pl.SpecialKind == self.PlaylistSpecialKind.index('Books'):
if DEBUG:
self.log.info(" Books playlist: '%s'" % (pl.Name))
lib_books = pl.Tracks
break
else:
if lib is not None:
lib_books = None
if lib.Playlists is not None:
for pl in lib.Playlists:
if pl.Kind == self.PlaylistKind.index('User') and \
pl.SpecialKind == self.PlaylistSpecialKind.index('Books'):
if DEBUG:
self.log.error(" no Library|Books playlist found")
self.log.info(" Books playlist: '%s'" % (pl.Name))
lib_books = pl.Tracks
break
else:
if DEBUG:
self.log.error(" no Library playlists found")
self.log.error(" no Library|Books playlist found")
else:
if DEBUG:
self.log.error(" no Library playlists found")
try:
for book in lib_books:
# This may need additional entries for international iTunes users
if book.KindAsString in ['MPEG audio file']:
if DEBUG:
self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString))
else:
# Remove calibre orphans
if book.Description.startswith(self.description_prefix):
if not book.Location:
if DEBUG:
self.log.info(" found calibre orphan '%s' in Library|Books" % book.Name)
#book.Delete()
#continue
try:
for book in lib_books:
# This may need additional entries for international iTunes users
if book.KindAsString in ['MPEG audio file']:
if DEBUG:
self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString))
else:
path = self.path_template % (book.Name, book.Artist)
path = self.path_template % (book.Name, book.Artist)
library_books[path] = book
if DEBUG:
self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString))
except:
if DEBUG:
self.log.info(" no books in library")
finally:
pythoncom.CoUninitialize()
# Collect calibre orphans
if book.Description.startswith(self.description_prefix):
if not book.Location:
library_orphans[path] = book
if DEBUG:
self.log.info(" found calibre orphan '%s' in Library|Books" % book.Name)
library_books[path] = book
if DEBUG:
self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString))
except:
if DEBUG:
self.log.info(" no books in library")
# finally:
# pythoncom.CoUninitialize()
self.library_orphans = library_orphans
return library_books
def _get_purchased_book_ids(self):
@ -1904,6 +1905,45 @@ class ITUNES(DevicePlugin):
self.version[0],self.version[1],self.version[2]))
self.log.info(" iTunes_media: %s" % self.iTunes_media)
def _purge_orphans(self,cached_books):
'''
Scan self.library_orphans for any paths not on device
Remove any true orphans from iTunes
This occurs when recipes are uploaded in a previous session
and the book has since been deleted on the device
'''
if DEBUG:
self.log.info(" ITUNES._purge_orphans")
#self.log.info(" cached_books:\n %s" % "\n ".join(cached_books.keys()))
orphan_paths = {}
if isosx:
for orphan in self.library_orphans:
path = self.path_template % (self.library_orphans[orphan].name(),
self.library_orphans[orphan].artist())
orphan_paths[path] = self.library_orphans[orphan]
# Scan orphan_paths for paths not found in cached_books
for orphan in orphan_paths.keys():
if orphan not in cached_books:
if DEBUG:
self.log.info(" '%s' not found on device, removing from iTunes" % orphan)
self.iTunes.delete(orphan_paths[orphan])
elif iswindows:
for orphan in self.library_orphans:
path = self.path_template % (self.library_orphans[orphan].Name,
self.library_orphans[orphan].Artist)
orphan_paths[path] = self.library_orphans[orphan]
# Scan orphan_paths for paths not found in cached_books
for orphan in orphan_paths.keys():
if orphan not in cached_books:
if DEBUG:
self.log.info(" '%s' not found on device, removing from iTunes" % orphan)
orphan_paths[orphan].Delete()
def _remove_existing_copies(self,path,file,metadata):
'''
'''
@ -1945,7 +1985,7 @@ class ITUNES(DevicePlugin):
if isosx:
if DEBUG:
self.log.info(" deleting %s" % cached_book['dev_book'])
result = cached_book['dev_book'].delete()
cached_book['dev_book'].delete()
elif iswindows:
dev_pl = self._get_device_books_playlist()
@ -1957,7 +1997,7 @@ class ITUNES(DevicePlugin):
if hit.Name == cached_book['title']:
if DEBUG:
self.log.info(" deleting '%s' by %s" % (hit.Name, hit.Artist))
results = hit.Delete()
hit.Delete()
break
def _remove_from_iTunes(self, cached_book):

View File

@ -36,4 +36,29 @@ class ESLICK(USBMS):
SUPPORTS_SUB_DIRS = True
@classmethod
def can_handle(cls, dev, debug=False):
return (dev[3], dev[4]) != ('philips', 'Philips d')
class EBK52(ESLICK):
name = 'EBK-52 Device Interface'
gui_name = 'Sigmatek EBK'
description = _('Communicate with the Sigmatek eBook reader.')
FORMATS = ['epub', 'fb2', 'pdf', 'txt']
VENDOR_NAME = ''
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_READER'
MAIN_MEMORY_VOLUME_LABEL = 'Sigmatek Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Sigmatek Storage Card'
@classmethod
def can_handle(cls, dev, debug=False):
return (dev[3], dev[4]) == ('philips', 'Philips d')

View File

@ -12,7 +12,7 @@ from uuid import uuid4
from lxml import etree
from calibre import prints, guess_type, iswindows
from calibre import prints, guess_type
from calibre.devices.errors import DeviceError
from calibre.devices.usbms.driver import debug_print
from calibre.constants import DEBUG
@ -47,7 +47,7 @@ def strptime(src):
src[2] = str(MONTH_MAP[src[2]])
return time.strptime(' '.join(src), '%w, %d %m %Y %H:%M:%S %Z')
def strftime(epoch, zone=time.gmtime):
def strftime(epoch, zone=time.localtime):
src = time.strftime("%w, %d %m %Y %H:%M:%S GMT", zone(epoch)).split()
src[0] = INVERSE_DAY_MAP[int(src[0][:-1])]+','
src[2] = INVERSE_MONTH_MAP[int(src[2])]
@ -424,9 +424,6 @@ class XMLCache(object):
def update_text_record(self, record, book, path, bl_index):
timestamp = os.path.getmtime(path)
# Correct for MS DST time 'adjustment'
if iswindows and time.daylight:
timestamp -= time.altzone - time.timezone
date = strftime(timestamp)
if date != record.get('date', None):
record.set('date', date)

View File

@ -687,7 +687,7 @@ class DeviceMixin(object):
self.emailer.send_mails(jobnames,
Dispatcher(partial(self.emails_sent, remove=remove)),
attachments, to_s, subjects, texts, attachment_names)
self.status_bar.showMessage(_('Sending email to')+' '+to, 3000)
self.status_bar.show_message(_('Sending email to')+' '+to, 3000)
auto = []
if _auto_ids != []:
@ -748,7 +748,7 @@ class DeviceMixin(object):
'%s'%errors, show=True
)
else:
self.status_bar.showMessage(_('Sent by email:') + ', '.join(good),
self.status_bar.show_message(_('Sent by email:') + ', '.join(good),
5000)
def cover_to_thumbnail(self, data):
@ -787,7 +787,7 @@ class DeviceMixin(object):
attachments, to_s, subjects, texts, attachment_names)
sent_mails.append(to_s[0])
if sent_mails:
self.status_bar.showMessage(_('Sent news to')+' '+\
self.status_bar.show_message(_('Sent news to')+' '+\
', '.join(sent_mails), 3000)
def sync_catalogs(self, send_ids=None, do_auto_convert=True):
@ -846,7 +846,7 @@ class DeviceMixin(object):
self.upload_books(files, names, metadata,
on_card=on_card,
memory=[files, remove])
self.status_bar.showMessage(_('Sending catalogs to device.'), 5000)
self.status_bar.show_message(_('Sending catalogs to device.'), 5000)
@ -909,7 +909,7 @@ class DeviceMixin(object):
self.upload_books(files, names, metadata,
on_card=on_card,
memory=[files, remove])
self.status_bar.showMessage(_('Sending news to device.'), 5000)
self.status_bar.show_message(_('Sending news to device.'), 5000)
def sync_to_device(self, on_card, delete_from_library,
@ -963,7 +963,7 @@ class DeviceMixin(object):
names.append('%s_%d%s'%(prefix, id, os.path.splitext(f)[1]))
remove = remove_ids if delete_from_library else []
self.upload_books(gf, names, good, on_card, memory=(_files, remove))
self.status_bar.showMessage(_('Sending books to device.'), 5000)
self.status_bar.show_message(_('Sending books to device.'), 5000)
auto = []
if _auto_ids != []:

View File

@ -49,12 +49,12 @@ class BookInfo(QDialog, Ui_BookInfo):
def open_book_path(self, path):
if os.sep in unicode(path):
QDesktopServices.openUrl(QUrl('file:'+path))
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
else:
format = unicode(path)
path = self.view.model().db.format_abspath(self.current_row, format)
if path is not None:
QDesktopServices.openUrl(QUrl('file:'+path))
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
def next(self):

View File

@ -132,6 +132,7 @@ class ToolbarMixin(object): # {{{
self.action_open_containing_folder.setShortcut(Qt.Key_O)
self.addAction(self.action_open_containing_folder)
self.action_open_containing_folder.triggered.connect(self.view_folder)
self.action_sync.setShortcut(Qt.Key_D)
self.action_sync.setEnabled(True)
self.create_device_menu()
@ -231,7 +232,7 @@ class LibraryViewMixin(object): # {{{
('connect_to_search_box', (self.search,
self.search_done)),
('connect_to_book_display',
(self.status_bar.book_info.show_data,)),
(self.book_details.show_data,)),
]:
for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view):
getattr(view, func)(*args)
@ -360,7 +361,7 @@ class LayoutMixin(object): # {{{
if config['gui_layout'] == 'narrow':
from calibre.gui2.status import StatusBar
self.status_bar = StatusBar(self)
self.status_bar = self.book_details = StatusBar(self)
self.stack = Stack(self)
self.bd_splitter = Splitter('book_details_splitter',
_('Book Details'), I('book.svg'),

View File

@ -292,7 +292,8 @@ class BooksView(QTableView): # {{{
old_state['column_positions'][name] = i
if name != 'ondevice':
old_state['column_sizes'][name] = \
max(self.sizeHintForColumn(i), h.sectionSizeHint(i))
min(350, max(self.sizeHintForColumn(i),
h.sectionSizeHint(i)))
if name == 'timestamp':
old_state['column_sizes'][name] += 12
return old_state

View File

@ -53,7 +53,24 @@ def init_qt(args):
app.setWindowIcon(QIcon(I('library.png')))
return app, opts, args, actions
def get_library_path():
def get_default_library_path():
fname = _('Calibre Library')
if isinstance(fname, unicode):
try:
fname = fname.encode(filesystem_encoding)
except:
fname = 'Calibre Library'
x = os.path.expanduser('~'+os.sep+fname)
if not os.path.exists(x):
try:
os.makedirs(x)
except:
x = os.path.expanduser('~')
return x
def get_library_path(parent=None):
library_path = prefs['library_path']
if library_path is None: # Need to migrate to new database layout
base = os.path.expanduser('~')
@ -73,10 +90,12 @@ def get_library_path():
try:
os.makedirs(library_path)
except:
error_dialog(None, _('Failed to create library'),
_('Failed to create calibre library at: %r. Aborting.')%library_path,
error_dialog(parent, _('Failed to create library'),
_('Failed to create calibre library at: %r.')%library_path,
det_msg=traceback.format_exc(), show=True)
library_path = None
library_path = choose_dir(parent, 'choose calibre library',
_('Choose a location for your new calibre e-book library'),
default_dir=get_default_library_path())
return library_path
class DBRepair(QThread):
@ -159,22 +178,9 @@ class GuiRunner(QObject):
'a new empty library.'),
det_msg=tb, show=True)
if db is None:
fname = _('Calibre Library')
if isinstance(fname, unicode):
try:
fname = fname.encode(filesystem_encoding)
except:
fname = 'Calibre Library'
x = os.path.expanduser('~'+os.sep+fname)
if not os.path.exists(x):
try:
os.makedirs(x)
except:
x = os.path.expanduser('~')
candidate = choose_dir(self.splash_screen, 'choose calibre library',
_('Choose a location for your new calibre e-book library'),
default_dir=x)
default_dir=get_default_library_path())
if not candidate:
self.initialization_failed()
@ -236,8 +242,8 @@ class GuiRunner(QObject):
if gprefs.get('show_splash_screen', True):
self.show_splash_screen()
self.library_path = get_library_path()
if self.library_path is None:
self.library_path = get_library_path(parent=self.splash_screen)
if not self.library_path:
self.initialization_failed()
self.initialize_db()

View File

@ -7,8 +7,8 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, \
pyqtSignal, SIGNAL, QObject, QDialog
from PyQt4.QtGui import QCompleter
pyqtSignal, SIGNAL, QObject, QDialog, QCompleter, \
QAction, QKeySequence
from calibre.gui2 import config
from calibre.gui2.dialogs.confirm_delete import confirm
@ -348,8 +348,14 @@ class SearchBoxMixin(object):
self.do_advanced_search)
self.search.clear()
self.search.setFocus(Qt.OtherFocusReason)
self.search.setMaximumWidth(self.width()-150)
self.action_focus_search = QAction(self)
shortcuts = QKeySequence.keyBindings(QKeySequence.Find)
shortcuts = list(shortcuts) + [QKeySequence('/')]
self.action_focus_search.setShortcuts(shortcuts)
self.action_focus_search.triggered.connect(lambda x:
self.search.setFocus(Qt.OtherFocusReason))
self.addAction(self.action_focus_search)
def search_box_cleared(self):
self.tags_view.clear()

View File

@ -61,7 +61,7 @@ class BookInfoDisplay(QWidget):
pixmap = self.pixmap()
pwidth, pheight = pixmap.width(), pixmap.height()
width, height = fit_image(pwidth, pheight,
pwidth, self.statusbar_height-12)[1:]
pwidth, self.statusbar_height-20)[1:]
self.setMaximumHeight(height)
try:
aspect_ratio = pwidth/float(pheight)
@ -162,17 +162,48 @@ class BookInfoDisplay(QWidget):
self.updateGeometry()
self.setVisible(True)
class StatusBar(QStatusBar):
resized = pyqtSignal(object)
files_dropped = pyqtSignal(object, object)
show_book_info = pyqtSignal()
class StatusBarInterface(object):
def initialize(self, systray=None):
self.systray = systray
self.notifier = get_notifier(systray)
self.book_info = BookInfoDisplay(self.clearMessage)
def show_message(self, msg, timeout=0):
QStatusBar.showMessage(self, msg, timeout)
if self.notifier is not None and not config['disable_tray_notification']:
if isosx and isinstance(msg, unicode):
try:
msg = msg.encode(preferred_encoding)
except UnicodeEncodeError:
msg = msg.encode('utf-8')
self.notifier(msg)
def clear_message(self):
QStatusBar.clearMessage(self)
class BookDetailsInterface(object):
# These signals must be defined in the class implementing this interface
files_dropped = None
show_book_info = None
def reset_info(self):
raise NotImplementedError()
def show_data(self, data):
raise NotImplementedError()
class StatusBar(QStatusBar, StatusBarInterface, BookDetailsInterface):
files_dropped = pyqtSignal(object, object)
show_book_info = pyqtSignal()
resized = pyqtSignal(object)
def initialize(self, systray=None):
StatusBarInterface.initialize(self, systray=systray)
self.book_info = BookInfoDisplay(self.clear_message)
self.book_info.setAcceptDrops(True)
self.scroll_area = QScrollArea()
self.scroll_area.setWidget(self.book_info)
@ -192,15 +223,6 @@ class StatusBar(QStatusBar):
def reset_info(self):
self.book_info.show_data({})
def showMessage(self, msg, timeout=0):
ret = QStatusBar.showMessage(self, msg, timeout)
if self.notifier is not None and not config['disable_tray_notification']:
if isosx and isinstance(msg, unicode):
try:
msg = msg.encode(preferred_encoding)
except UnicodeEncodeError:
msg = msg.encode('utf-8')
self.notifier(msg)
return ret
def show_data(self, data):
self.book_info.show_data(data)

View File

@ -223,7 +223,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
####################### Setup device detection ########################
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
self.job_manager, Dispatcher(self.status_bar.showMessage))
self.job_manager, Dispatcher(self.status_bar.show_message))
self.device_manager.start()
@ -256,8 +256,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
####################### Status Bar #####################
self.status_bar.initialize(self.system_tray_icon)
self.status_bar.show_book_info.connect(self.show_book_info)
self.status_bar.files_dropped.connect(self.files_dropped_on_book)
self.book_details.show_book_info.connect(self.show_book_info)
self.book_details.files_dropped.connect(self.files_dropped_on_book)
####################### Setup Toolbar #####################
ToolbarMixin.__init__(self)
@ -482,7 +482,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
Dispatcher(self.info_read))
self.set_default_thumbnail(\
self.device_manager.device.THUMBNAIL_HEIGHT)
self.status_bar.showMessage(_('Device: ')+\
self.status_bar.show_message(_('Device: ')+\
self.device_manager.device.__class__.get_gui_name()+\
_(' detected.'), 3000)
self.device_connected = 'device' if not is_folder_device else 'folder'
@ -503,7 +503,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
dict(version=self.latest_version, device=' '))
self.device_info = ' '
if self.current_view() != self.library_view:
self.status_bar.reset_info()
self.book_details.reset_info()
self.location_view.setCurrentIndex(self.location_view.model().index(0))
self.eject_action.setEnabled(False)
self.refresh_ondevice_info (device_connected = False)
@ -861,7 +861,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
to_device = allow_device and self.stack.currentIndex() != 0
self._add_books(books, to_device)
if to_device:
self.status_bar.showMessage(\
self.status_bar.show_message(\
_('Uploading books to device.'), 2000)
@ -912,7 +912,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
self.upload_books(paths,
list(map(ascii_filename, names)),
infos, on_card=on_card)
self.status_bar.showMessage(
self.status_bar.show_message(
_('Uploading books to device.'), 2000)
if getattr(self._adder, 'number_of_books_added', 0) > 0:
self.library_view.model().books_added(self._adder.number_of_books_added)
@ -1058,7 +1058,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
job = self.remove_paths(paths)
self.delete_memory[job] = (paths, view.model())
view.model().mark_for_deletion(job, rows)
self.status_bar.showMessage(_('Deleting books from device.'), 1000)
self.status_bar.show_message(_('Deleting books from device.'), 1000)
def remove_paths(self, paths):
return self.device_manager.delete_books(\
@ -1424,7 +1424,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
job.catalog_file_path = out
job.fmt = fmt
job.catalog_sync, job.catalog_title = sync, title
self.status_bar.showMessage(_('Generating %s catalog...')%fmt)
self.status_bar.show_message(_('Generating %s catalog...')%fmt)
def catalog_generated(self, job):
if job.result:
@ -1440,7 +1440,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
sync = dynamic.get('catalogs_to_be_synced', set([]))
sync.add(id)
dynamic.set('catalogs_to_be_synced', sync)
self.status_bar.showMessage(_('Catalog generated.'), 3000)
self.status_bar.show_message(_('Catalog generated.'), 3000)
self.sync_catalogs()
if job.fmt not in ['EPUB','MOBI']:
export_dir = choose_dir(self, _('Export Catalog Directory'),
@ -1458,7 +1458,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
Dispatcher(self.scheduled_recipe_fetched), func, args=args,
description=desc)
self.conversion_jobs[job] = (temp_files, fmt, arg)
self.status_bar.showMessage(_('Fetching news from ')+arg['title'], 2000)
self.status_bar.show_message(_('Fetching news from ')+arg['title'], 2000)
def scheduled_recipe_fetched(self, job):
temp_files, fmt, arg = self.conversion_jobs.pop(job)
@ -1472,7 +1472,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
sync.add(id)
dynamic.set('news_to_be_synced', sync)
self.scheduler.recipe_downloaded(arg)
self.status_bar.showMessage(arg['title'] + _(' fetched.'), 3000)
self.status_bar.show_message(arg['title'] + _(' fetched.'), 3000)
self.email_news(id)
self.sync_news()
@ -1552,7 +1552,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
num = len(jobs)
if num > 0:
self.status_bar.showMessage(_('Starting conversion of %d book(s)') %
self.status_bar.show_message(_('Starting conversion of %d book(s)') %
num, 2000)
def queue_convert_jobs(self, jobs, changed, bad, rows, previous,
@ -1599,7 +1599,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
self.library_view.model().db.add_format(book_id, \
fmt, data, index_is_id=True)
data.close()
self.status_bar.showMessage(job.description + \
self.status_bar.show_message(job.description + \
(' completed'), 2000)
finally:
for f in temp_files:
@ -1802,9 +1802,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
self.library_view.set_database(db)
self.tags_view.set_database(db, self.tag_match, self.popularity)
self.library_view.model().set_book_on_device_func(self.book_on_device)
self.status_bar.clearMessage()
self.status_bar.clear_message()
self.search.clear_to_help()
self.status_bar.reset_info()
self.book_details.reset_info()
self.library_view.model().count_changed()
prefs['library_path'] = self.library_path
@ -1831,7 +1831,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
'''
page = 0 if location == 'library' else 1 if location == 'main' else 2 if location == 'carda' else 3
self.stack.setCurrentIndex(page)
self.status_bar.reset_info()
self.book_details.reset_info()
for x in ('tb', 'cb'):
splitter = getattr(self, x+'_splitter')
splitter.button.setEnabled(location == 'library')

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ class SafeLocalTimeZone(tzlocal):
def compute_locale_info_for_parse_date():
try:
dt = datetime.strptime('1/5/2000', "%x")
except ValueError:
except:
try:
dt = datetime.strptime('1/5/01', '%x')
except: