Merge from trunk

This commit is contained in:
Charles Haley 2010-06-13 07:48:19 +01:00
commit 7bf0c77dff
19 changed files with 1639 additions and 1714 deletions

View File

@ -14,8 +14,8 @@ class LiberoNews(BasicNewsRecipe):
__author__ = 'Marini Gabriele' __author__ = 'Marini Gabriele'
description = 'Italian daily newspaper' description = 'Italian daily newspaper'
cover_url = 'http://www.ilgiornale.it/img_v1/logo.gif' cover_url = 'http://www.libero-news.it/images/logo.png'
title = u'Libero' title = u'Libero '
publisher = 'EDITORIALE LIBERO s.r.l 2006' publisher = 'EDITORIALE LIBERO s.r.l 2006'
category = 'News, politics, culture, economy, general interest' category = 'News, politics, culture, economy, general interest'

View File

@ -16,7 +16,7 @@ class DailyTelegraph(BasicNewsRecipe):
language = 'en_AU' language = 'en_AU'
oldest_article = 2 oldest_article = 2
max_articles_per_feed = 10 max_articles_per_feed = 20
remove_javascript = True remove_javascript = True
no_stylesheets = True no_stylesheets = True
encoding = 'utf8' encoding = 'utf8'

View File

@ -30,7 +30,7 @@ class ANDROID(USBMS):
0x18d1 : { 0x4e11 : [0x0100, 0x226], 0x4e12: [0x0100, 0x226]}, 0x18d1 : { 0x4e11 : [0x0100, 0x226], 0x4e12: [0x0100, 0x226]},
# Samsung # Samsung
0x04e8 : { 0x681d : [0x0222], 0x681c : [0x0222, 0x0224]}, 0x04e8 : { 0x681d : [0x0222, 0x0400], 0x681c : [0x0222, 0x0224]},
# Acer # Acer
0x502 : { 0x3203 : [0x0100]}, 0x502 : { 0x3203 : [0x0100]},
@ -41,10 +41,12 @@ class ANDROID(USBMS):
'be used') 'be used')
EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(EBOOK_DIR_MAIN) EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(EBOOK_DIR_MAIN)
VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER', 'GT-I5700'] VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER',
'GT-I5700', 'SAMSUNG']
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',
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE'] 'PROD_GT-I9000']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'PROD_GT-I9000_CARD']
OSX_MAIN_MEM = 'HTC Android Phone Media' OSX_MAIN_MEM = 'HTC Android Phone Media'

View File

