merge from trunk

This commit is contained in:
Lee 2011-06-13 02:49:19 +10:00
commit 67b12111e2
6 changed files with 116 additions and 29 deletions

View File

@ -0,0 +1,52 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1306061239(BasicNewsRecipe):
title = u'The Daily Mirror'
description = 'News as provide by The Daily Mirror -UK'
__author__ = 'Dave Asbury'
language = 'en_GB'
cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg'
masthead_url = 'http://www.nmauk.co.uk/nma/images/daily_mirror.gif'
oldest_article = 1
max_articles_per_feed = 100
remove_empty_feeds = True
remove_javascript = True
no_stylesheets = True
keep_only_tags = [
dict(name='h1'),
dict(attrs={'class':['article-attr']}),
dict(name='div', attrs={'class' : [ 'article-body', 'crosshead']})
]
remove_tags = [
dict(name='div', attrs={'class' : ['caption', 'article-resize']}),
dict( attrs={'class':'append-html'})
]
feeds = [
(u'News', u'http://www.mirror.co.uk/news/rss.xml')
,(u'Tech News', u'http://www.mirror.co.uk/news/technology/rss.xml')
,(u'Weird World','http://www.mirror.co.uk/news/weird-world/rss.xml')
,(u'Film Gossip','http://www.mirror.co.uk/celebs/film/rss.xml')
,(u'Music News','http://www.mirror.co.uk/celebs/music/rss.xml')
,(u'Celebs and Tv Gossip','http://www.mirror.co.uk/celebs/tv/rss.xml')
,(u'Sport','http://www.mirror.co.uk/sport/rss.xml')
,(u'Life Style','http://www.mirror.co.uk/life-style/rss.xml')
,(u'Advice','http://www.mirror.co.uk/advice/rss.xml')
,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml')
# example of commented out feed not needed ,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml')
]

View File