@ -76,7 +76,7 @@ class ITUNES(DevicePlugin):
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, 5, 0) version = (0,6,0)
OPEN_FEEDBACK_MESSAGE = _( OPEN_FEEDBACK_MESSAGE = _(
'Apple device detected, launching iTunes, please wait ...') 'Apple device detected, launching iTunes, please wait ...')
@ -280,7 +280,7 @@ class ITUNES(DevicePlugin):
if self.report_progress is not None: if self.report_progress is not None:
self.report_progress(i+1/book_count, _('%d of %d') % (i+1, book_count)) self.report_progress(i+1/book_count, _('%d of %d') % (i+1, book_count))
self._purge_orphans(cached_books) self._purge_orphans(library_books, cached_books)
elif iswindows: elif iswindows:
try: try:
@ -316,7 +316,7 @@ class ITUNES(DevicePlugin):
if self.report_progress is not None: if self.report_progress is not None:
self.report_progress(i+1/book_count, self.report_progress(i+1/book_count,
_('%d of %d') % (i+1, book_count)) _('%d of %d') % (i+1, book_count))
self._purge_orphans(cached_books) self._purge_orphans(library_books, cached_books)
finally: finally:
pythoncom.CoUninitialize() pythoncom.CoUninitialize()
@ -324,9 +324,9 @@ class ITUNES(DevicePlugin):
if self.report_progress is not None: if self.report_progress is not None:
self.report_progress(1.0, _('finished')) self.report_progress(1.0, _('finished'))
self.cached_books = cached_books self.cached_books = cached_books
if DEBUG: # if DEBUG:
self._dump_booklist(booklist, 'returning from books():') # self._dump_booklist(booklist, 'returning from books():')
self._dump_cached_books('returning from books():') # self._dump_cached_books('returning from books():')
return booklist return booklist
else: else:
return [] return []
@ -463,7 +463,7 @@ class ITUNES(DevicePlugin):
else: else:
# iTunes running, but not connected iPad # iTunes running, but not connected iPad
if DEBUG: if DEBUG:
self.log.info(' self.ejected = True') self.log.info(' iDevice has been ejected')
self.ejected = True self.ejected = True
return False return False
@ -782,121 +782,6 @@ class ITUNES(DevicePlugin):
# self._dump_cached_books('upload_books()') # self._dump_cached_books('upload_books()')
self._dump_update_list('upload_books()') self._dump_update_list('upload_books()')
'''
if isosx:
for (i,file) in enumerate(files):
path = self.path_template % (metadata[i].title, metadata[i].author[0])
if self.manual_sync_mode:
# Delete existing from Device|Books, add to self.update_list
# for deletion from booklist[0] during add_books_to_metadata
if path in self.cached_books:
self.update_list.append(self.cached_books[path])
if DEBUG:
self.log.info(" adding '%s' by %s to self.update_list" %
(self.cached_books[path]['title'],self.cached_books[path]['author']))
if DEBUG:
self.log.info( " deleting existing '%s'" % (path))
self._remove_from_iTunes(self.cached_books[path])
if self.manual_sync_mode:
dev_book_added = self._remove_from_device(self.cached_books[path])
# Add to iTunes Library|Books
fpath = file
if getattr(file, 'orig_file_path', None) is not None:
fpath = file.orig_file_path
elif getattr(file, 'name', None) is not None:
fpath = file.name
if isinstance(file,PersistentTemporaryFile) and self.manual_sync_mode:
if DEBUG:
self.log.info(" PTF not added to Library|Books")
else:
added = self.iTunes.add(appscript.mactypes.File(fpath))
if DEBUG:
self.log.info(" file added to Library|Books")
dev_book_added = None
if self.manual_sync_mode:
dev_book_added = self._add_device_book(fpath)
thumb = None
if metadata[i].cover:
try:
# Use cover data as artwork
cover_data = open(metadata[i].cover,'rb')
added.artworks[1].data_.set(cover_data.read())
# Resize for thumb
width = metadata[i].thumbnail[0]
height = metadata[i].thumbnail[1]
im = PILImage.open(metadata[i].cover)
im = im.resize((width, height), PILImage.ANTIALIAS)
of = cStringIO.StringIO()
im.convert('RGB').save(of, 'JPEG')
thumb = of.getvalue()
# Refresh the thumbnail cache
if DEBUG:
self.log.info( " refreshing cached thumb for '%s'" % metadata[i].title)
archive_path = os.path.join(self.cache_dir, "thumbs.zip")
zfw = zipfile.ZipFile(archive_path, mode='a')
thumb_path = path.rpartition('.')[0] + '.jpg'
zfw.writestr(thumb_path, thumb)
zfw.close()
except:
self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0]))
self.log.error("ITUNES.upload_books(): error converting '%s' to thumb for '%s'" % (metadata[i].cover,metadata[i].title))
# Create a new Book
this_book = Book(metadata[i].title, metadata[i].author[0])
try:
this_book.datetime = parse_date(str(added.date_added())).timetuple()
except:
pass
this_book.db_id = None
this_book.device_collections = []
this_book.library_id = added
this_book.path = path
this_book.size = self._get_device_book_size(fpath, added.size())
this_book.thumbnail = thumb
this_book.iTunes_id = added
new_booklist.append(this_book)
# Populate the iTunes metadata
if metadata[i].comments:
added.comment.set(strip_tags.sub('',metadata[i].comments))
added.description.set("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S'))
added.enabled.set(True)
if metadata[i].rating:
added.rating.set(metadata[i].rating*10)
added.sort_artist.set(metadata[i].author_sort.title())
added.sort_name.set(this_book.title_sorter)
# Set genre from metadata
# iTunes grabs the first dc:subject from the opf metadata,
# But we can manually override with first tag starting with alpha
for tag in metadata[i].tags:
if self._is_alpha(tag[0]):
added.genre.set(tag)
break
# Add new_book to self.cached_paths
self.cached_books[this_book.path] = {
'title': this_book.title,
'author': this_book.author,
'lib_book': added,
'dev_book': dev_book_added
}
# Report progress
if self.report_progress is not None:
self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count))
'''
if isosx: if isosx:
for (i,file) in enumerate(files): for (i,file) in enumerate(files):
path = self.path_template % (metadata[i].title, metadata[i].author[0]) path = self.path_template % (metadata[i].title, metadata[i].author[0])
@ -1378,6 +1263,15 @@ class ITUNES(DevicePlugin):
self.log.info(" %s" % file.name) self.log.info(" %s" % file.name)
self.log.info() self.log.info()
def _dump_library_books(self, library_books):
'''
'''
if DEBUG:
self.log.info("\n library_books:")
for book in library_books:
self.log.info(" %s" % book)
self.log.info()
def _dump_update_list(self,header=None): def _dump_update_list(self,header=None):
if header: if header:
msg = '\nself.update_list called from %s' % header msg = '\nself.update_list called from %s' % header
@ -1590,7 +1484,7 @@ class ITUNES(DevicePlugin):
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:
if DEBUG: if DEBUG:
self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind())) self.log.info(" adding %-30.30s %-30.30s [%s]" % (book.name(), book.artist(), book.kind()))
device_books.append(book) device_books.append(book)
elif iswindows: elif iswindows:
@ -1619,7 +1513,7 @@ class ITUNES(DevicePlugin):
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:
if DEBUG: if DEBUG:
self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) self.log.info(" adding %-30.30s %-30.30s [%s]" % (book.Name, book.Artist, book.KindAsString))
device_books.append(book) device_books.append(book)
finally: finally:
@ -1716,11 +1610,11 @@ class ITUNES(DevicePlugin):
if book.location() == appscript.k.missing_value: if book.location() == appscript.k.missing_value:
library_orphans[path] = book library_orphans[path] = book
if DEBUG: if DEBUG:
self.log.info(" found calibre orphan '%s' in Library|Books" % book.name()) self.log.info(" found iTunes PTF '%s' in Library|Books" % book.name())
library_books[path] = book library_books[path] = book
if DEBUG: if DEBUG:
self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind())) self.log.info(" adding %-30.30s %-30.30s [%s]" % (book.name(), book.artist(), book.kind()))
else: else:
if DEBUG: if DEBUG:
self.log.info(' no Library playlists') self.log.info(' no Library playlists')
@ -1730,9 +1624,6 @@ class ITUNES(DevicePlugin):
elif iswindows: elif iswindows:
lib = None lib = None
# try:
# pythoncom.CoInitialize()
# self.iTunes = win32com.client.Dispatch("iTunes.Application")
for source in self.iTunes.sources: for source in self.iTunes.sources:
if source.Kind == self.Sources.index('Library'): if source.Kind == self.Sources.index('Library'):
lib = source lib = source
@ -1772,16 +1663,14 @@ class ITUNES(DevicePlugin):
if not book.Location: if not book.Location:
library_orphans[path] = book library_orphans[path] = book
if DEBUG: if DEBUG:
self.log.info(" found calibre orphan '%s' in Library|Books" % book.Name) self.log.info(" found iTunes PTF '%s' in Library|Books" % book.Name)
library_books[path] = book library_books[path] = book
if DEBUG: if DEBUG:
self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) self.log.info(" adding %-30.30s %-30.30s [%s]" % (book.Name, book.Artist, book.KindAsString))
except: except:
if DEBUG: if DEBUG:
self.log.info(" no books in library") self.log.info(" no books in library")
# finally:
# pythoncom.CoUninitialize()
self.library_orphans = library_orphans self.library_orphans = library_orphans
return library_books return library_books
@ -1905,44 +1794,36 @@ class ITUNES(DevicePlugin):
self.version[0],self.version[1],self.version[2])) self.version[0],self.version[1],self.version[2]))
self.log.info(" iTunes_media: %s" % self.iTunes_media) self.log.info(" iTunes_media: %s" % self.iTunes_media)
def _purge_orphans(self,cached_books): def _purge_orphans(self,library_books, cached_books):
''' '''
Scan self.library_orphans for any paths not on device Scan library_books for any paths not on device
Remove any true orphans from iTunes Remove any iTunes orphans originally added by calibre
This occurs when recipes are uploaded in a previous session This occurs when the user deletes a book in iBooks while disconnected
and the book has since been deleted on the device
''' '''
if DEBUG: if DEBUG:
self.log.info(" ITUNES._purge_orphans") self.log.info("\n ITUNES._purge_orphans")
#self._dump_library_books(library_books)
#self.log.info(" cached_books:\n %s" % "\n ".join(cached_books.keys())) #self.log.info(" cached_books:\n %s" % "\n ".join(cached_books.keys()))
orphan_paths = {} for book in library_books:
if isosx: if isosx:
for orphan in self.library_orphans: if book not in cached_books and \
path = self.path_template % (self.library_orphans[orphan].name(), str(library_books[book].description()).startswith(self.description_prefix):
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: if DEBUG:
self.log.info(" '%s' not found on device, removing from iTunes" % orphan) self.log.info(" '%s' not found on iDevice, removing from iTunes" % book)
self.iTunes.delete(orphan_paths[orphan]) btr = { 'title':library_books[book].name(),
'author':library_books[book].artist(),
'lib_book':library_books[book]}
self._remove_from_iTunes(btr)
elif iswindows: elif iswindows:
for orphan in self.library_orphans: if book not in cached_books and \
path = self.path_template % (self.library_orphans[orphan].Name, library_books[book].Description.startswith(self.description_prefix):
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: if DEBUG:
self.log.info(" '%s' not found on device, removing from iTunes" % orphan) self.log.info(" '%s' not found on iDevice, removing from iTunes" % book)
orphan_paths[orphan].Delete() btr = { 'title':library_books[book].Name,
'author':library_books[book].Artist,
'lib_book':library_books[book]}
self._remove_from_iTunes(btr)
def _remove_existing_copies(self,path,file,metadata): def _remove_existing_copies(self,path,file,metadata):
''' '''
@ -2049,10 +1930,13 @@ class ITUNES(DevicePlugin):
Assume we're wrapped in a pythoncom Assume we're wrapped in a pythoncom
Windows stores the book under a common author directory, so we just delete the .epub Windows stores the book under a common author directory, so we just delete the .epub
''' '''
try:
book = self._find_library_book(cached_book) book = cached_book['lib_book']
if book:
path = book.Location path = book.Location
except:
book = self._find_library_book(cached_book)
path = book.Location
storage_path = os.path.split(book.Location) storage_path = os.path.split(book.Location)
if book.Location.startswith(self.iTunes_media): if book.Location.startswith(self.iTunes_media):
if DEBUG: if DEBUG:
@ -2074,9 +1958,6 @@ class ITUNES(DevicePlugin):
book.Delete() book.Delete()
else:
self.log.warning(" could not find '%s' in iTunes database" % cached_book['title'])
def _update_device(self, msg='', wait=True): def _update_device(self, msg='', wait=True):
''' '''
Trigger a sync, wait for completion Trigger a sync, wait for completion

View File

@ -103,8 +103,8 @@ class CoverManager(object):
32)] 32)]
img_data = create_cover_page(lines, I('library.png')) img_data = create_cover_page(lines, I('library.png'))
id, href = self.oeb.manifest.generate('cover_image', id, href = self.oeb.manifest.generate('cover_image',
'cover_image.png') 'cover_image.jpg')
item = self.oeb.manifest.add(id, href, guess_type('t.png')[0], item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0],
data=img_data) data=img_data)
m.clear('cover') m.clear('cover')
m.add('cover', item.id) m.add('cover', item.id)

1200
src/calibre/gui2/actions.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -83,7 +83,6 @@ if pictureflow is not None:
self.setFocusPolicy(Qt.WheelFocus) self.setFocusPolicy(Qt.WheelFocus)
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
QSizePolicy.Expanding)) QSizePolicy.Expanding))
self.setZoomFactor(150)
def sizeHint(self): def sizeHint(self):
return self.minimumSize() return self.minimumSize()

View File

@ -3,24 +3,26 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
# Imports {{{ # Imports {{{
import os, traceback, Queue, time, socket, cStringIO, re import os, traceback, Queue, time, socket, cStringIO, re, sys
from threading import Thread, RLock from threading import Thread, RLock
from itertools import repeat from itertools import repeat
from functools import partial from functools import partial
from binascii import unhexlify from binascii import unhexlify
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \ from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \
Qt, pyqtSignal Qt, pyqtSignal, QColor, QPainter
from PyQt4.QtSvg import QSvgRenderer
from calibre.customize.ui import available_input_formats, available_output_formats, \ from calibre.customize.ui import available_input_formats, available_output_formats, \
device_plugins device_plugins
from calibre.devices.interface import DevicePlugin from calibre.devices.interface import DevicePlugin
from calibre.devices.errors import UserFeedback
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.utils.ipc.job import BaseJob from calibre.utils.ipc.job import BaseJob
from calibre.devices.scanner import DeviceScanner from calibre.devices.scanner import DeviceScanner
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \ from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
pixmap_to_data, warning_dialog, \ pixmap_to_data, warning_dialog, \
question_dialog question_dialog, info_dialog, choose_dir
from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string
from calibre import preferred_encoding, prints from calibre import preferred_encoding, prints
from calibre.utils.filenames import ascii_filename from calibre.utils.filenames import ascii_filename
@ -597,10 +599,204 @@ class Emailer(Thread): # {{{
# }}} # }}}
class DeviceMixin(object): class DeviceMixin(object): # {{{
def __init__(self): def __init__(self):
self.db_book_uuid_cache = set() self.db_book_uuid_cache = set()
self.device_error_dialog = error_dialog(self, _('Error'),
_('Error communicating with device'), ' ')
self.device_error_dialog.setModal(Qt.NonModal)
self.device_connected = None
self.emailer = Emailer()
self.emailer.start()
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
self.job_manager, Dispatcher(self.status_bar.show_message))
self.device_manager.start()
def set_default_thumbnail(self, height):
r = QSvgRenderer(I('book.svg'))
pixmap = QPixmap(height, height)
pixmap.fill(QColor(255,255,255))
p = QPainter(pixmap)
r.render(p)
p.end()
self.default_thumbnail = (pixmap.width(), pixmap.height(),
pixmap_to_data(pixmap))
def connect_to_folder(self):
dir = choose_dir(self, 'Select Device Folder',
_('Select folder to open as device'))
if dir is not None:
self.device_manager.connect_to_folder(dir)
def disconnect_from_folder(self):
self.device_manager.disconnect_folder()
def _sync_action_triggered(self, *args):
m = getattr(self, '_sync_menu', None)
if m is not None:
m.trigger_default()
def create_device_menu(self):
self._sync_menu = DeviceMenu(self)
self.action_sync.setMenu(self._sync_menu)
self.connect(self._sync_menu,
SIGNAL('sync(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
self.dispatch_sync_event)
self._sync_menu.fetch_annotations.connect(self.fetch_annotations)
self._sync_menu.connect_to_folder.connect(self.connect_to_folder)
self._sync_menu.disconnect_from_folder.connect(self.disconnect_from_folder)
if self.device_connected:
self._sync_menu.connect_to_folder_action.setEnabled(False)
if self.device_connected == 'folder':
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
else:
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
else:
self._sync_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
def device_job_exception(self, job):
'''
Handle exceptions in threaded device jobs.
'''
if isinstance(getattr(job, 'exception', None), UserFeedback):
ex = job.exception
func = {UserFeedback.ERROR:error_dialog,
UserFeedback.WARNING:warning_dialog,
UserFeedback.INFO:info_dialog}[ex.level]
return func(self, _('Failed'), ex.msg, det_msg=ex.details if
ex.details else '', show=True)
try:
if 'Could not read 32 bytes on the control bus.' in \
unicode(job.details):
error_dialog(self, _('Error talking to device'),
_('There was a temporary error talking to the '
'device. Please unplug and reconnect the device '
'and or reboot.')).show()
return
except:
pass
try:
prints(job.details, file=sys.stderr)
except:
pass
if not self.device_error_dialog.isVisible():
self.device_error_dialog.setDetailedText(job.details)
self.device_error_dialog.show()
# Device connected {{{
def device_detected(self, connected, is_folder_device):
'''
Called when a device is connected to the computer.
'''
if connected:
self._sync_menu.connect_to_folder_action.setEnabled(False)
if is_folder_device:
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
self.device_manager.get_device_information(\
Dispatcher(self.info_read))
self.set_default_thumbnail(\
self.device_manager.device.THUMBNAIL_HEIGHT)
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'
self._sync_menu.enable_device_actions(True,
self.device_manager.device.card_prefix(),
self.device_manager.device)
self.location_view.model().device_connected(self.device_manager.device)
self.eject_action.setEnabled(True)
self.refresh_ondevice_info (device_connected = True, reset_only = True)
else:
self._sync_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
self.device_connected = None
self._sync_menu.enable_device_actions(False)
self.location_view.model().update_devices()
self.vanity.setText(self.vanity_template%\
dict(version=self.latest_version, device=' '))
self.device_info = ' '
if self.current_view() != self.library_view:
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)
def info_read(self, job):
'''
Called once device information has been read.
'''
if job.failed:
return self.device_job_exception(job)
info, cp, fs = job.result
self.location_view.model().update_devices(cp, fs)
self.device_info = _('Connected ')+info[0]
self.vanity.setText(self.vanity_template%\
dict(version=self.latest_version, device=self.device_info))
self.device_manager.books(Dispatcher(self.metadata_downloaded))
def metadata_downloaded(self, job):
'''
Called once metadata has been read for all books on the device.
'''
if job.failed:
self.device_job_exception(job)
return
self.set_books_in_library(job.result, reset=True)
mainlist, cardalist, cardblist = job.result
self.memory_view.set_database(mainlist)
self.memory_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
self.card_a_view.set_database(cardalist)
self.card_a_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
self.card_b_view.set_database(cardblist)
self.card_b_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
self.sync_news()
self.sync_catalogs()
self.refresh_ondevice_info(device_connected = True)
def refresh_ondevice_info(self, device_connected, reset_only = False):
'''
Force the library view to refresh, taking into consideration
books information
'''
self.book_on_device(None, reset=True)
if reset_only:
return
self.library_view.set_device_connected(device_connected)
# }}}
def remove_paths(self, paths):
return self.device_manager.delete_books(
Dispatcher(self.books_deleted), paths)
def books_deleted(self, job):
'''
Called once deletion is done on the device
'''
for view in (self.memory_view, self.card_a_view, self.card_b_view):
view.model().deletion_done(job, job.failed)
if job.failed:
self.device_job_exception(job)
return
if self.delete_memory.has_key(job):
paths, model = self.delete_memory.pop(job)
self.device_manager.remove_books_from_metadata(paths,
self.booklists())
model.paths_deleted(paths)
self.upload_booklists()
# Clear the ondevice info so it will be recomputed
self.book_on_device(None, None, reset=True)
# We want to reset all the ondevice flags in the library. Use a big
# hammer, so we don't need to worry about whether some succeeded or not
self.library_view.model().refresh()
def dispatch_sync_event(self, dest, delete, specific): def dispatch_sync_event(self, dest, delete, specific):
rows = self.library_view.selectionModel().selectedRows() rows = self.library_view.selectionModel().selectedRows()
@ -1220,3 +1416,6 @@ class DeviceMixin(object):
# Correct the metadata cache on device. # Correct the metadata cache on device.
if self.device_manager.is_device_connected: if self.device_manager.is_device_connected:
self.device_manager.sync_booklists(None, booklists) self.device_manager.sync_booklists(None, booklists)
# }}}