@ -32,16 +32,11 @@
<xsl:value-of select="fb:description/fb:title-info/fb:book-title"/>
</title>
<style type="text/css">
a { color : #0002CC }
a:hover { color : #BF0000 }
body { background-color : #FEFEFE; color : #000000; font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; text-align : justify }
body { text-align : justify }
h1{ font-size : 160%; font-style : normal; font-weight : bold; text-align : left; border : 1px solid Black; background-color : #E7E7E7; margin-left : 0px; page-break-before : always; }
h2{ font-size : 130%; font-style : normal; font-weight : bold; text-align : left; background-color : #EEEEEE; border : 1px solid Gray; page-break-before : always; }
h3{ font-size : 110%; font-style : normal; font-weight : bold; text-align : left; background-color : #F1F1F1; border : 1px solid Silver;}
h4{ font-size : 100%; font-style : normal; font-weight : bold; text-align : left; border : 1px solid Gray; background-color : #F4F4F4;}
@ -56,13 +51,11 @@
hr { color : Black }
div {font-family : "Times New Roman", Times, serif; text-align : justify}
ul {margin-left: 0}
.epigraph{width:50%; margin-left : 35%;}
div.paragraph { text-align: justify; text-indent: 2em; }
div.paragraph { text-indent: 2em; }
</style>
<link rel="stylesheet" type="text/css" href="inline-styles.css" />
</head>

View File

@ -179,6 +179,24 @@ class Win32Freeze(Command, WixMixIn):
shutil.copytree(self.j(comext, 'shell'), self.j(sp_dir, 'win32com', 'shell'))
shutil.rmtree(comext)
# Fix PyCrypto, removing the bootstrap .py modules that load the .pyd
# modules, since they do not work when in a zip file
for crypto_dir in glob.glob(self.j(sp_dir, 'pycrypto-*', 'Crypto')):
for dirpath, dirnames, filenames in os.walk(crypto_dir):
for f in filenames:
name, ext = os.path.splitext(f)
if ext == '.pyd':
with open(self.j(dirpath, name+'.py')) as f:
raw = f.read().strip()
if (not raw.startswith('def __bootstrap__') or not
raw.endswith('__bootstrap__()')):
raise Exception('The PyCrypto file %r has non'
' bootstrap code'%self.j(dirpath, f))
for ext in ('.py', '.pyc', '.pyo'):
x = self.j(dirpath, name+ext)
if os.path.exists(x):
os.remove(x)
for pat in (r'PyQt4\uic\port_v3', ):
x = glob.glob(self.j(self.lib_dir, 'site-packages', pat))[0]
shutil.rmtree(x)

View File

@ -271,6 +271,9 @@ class Dispatcher(QObject):
Convenience class to use Qt signals with arbitrary python callables.
By default, ensures that a function call always happens in the
thread this Dispatcher was created in.
Note that if you create the Dispatcher in a thread without an event loop of
its own, the function call will happen in the GUI thread (I think).
'''
dispatch_signal = pyqtSignal(object, object)
@ -292,11 +295,20 @@ class FunctionDispatcher(QObject):
'''
Convenience class to use Qt signals with arbitrary python functions.
By default, ensures that a function call always happens in the
thread this Dispatcher was created in.
thread this FunctionDispatcher was created in.
Note that you must create FunctionDispatcher objects in the GUI thread.
'''
dispatch_signal = pyqtSignal(object, object, object)
def __init__(self, func, queued=True, parent=None):
global gui_thread
if gui_thread is None:
gui_thread = QThread.currentThread()
if not is_gui_thread():
raise ValueError(
'You can only create a FunctionDispatcher in the GUI thread')
QObject.__init__(self, parent)
self.func = func
typ = Qt.QueuedConnection
@ -307,6 +319,8 @@ class FunctionDispatcher(QObject):
self.lock = threading.Lock()
def __call__(self, *args, **kwargs):
if is_gui_thread():
return self.func(*args, **kwargs)
with self.lock:
self.dispatch_signal.emit(self.q, args, kwargs)
res = self.q.get()

View File

@ -6,18 +6,18 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, traceback, Queue, time, cStringIO, re, sys
from threading import Thread
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \
Qt, pyqtSignal, QDialog, QObject
from PyQt4.Qt import (QMenu, QAction, QActionGroup, QIcon, SIGNAL,
Qt, pyqtSignal, QDialog, QObject)
from calibre.customize.ui import available_input_formats, available_output_formats, \
device_plugins
from calibre.customize.ui import (available_input_formats, available_output_formats,
device_plugins)
from calibre.devices.interface import DevicePlugin
from calibre.devices.errors import UserFeedback, OpenFeedback
from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog
from calibre.utils.ipc.job import BaseJob
from calibre.devices.scanner import DeviceScanner
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
warning_dialog, info_dialog, choose_dir
from calibre.gui2 import (config, error_dialog, Dispatcher, dynamic,
warning_dialog, info_dialog, choose_dir, FunctionDispatcher)
from calibre.ebooks.metadata import authors_to_string
from calibre import preferred_encoding, prints, force_unicode, as_unicode
from calibre.utils.filenames import ascii_filename
@ -35,8 +35,13 @@ class DeviceJob(BaseJob): # {{{
def __init__(self, func, done, job_manager, args=[], kwargs={},
description=''):
BaseJob.__init__(self, description, done=done)
BaseJob.__init__(self, description)
self.func = func
self.callback_on_done = done
if not isinstance(self.callback_on_done, (Dispatcher,
FunctionDispatcher)):
self.callback_on_done = FunctionDispatcher(self.callback_on_done)
self.args, self.kwargs = args, kwargs
self.exception = None
self.job_manager = job_manager
@ -50,6 +55,10 @@ class DeviceJob(BaseJob): # {{{
def job_done(self):
self.duration = time.time() - self.start_time
self.percent = 1
try:
self.callback_on_done(self)
except:
pass
self.job_manager.changed_queue.put(self)
def report_progress(self, percent, msg=''):
@ -254,7 +263,8 @@ class DeviceManager(Thread): # {{{
job = self.next()
if job is not None:
self.current_job = job
self.device.set_progress_reporter(job.report_progress)
if self.device is not None:
self.device.set_progress_reporter(job.report_progress)
self.current_job.run()
self.current_job = None
else:
@ -611,7 +621,7 @@ class DeviceMixin(object): # {{{
self.device_error_dialog = error_dialog(self, _('Error'),
_('Error communicating with device'), ' ')
self.device_error_dialog.setModal(Qt.NonModal)
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
self.device_manager = DeviceManager(FunctionDispatcher(self.device_detected),
self.job_manager, Dispatcher(self.status_bar.show_message),
Dispatcher(self.show_open_feedback))
self.device_manager.start()
@ -736,7 +746,7 @@ class DeviceMixin(object): # {{{
self.set_device_menu_items_state(connected)
if connected:
self.device_manager.get_device_information(\
Dispatcher(self.info_read))
FunctionDispatcher(self.info_read))
self.set_default_thumbnail(\
self.device_manager.device.THUMBNAIL_HEIGHT)
self.status_bar.show_message(_('Device: ')+\
@ -767,7 +777,7 @@ class DeviceMixin(object): # {{{
self.device_manager.device.icon)
self.bars_manager.update_bars()
self.status_bar.device_connected(info[0])
self.device_manager.books(Dispatcher(self.metadata_downloaded))
self.device_manager.books(FunctionDispatcher(self.metadata_downloaded))
def metadata_downloaded(self, job):
'''
@ -810,7 +820,7 @@ class DeviceMixin(object): # {{{
def remove_paths(self, paths):
return self.device_manager.delete_books(
Dispatcher(self.books_deleted), paths)
FunctionDispatcher(self.books_deleted), paths)
def books_deleted(self, job):
'''
@ -1187,7 +1197,7 @@ class DeviceMixin(object): # {{{
Upload metadata to device.
'''
plugboards = self.library_view.model().db.prefs.get('plugboards', {})
self.device_manager.sync_booklists(Dispatcher(self.metadata_synced),
self.device_manager.sync_booklists(FunctionDispatcher(self.metadata_synced),
self.booklists(), plugboards)
def metadata_synced(self, job):
@ -1222,7 +1232,7 @@ class DeviceMixin(object): # {{{
titles = [i.title for i in metadata]
plugboards = self.library_view.model().db.prefs.get('plugboards', {})
job = self.device_manager.upload_books(
Dispatcher(self.books_uploaded),
FunctionDispatcher(self.books_uploaded),
files, names, on_card=on_card,
metadata=metadata, titles=titles, plugboards=plugboards
)
@ -1475,7 +1485,7 @@ class DeviceMixin(object): # {{{
self.cover_to_thumbnail(open(book.cover, 'rb').read())
plugboards = self.library_view.model().db.prefs.get('plugboards', {})
self.device_manager.sync_booklists(
Dispatcher(self.metadata_synced), booklists,
FunctionDispatcher(self.metadata_synced), booklists,
plugboards)
return update_metadata
# }}}

View File

@ -11,8 +11,8 @@ from binascii import unhexlify
from functools import partial
from itertools import repeat
from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \
config as email_config
from calibre.utils.smtp import (compose_mail, sendmail, extract_email_address,
config as email_config)
from calibre.utils.filenames import ascii_filename
from calibre.customize.ui import available_input_formats, available_output_formats
from calibre.ebooks.metadata import authors_to_string
@ -67,8 +67,8 @@ class Sendmail(object):
from_ = opts.from_
if not from_:
from_ = 'calibre <calibre@'+socket.getfqdn()+'>'
msg = compose_mail(from_, to, text, subject, open(attachment, 'rb'),
aname)
with lopen(attachment, 'rb') as f:
msg = compose_mail(from_, to, text, subject, f, aname)
efrom, eto = map(extract_email_address, (from_, to))
eto = [eto]
sendmail(msg, efrom, eto, localhost=None,