View File

@ -47,7 +47,7 @@ class ToolbarMixin(object): # {{{
def __init__(self): def __init__(self):
md = QMenu() md = QMenu()
md.addAction(_('Edit metadata individually'), md.addAction(_('Edit metadata individually'),
partial(self.edit_metadata, False)) partial(self.edit_metadata, False, bulk=False))
md.addSeparator() md.addSeparator()
md.addAction(_('Edit metadata in bulk'), md.addAction(_('Edit metadata in bulk'),
partial(self.edit_metadata, False, bulk=True)) partial(self.edit_metadata, False, bulk=True))

View File

@ -85,7 +85,9 @@ typedef long PFreal;
typedef unsigned short QRgb565; typedef unsigned short QRgb565;
#define FONT_SIZE 18 #define REFLECTION_FACTOR 1.5
#define MAX(x, y) ((x > y) ? x : y)
#define RGB565_RED_MASK 0xF800 #define RGB565_RED_MASK 0xF800
#define RGB565_GREEN_MASK 0x07E0 #define RGB565_GREEN_MASK 0x07E0
@ -124,6 +126,7 @@ inline PFreal floatToFixed(float val)
return (PFreal)(val*PFREAL_ONE); return (PFreal)(val*PFREAL_ONE);
} }
// sinTable {{{
#define IANGLE_MAX 1024 #define IANGLE_MAX 1024
#define IANGLE_MASK 1023 #define IANGLE_MASK 1023
@ -293,6 +296,7 @@ int main(int, char**)
return 0; return 0;
} }
#endif #endif
// }}}
inline PFreal fsin(int iangle) inline PFreal fsin(int iangle)
{ {
@ -315,6 +319,8 @@ struct SlideInfo
PFreal cy; PFreal cy;
}; };
// PicturePlowPrivate {{{
class PictureFlowPrivate class PictureFlowPrivate
{ {
public: public:
@ -369,6 +375,7 @@ private:
int slideWidth; int slideWidth;
int slideHeight; int slideHeight;
int fontSize;
int zoom; int zoom;
int queueLength; int queueLength;
@ -406,6 +413,7 @@ PictureFlowPrivate::PictureFlowPrivate(PictureFlow* w, int queueLength_)
slideWidth = 200; slideWidth = 200;
slideHeight = 200; slideHeight = 200;
fontSize = 10;
zoom = 100; zoom = 100;
centerIndex = 0; centerIndex = 0;
@ -542,8 +550,11 @@ void PictureFlowPrivate::showSlide(int index)
void PictureFlowPrivate::resize(int w, int h) void PictureFlowPrivate::resize(int w, int h)
{ {
slideHeight = int(float(h)/2.); if (w < 10) w = 10;
if (h < 10) h = 10;
slideHeight = int(float(h)/REFLECTION_FACTOR);
slideWidth = int(float(slideHeight) * 2/3.); slideWidth = int(float(slideHeight) * 2/3.);
fontSize = MAX(int(h/20.), 12);
recalc(w, h); recalc(w, h);
resetSlides(); resetSlides();
triggerRender(); triggerRender();
@ -592,8 +603,8 @@ static QImage prepareSurface(QImage img, int w, int h)
img = img.scaled(w, h, Qt::IgnoreAspectRatio, mode); img = img.scaled(w, h, Qt::IgnoreAspectRatio, mode);
// slightly larger, to accomodate for the reflection // slightly larger, to accomodate for the reflection
int hs = h * 2; int hs = int(h * REFLECTION_FACTOR);
int hofs = h / 3; int hofs = 0;
// offscreen buffer: black is sweet // offscreen buffer: black is sweet
QImage result(hs, w, QImage::Format_RGB16); QImage result(hs, w, QImage::Format_RGB16);
@ -715,13 +726,13 @@ void PictureFlowPrivate::render()
QFont font = QFont(); QFont font = QFont();
font.setBold(true); font.setBold(true);
font.setPointSize(FONT_SIZE); font.setPixelSize(fontSize);
painter.setFont(font); painter.setFont(font);
painter.setPen(Qt::white); painter.setPen(Qt::white);
//painter.setPen(QColor(255,255,255,127)); //painter.setPen(QColor(255,255,255,127));
if (centerIndex < slideCount() && centerIndex > -1) if (centerIndex < slideCount() && centerIndex > -1)
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2-FONT_SIZE*3), painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2-fontSize*3),
Qt::AlignCenter, slideImages->caption(centerIndex)); Qt::AlignCenter, slideImages->caption(centerIndex));
painter.end(); painter.end();
@ -766,7 +777,7 @@ void PictureFlowPrivate::render()
QFont font = QFont(); QFont font = QFont();
font.setBold(true); font.setBold(true);
font.setPointSize(FONT_SIZE); font.setPixelSize(fontSize);
painter.setFont(font); painter.setFont(font);
int leftTextIndex = (step>0) ? centerIndex : centerIndex-1; int leftTextIndex = (step>0) ? centerIndex : centerIndex-1;
@ -774,12 +785,12 @@ void PictureFlowPrivate::render()
painter.setPen(QColor(255,255,255, (255-fade) )); painter.setPen(QColor(255,255,255, (255-fade) ));
if (leftTextIndex < sc && leftTextIndex > -1) if (leftTextIndex < sc && leftTextIndex > -1)
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - FONT_SIZE*3), painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*3),
Qt::AlignCenter, slideImages->caption(leftTextIndex)); Qt::AlignCenter, slideImages->caption(leftTextIndex));
painter.setPen(QColor(255,255,255, fade)); painter.setPen(QColor(255,255,255, fade));
if (leftTextIndex+1 < sc && leftTextIndex > -2) if (leftTextIndex+1 < sc && leftTextIndex > -2)
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - FONT_SIZE*3), painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*3),
Qt::AlignCenter, slideImages->caption(leftTextIndex+1)); Qt::AlignCenter, slideImages->caption(leftTextIndex+1));
@ -893,7 +904,7 @@ int col1, int col2)
int center = (sh*BILINEAR_STRETCH_VER/2); int center = (sh*BILINEAR_STRETCH_VER/2);
int dy = dist*BILINEAR_STRETCH_VER / h; int dy = dist*BILINEAR_STRETCH_VER / h;
#else #else
int center = (sh/2); int center = sh/2;
int dy = dist / h; int dy = dist / h;
#endif #endif
int p1 = center*PFREAL_ONE - dy/2; int p1 = center*PFREAL_ONE - dy/2;
@ -1110,8 +1121,9 @@ void PictureFlowPrivate::clearSurfaceCache()
surfaceCache.clear(); surfaceCache.clear();
} }
// ----------------------------------------- // }}}
// PictureFlow {{{
PictureFlow::PictureFlow(QWidget* parent, int queueLength): QWidget(parent) PictureFlow::PictureFlow(QWidget* parent, int queueLength): QWidget(parent)
{ {
d = new PictureFlowPrivate(this, queueLength); d = new PictureFlowPrivate(this, queueLength);
@ -1387,3 +1399,5 @@ void PictureFlow::emitcurrentChanged(int index) { emit currentChanged(index); }
int FlowImages::count() { return 0; } int FlowImages::count() { return 0; }
QImage FlowImages::image(int index) { index=0; return QImage(); } QImage FlowImages::image(int index) { index=0; return QImage(); }
QString FlowImages::caption(int index) {index=0; return QString(); } QString FlowImages::caption(int index) {index=0; return QString(); }
// }}}

View File

@ -2,9 +2,10 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, collections import os, collections
from PyQt4.QtGui import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \ from PyQt4.Qt import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \
QSizePolicy, QScrollArea QSizePolicy, QScrollArea, Qt, QSize, pyqtSignal, \
from PyQt4.QtCore import Qt, QSize, pyqtSignal QPropertyAnimation, QEasingCurve
from calibre import fit_image, preferred_encoding, isosx from calibre import fit_image, preferred_encoding, isosx
from calibre.gui2 import config from calibre.gui2 import config
@ -50,6 +51,10 @@ class BookInfoDisplay(QWidget):
def __init__(self, coverpath=I('book.svg')): def __init__(self, coverpath=I('book.svg')):
QLabel.__init__(self) QLabel.__init__(self)
self.animation = QPropertyAnimation(self, 'size', self)
self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo))
self.animation.setDuration(1000)
self.animation.setStartValue(QSize(0, 0))
self.setMaximumWidth(81) self.setMaximumWidth(81)
self.setMaximumHeight(108) self.setMaximumHeight(108)
self.default_pixmap = QPixmap(coverpath) self.default_pixmap = QPixmap(coverpath)
@ -58,6 +63,7 @@ class BookInfoDisplay(QWidget):
self.setPixmap(self.default_pixmap) self.setPixmap(self.default_pixmap)
def do_layout(self): def do_layout(self):
self.animation.stop()
pixmap = self.pixmap() pixmap = self.pixmap()
pwidth, pheight = pixmap.width(), pixmap.height() pwidth, pheight = pixmap.width(), pixmap.height()
width, height = fit_image(pwidth, pheight, width, height = fit_image(pwidth, pheight,
@ -68,11 +74,12 @@ class BookInfoDisplay(QWidget):
except ZeroDivisionError: except ZeroDivisionError:
aspect_ratio = 1 aspect_ratio = 1
self.setMaximumWidth(int(aspect_ratio*self.maximumHeight())) self.setMaximumWidth(int(aspect_ratio*self.maximumHeight()))
self.animation.setEndValue(self.maximumSize())
def setPixmap(self, pixmap): def setPixmap(self, pixmap):
QLabel.setPixmap(self, pixmap) QLabel.setPixmap(self, pixmap)
self.do_layout() self.do_layout()
self.animation.start()
def sizeHint(self): def sizeHint(self):
return QSize(self.maximumWidth(), self.maximumHeight()) return QSize(self.maximumWidth(), self.maximumHeight())

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,13 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import traceback import traceback
from PyQt4.QtCore import QThread, pyqtSignal from PyQt4.Qt import QThread, pyqtSignal, QDesktopServices, QUrl, Qt
import mechanize import mechanize
from calibre.constants import __version__, iswindows, isosx from calibre.constants import __appname__, __version__, iswindows, isosx
from calibre import browser from calibre import browser
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.gui2 import config, dynamic, question_dialog
URL = 'http://status.calibre-ebook.com/latest' URL = 'http://status.calibre-ebook.com/latest'
@ -36,3 +37,35 @@ class CheckForUpdates(QThread):
traceback.print_exc() traceback.print_exc()
self.sleep(self.INTERVAL) self.sleep(self.INTERVAL)
class UpdateMixin(object):
def __init__(self, opts):
if not opts.no_update_check:
self.update_checker = CheckForUpdates(self)
self.update_checker.update_found.connect(self.update_found,
type=Qt.QueuedConnection)
self.update_checker.start()
def update_found(self, version):
os = 'windows' if iswindows else 'osx' if isosx else 'linux'
url = 'http://calibre-ebook.com/download_%s'%os
self.latest_version = '<br>' + _('<span style="color:red; font-weight:bold">'
'Latest version: <a href="%s">%s</a></span>')%(url, version)
self.vanity.setText(self.vanity_template%\
(dict(version=self.latest_version,
device=self.device_info)))
self.vanity.update()
if config.get('new_version_notification') and \
dynamic.get('update to version %s'%version, True):
if question_dialog(self, _('Update available'),
_('%s has been updated to version %s. '
'See the <a href="http://calibre-ebook.com/whats-new'
'">new features</a>. Visit the download pa'
'ge?')%(__appname__, version)):
url = 'http://calibre-ebook.com/download_'+\
('windows' if iswindows else 'osx' if isosx else 'linux')
QDesktopServices.openUrl(QUrl(url))
dynamic.set('update to version %s'%version, False)

View File

@ -10,14 +10,14 @@ import collections, glob, os, re, itertools, functools
from itertools import repeat from itertools import repeat
from datetime import timedelta from datetime import timedelta
from PyQt4.QtCore import QThread, QReadWriteLock from PyQt4.Qt import QThread, QReadWriteLock, QImage, Qt
from PyQt4.QtGui import QImage
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.utils.date import parse_date, now, UNDEFINED_DATE from calibre.utils.date import parse_date, now, UNDEFINED_DATE
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata import title_sort
from calibre import fit_image
class CoverCache(QThread): class CoverCache(QThread):
@ -96,6 +96,11 @@ class CoverCache(QThread):
img.loadFromData(data) img.loadFromData(data)
if img.isNull(): if img.isNull():
continue continue
scaled, nwidth, nheight = fit_image(img.width(),
img.height(), 600, 800)
if scaled:
img = img.scaled(nwidth, nheight, Qt.KeepAspectRatio,
Qt.SmoothTransformation)
except: except:
continue continue
self.cache_lock.lockForWrite() self.cache_lock.lockForWrite()

View File

@ -111,7 +111,7 @@ Pre/post processing of downloaded HTML
.. automember:: BasicNewsRecipe.remove_javascript .. automember:: BasicNewsRecipe.remove_javascript
.. automethod:: BasicNewsRecipe.prepreprocess_html .. automethod:: BasicNewsRecipe.skip_ad_pages
.. automethod:: BasicNewsRecipe.preprocess_html .. automethod:: BasicNewsRecipe.preprocess_html

View File

@ -175,7 +175,7 @@ def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
p.DestroyMagickWand(canvas) p.DestroyMagickWand(canvas)
def create_cover_page(top_lines, logo_path, width=590, height=750, def create_cover_page(top_lines, logo_path, width=590, height=750,
bgcolor='white', output_format='png'): bgcolor='white', output_format='jpg'):
ans = None ans = None
with p.ImageMagick(): with p.ImageMagick():
canvas = create_canvas(width, height, bgcolor) canvas = create_canvas(width, height, bgcolor)

View File

@ -413,18 +413,19 @@ class BasicNewsRecipe(Recipe):
return url return url
return article.get('link', None) return article.get('link', None)
def prepreprocess_html(self, soup): def skip_ad_pages(self, soup):
''' '''
This method is called with the source of each downloaded :term:`HTML` file, before This method is called with the source of each downloaded :term:`HTML` file, before
any of the cleanup attributes like remove_tags, keep_only_tags are any of the cleanup attributes like remove_tags, keep_only_tags are
applied. Note that preprocess_regexps will have already been applied. applied. Note that preprocess_regexps will have already been applied.
It can be used to do arbitrarily powerful pre-processing on the :term:`HTML`. It is meant to allow the recipe to skip ad pages. If the soup represents
It should return `soup` after processing it. an ad page, return the HTML of the real page. Otherwise return
None.
`soup`: A `BeautifulSoup <http://www.crummy.com/software/BeautifulSoup/documentation.html>`_ `soup`: A `BeautifulSoup <http://www.crummy.com/software/BeautifulSoup/documentation.html>`_
instance containing the downloaded :term:`HTML`. instance containing the downloaded :term:`HTML`.
''' '''
return soup return None
def preprocess_html(self, soup): def preprocess_html(self, soup):
@ -628,7 +629,7 @@ class BasicNewsRecipe(Recipe):
self.web2disk_options = web2disk_option_parser().parse_args(web2disk_cmdline)[0] self.web2disk_options = web2disk_option_parser().parse_args(web2disk_cmdline)[0]
for extra in ('keep_only_tags', 'remove_tags', 'preprocess_regexps', for extra in ('keep_only_tags', 'remove_tags', 'preprocess_regexps',
'prepreprocess_html', 'preprocess_html', 'remove_tags_after', 'skip_ad_pages', 'preprocess_html', 'remove_tags_after',
'remove_tags_before', 'is_link_wanted'): 'remove_tags_before', 'is_link_wanted'):
setattr(self.web2disk_options, extra, getattr(self, extra)) setattr(self.web2disk_options, extra, getattr(self, extra))
self.web2disk_options.postprocess_html = self._postprocess_html self.web2disk_options.postprocess_html = self._postprocess_html
@ -801,11 +802,6 @@ class BasicNewsRecipe(Recipe):
.calibre_navbar { .calibre_navbar {
font-family:monospace; font-family:monospace;
} }
hr {
border-color:gray;
border-style:solid;
border-width:thin;
}
''' '''

View File

@ -108,7 +108,7 @@ class TouchscreenNavBarTemplate(Template):
navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_100', navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_100',
style='text-align:'+align)) style='text-align:'+align))
if bottom: if bottom:
navbar.append(HR()) navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white"))
text = 'This article was downloaded by ' text = 'This article was downloaded by '
p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left')
p[0].tail = ' from ' p[0].tail = ' from '
@ -136,7 +136,7 @@ class TouchscreenNavBarTemplate(Template):
navbar.iterchildren(reversed=True).next().tail = ' | ' navbar.iterchildren(reversed=True).next().tail = ' | '
if not bottom: if not bottom:
navbar.append(HR()) navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white"))
self.root = HTML(head, BODY(navbar)) self.root = HTML(head, BODY(navbar))
@ -193,6 +193,8 @@ class TouchscreenIndexTemplate(Template):
div = DIV( div = DIV(
masthead_p, masthead_p,
PT(date, style='text-align:center'), PT(date, style='text-align:center'),
#DIV(style="border-color:gray;border-top-style:solid;border-width:thin"),
DIV(style="border-top:1px solid gray;border-bottom:1em solid white"),
toc) toc)
self.root = HTML(head, BODY(div)) self.root = HTML(head, BODY(div))
@ -256,9 +258,8 @@ class TouchscreenFeedTemplate(Template):
head.append(STYLE(extra_css, type='text/css')) head.append(STYLE(extra_css, type='text/css'))
body = BODY(style='page-break-before:always') body = BODY(style='page-break-before:always')
div = DIV( div = DIV(
H2(feed.title, H2(feed.title, CLASS('calibre_feed_title', 'calibre_rescale_160')),
CLASS('calibre_feed_title', 'calibre_rescale_160')), DIV(style="border-top:1px solid gray;border-bottom:1em solid white")
CLASS('calibre_rescale_100')
) )
body.append(div) body.append(div)
if getattr(feed, 'image', None): if getattr(feed, 'image', None):
@ -278,6 +279,20 @@ class TouchscreenFeedTemplate(Template):
if not getattr(article, 'downloaded', False): if not getattr(article, 'downloaded', False):
continue continue
tr = TR() tr = TR()
if True:
div_td = DIV(
A(article.title, CLASS('summary_headline','calibre_rescale_120',
href=article.url)),
style="display:inline-block")
if article.author:
div_td.append(DIV(article.author,
CLASS('summary_byline', 'calibre_rescale_100')))
if article.summary:
div_td.append(DIV(cutoff(article.text_summary),
CLASS('summary_text', 'calibre_rescale_100')))
tr.append(TD(div_td))
else:
td = TD( td = TD(
A(article.title, CLASS('summary_headline','calibre_rescale_120', A(article.title, CLASS('summary_headline','calibre_rescale_120',
href=article.url)) href=article.url))
@ -288,7 +303,9 @@ class TouchscreenFeedTemplate(Template):
if article.summary: if article.summary:
td.append(DIV(cutoff(article.text_summary), td.append(DIV(cutoff(article.text_summary),
CLASS('summary_text', 'calibre_rescale_100'))) CLASS('summary_text', 'calibre_rescale_100')))
tr.append(td) tr.append(td)
toc.append(tr) toc.append(tr)
div.append(toc) div.append(toc)

View File

@ -136,7 +136,7 @@ class RecursiveFetcher(object):
self.remove_tags_before = getattr(options, 'remove_tags_before', None) self.remove_tags_before = getattr(options, 'remove_tags_before', None)
self.keep_only_tags = getattr(options, 'keep_only_tags', []) self.keep_only_tags = getattr(options, 'keep_only_tags', [])
self.preprocess_html_ext = getattr(options, 'preprocess_html', lambda soup: soup) self.preprocess_html_ext = getattr(options, 'preprocess_html', lambda soup: soup)
self.prepreprocess_html_ext = getattr(options, 'prepreprocess_html', lambda soup: soup) self.prepreprocess_html_ext = getattr(options, 'skip_ad_pages', lambda soup: None)
self.postprocess_html_ext= getattr(options, 'postprocess_html', None) self.postprocess_html_ext= getattr(options, 'postprocess_html', None)
self._is_link_wanted = getattr(options, 'is_link_wanted', self._is_link_wanted = getattr(options, 'is_link_wanted',
default_is_link_wanted) default_is_link_wanted)
@ -154,7 +154,9 @@ class RecursiveFetcher(object):
nmassage.append((re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')) nmassage.append((re.compile(r'<!--.*?-->', re.DOTALL), lambda m: ''))
soup = BeautifulSoup(xml_to_unicode(src, self.verbose, strip_encoding_pats=True)[0], markupMassage=nmassage) soup = BeautifulSoup(xml_to_unicode(src, self.verbose, strip_encoding_pats=True)[0], markupMassage=nmassage)
soup = self.prepreprocess_html_ext(soup) replace = self.prepreprocess_html_ext(soup)
if replace is not None:
soup = BeautifulSoup(xml_to_unicode(src, 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')