This commit is contained in:
GRiker 2012-08-04 10:43:16 -06:00
commit 9d22a3d22a
8 changed files with 129 additions and 80 deletions

View File

@ -18,15 +18,15 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
keep_only_tags = [ keep_only_tags = [
dict(name='h1'), dict(name='h1'),
dict(name='img',attrs={'id' : 'ctl00_Body_imgMainImage'}), dict(name='img',attrs={'id' : 'ctl00_Body_imgMainImage'}),
dict(name='div',attrs={'id' : ['articleLeft']}), dict(name='div',attrs={'id' : ['profileLeft','articleLeft','profileRight','profileBody']}),
dict(name='div',attrs={'class' : ['imagesCenterArticle','containerCenterArticle','articleBody']}), dict(name='div',attrs={'class' : ['imagesCenterArticle','containerCenterArticle','articleBody',]}),
] ]
#remove_tags = [ remove_tags = [
#dict(attrs={'class' : ['player']}), dict(attrs={'id' : ['ctl00_Body_divSlideShow' ]}),
#] ]
feeds = [ feeds = [
(u'Homepage 1',u'http://feed43.com/6655867614547036.xml'), (u'Homepage 1',u'http://feed43.com/6655867614547036.xml'),
(u'Homepage 2',u'http://feed43.com/4167731873103110.xml'), (u'Homepage 2',u'http://feed43.com/4167731873103110.xml'),
@ -34,7 +34,7 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
(u'Homepage 4',u'http://feed43.com/6550421522527341.xml'), (u'Homepage 4',u'http://feed43.com/6550421522527341.xml'),
(u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'), (u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'),
(u'Gaming',u'http://feed43.com/6537162612465672.xml'), (u'Gaming',u'http://feed43.com/6537162612465672.xml'),
(u'Girls',u'http://feed43.com/3674777224513254.xml'), (u'Girls',u'http://feed43.com/4574262733341068.xml'),# edit link http://feed43.com/feed.html?name=4574262733341068
] ]
extra_css = ''' extra_css = '''

View File

@ -101,7 +101,7 @@ class POCKETBOOK360(EB600):
VENDOR_NAME = ['PHILIPS', '__POCKET', 'POCKETBO'] VENDOR_NAME = ['PHILIPS', '__POCKET', 'POCKETBO']
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['MASS_STORGE', 'BOOK_USB_STORAGE', WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['MASS_STORGE', 'BOOK_USB_STORAGE',
'OK_POCKET_611_61'] 'OK_POCKET_611_61', 'OK_POCKET_360+61']
OSX_MAIN_MEM = OSX_CARD_A_MEM = 'Philips Mass Storge Media' OSX_MAIN_MEM = OSX_CARD_A_MEM = 'Philips Mass Storge Media'
OSX_MAIN_MEM_VOL_PAT = re.compile(r'/Pocket') OSX_MAIN_MEM_VOL_PAT = re.compile(r'/Pocket')

View File

@ -48,6 +48,19 @@ class OpenFeedback(DeviceError):
''' '''
raise NotImplementedError raise NotImplementedError
class InitialConnectionError(OpenFeedback):
""" Errors detected during connection after detection but before open, for
e.g. in the is_connected() method. """
class OpenFailed(ProtocolError):
""" Raised when device cannot be opened this time. No retry is to be done.
The device should continue to be polled for future opens. If the
message is empty, no exception trace is produced. """
def __init__(self, msg):
ProtocolError.__init__(self, msg)
self.show_me = bool(msg and msg.strip())
class DeviceBusy(ProtocolError): class DeviceBusy(ProtocolError):
""" Raised when device is busy """ """ Raised when device is busy """
def __init__(self, uerr=""): def __init__(self, uerr=""):

View File

@ -14,7 +14,8 @@ from functools import wraps
from calibre import prints from calibre import prints
from calibre.constants import numeric_version, DEBUG from calibre.constants import numeric_version, DEBUG
from calibre.devices.errors import OpenFeedback from calibre.devices.errors import (OpenFailed, ControlError, TimeoutError,
InitialConnectionError)
from calibre.devices.interface import DevicePlugin from calibre.devices.interface import DevicePlugin
from calibre.devices.usbms.books import Book, BookList from calibre.devices.usbms.books import Book, BookList
from calibre.devices.usbms.deviceconfig import DeviceConfig from calibre.devices.usbms.deviceconfig import DeviceConfig
@ -82,6 +83,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
BASE_PACKET_LEN = 4096 BASE_PACKET_LEN = 4096
PROTOCOL_VERSION = 1 PROTOCOL_VERSION = 1
MAX_CLIENT_COMM_TIMEOUT = 60.0 # Wait at most N seconds for an answer MAX_CLIENT_COMM_TIMEOUT = 60.0 # Wait at most N seconds for an answer
MAX_UNSUCCESSFUL_CONNECTS = 5
opcodes = { opcodes = {
'NOOP' : 12, 'NOOP' : 12,
@ -353,24 +355,18 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._debug('protocol error -- empty json string') self._debug('protocol error -- empty json string')
except socket.timeout: except socket.timeout:
self._debug('timeout communicating with device') self._debug('timeout communicating with device')
self.device_socket.close() self._close_device_socket()
self.device_socket = None raise TimeoutError('Device did not respond in reasonable time')
self.is_connected = False
raise IOError(_('Device did not respond in reasonable time'))
except socket.error: except socket.error:
self._debug('device went away') self._debug('device went away')
self.device_socket.close() self._close_device_socket()
self.device_socket = None raise ControlError('Device closed the network connection')
self.is_connected = False
raise IOError(_('Device closed the network connection'))
except: except:
self._debug('other exception') self._debug('other exception')
traceback.print_exc() traceback.print_exc()
self.device_socket.close() self._close_device_socket()
self.device_socket = None
self.is_connected = False
raise raise
raise IOError('Device responded with incorrect information') raise ControlError('Device responded with incorrect information')
# Write a file as a series of base64-encoded strings. # Write a file as a series of base64-encoded strings.
def _put_file(self, infile, lpath, book_metadata, this_book, total_books): def _put_file(self, infile, lpath, book_metadata, this_book, total_books):
@ -449,8 +445,16 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
else: else:
self.known_metadata[lpath] = book.deepcopy() self.known_metadata[lpath] = book.deepcopy()
# The public interface methods. def _close_device_socket(self):
if self.device_socket is not None:
try:
self.device_socket.close()
except:
pass
self.device_socket = None
self.is_connected = False
# The public interface methods.
@synchronous('sync_lock') @synchronous('sync_lock')
def is_usb_connected(self, devices_on_system, debug=False, only_presence=False): def is_usb_connected(self, devices_on_system, debug=False, only_presence=False):
@ -471,11 +475,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
pass pass
try: try:
if self._call_client('NOOP', dict())[0] is None: if self._call_client('NOOP', dict())[0] is None:
self.is_connected = False self._close_device_socket()
except: except:
self.is_connected = False self._close_device_socket()
if not self.is_connected:
self.device_socket.close()
return (self.is_connected, self) return (self.is_connected, self)
if getattr(self, 'listen_socket', None) is not None: if getattr(self, 'listen_socket', None) is not None:
ans = select.select((self.listen_socket,), (), (), 0) ans = select.select((self.listen_socket,), (), (), 0)
@ -490,23 +492,35 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.listen_socket.settimeout(None) self.listen_socket.settimeout(None)
self.device_socket.settimeout(None) self.device_socket.settimeout(None)
self.is_connected = True self.is_connected = True
try:
peer = self.device_socket.getpeername()[0]
attempts = self.connection_attempts.get(peer, 0)
if attempts >= self.MAX_UNSUCCESSFUL_CONNECTS:
self._debug('too many connection attempts from', peer)
self._close_device_socket()
raise InitialConnectionError(_('Too many connection attempts from %s')%peer)
else:
self.connection_attempts[peer] = attempts + 1
except InitialConnectionError:
raise
except:
pass
except socket.timeout: except socket.timeout:
if self.device_socket is not None: self._close_device_socket()
self.device_socket.close()
self.is_connected = False
except socket.error: except socket.error:
x = sys.exc_info()[1] x = sys.exc_info()[1]
self._debug('unexpected socket exception', x.args[0]) self._debug('unexpected socket exception', x.args[0])
if self.device_socket is not None: self._close_device_socket()
self.device_socket.close()
self.is_connected = False
raise raise
return (True, self) return (self.is_connected, self)
return (False, None) return (False, None)
@synchronous('sync_lock') @synchronous('sync_lock')
def open(self, connected_device, library_uuid): def open(self, connected_device, library_uuid):
self._debug() self._debug()
if not self.is_connected:
# We have been called to retry the connection. Give up immediately
raise ControlError('Attempt to open a closed device')
self.current_library_uuid = library_uuid self.current_library_uuid = library_uuid
self.current_library_name = current_library_name() self.current_library_name = current_library_name()
try: try:
@ -530,28 +544,24 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
# Something wrong with the return. Close the socket # Something wrong with the return. Close the socket
# and continue. # and continue.
self._debug('Protocol error - Opcode not OK') self._debug('Protocol error - Opcode not OK')
self.device_socket.close() self._close_device_socket()
self.is_connected = False
return False return False
if not result.get('versionOK', False): if not result.get('versionOK', False):
# protocol mismatch # protocol mismatch
self._debug('Protocol error - protocol version mismatch') self._debug('Protocol error - protocol version mismatch')
self.device_socket.close() self._close_device_socket()
self.is_connected = False
return False return False
if result.get('maxBookContentPacketLen', 0) <= 0: if result.get('maxBookContentPacketLen', 0) <= 0:
# protocol mismatch # protocol mismatch
self._debug('Protocol error - bogus book packet length') self._debug('Protocol error - bogus book packet length')
self.device_socket.close() self._close_device_socket()
self.is_connected = False
return False return False
self.max_book_packet_len = result.get('maxBookContentPacketLen', self.max_book_packet_len = result.get('maxBookContentPacketLen',
self.BASE_PACKET_LEN) self.BASE_PACKET_LEN)
exts = result.get('acceptedExtensions', None) exts = result.get('acceptedExtensions', None)
if exts is None or not isinstance(exts, list) or len(exts) == 0: if exts is None or not isinstance(exts, list) or len(exts) == 0:
self._debug('Protocol error - bogus accepted extensions') self._debug('Protocol error - bogus accepted extensions')
self.device_socket.close() self._close_device_socket()
self.is_connected = False
return False return False
self.FORMATS = exts self.FORMATS = exts
if password: if password:
@ -559,25 +569,34 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
if result.get('passwordHash', None) is None: if result.get('passwordHash', None) is None:
# protocol mismatch # protocol mismatch
self._debug('Protocol error - missing password hash') self._debug('Protocol error - missing password hash')
self.device_socket.close() self._close_device_socket()
self.is_connected = False
return False return False
if returned_hash != hash_digest: if returned_hash != hash_digest:
# bad password # bad password
self._debug('password mismatch') self._debug('password mismatch')
self._call_client("DISPLAY_MESSAGE", {'messageKind':1}) try:
self.is_connected = False self._call_client("DISPLAY_MESSAGE",
self.device_socket.close() {'messageKind':1,
raise OpenFeedback('Incorrect password supplied') 'currentLibraryName': self.current_library_name,
'currentLibraryUUID': library_uuid})
except:
pass
self._close_device_socket()
# Don't bother with a message. The user will be informed on
# the device.
raise OpenFailed('')
try:
peer = self.device_socket.getpeername()
self.connection_attempts[peer] = 0
except:
pass
return True return True
except socket.timeout: except socket.timeout:
self.device_socket.close() self._close_device_socket()
self.is_connected = False
except socket.error: except socket.error:
x = sys.exc_info()[1] x = sys.exc_info()[1]
self._debug('unexpected socket exception', x.args[0]) self._debug('unexpected socket exception', x.args[0])
self.device_socket.close() self._close_device_socket()
self.is_connected = False
raise raise
return False return False
@ -656,7 +675,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._set_known_metadata(book) self._set_known_metadata(book)
bl.add_book(book, replace_metadata=True) bl.add_book(book, replace_metadata=True)
else: else:
raise IOError(_('Protocol error -- book metadata not returned')) raise ControlError('book metadata not returned')
return bl return bl
@synchronous('sync_lock') @synchronous('sync_lock')
@ -675,15 +694,12 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
print_debug_info=False) print_debug_info=False)
if opcode != 'OK': if opcode != 'OK':
self._debug('protocol error', opcode, i) self._debug('protocol error', opcode, i)
raise IOError(_('Protocol error -- sync_booklists')) raise ControlError('sync_booklists')
@synchronous('sync_lock') @synchronous('sync_lock')
def eject(self): def eject(self):
self._debug() self._debug()
if self.device_socket: self._close_device_socket()
self.device_socket.close()
self.device_socket = None
self.is_connected = False
@synchronous('sync_lock') @synchronous('sync_lock')
def post_yank_cleanup(self): def post_yank_cleanup(self):
@ -706,7 +722,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
book = Book(self.PREFIX, lpath, other=mdata) book = Book(self.PREFIX, lpath, other=mdata)
length = self._put_file(infile, lpath, book, i, len(files)) length = self._put_file(infile, lpath, book, i, len(files))
if length < 0: if length < 0:
raise IOError(_('Sending book %s to device failed') % lpath) raise ControlError('Sending book %s to device failed' % lpath)
paths.append((lpath, length)) paths.append((lpath, length))
# No need to deal with covers. The client will get the thumbnails # No need to deal with covers. The client will get the thumbnails
# in the mi structure # in the mi structure
@ -747,7 +763,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
if opcode == 'OK': if opcode == 'OK':
self._debug('removed book with UUID', result['uuid']) self._debug('removed book with UUID', result['uuid'])
else: else:
raise IOError(_('Protocol error - delete books')) raise ControlError('Protocol error - delete books')
@synchronous('sync_lock') @synchronous('sync_lock')
def remove_books_from_metadata(self, paths, booklists): def remove_books_from_metadata(self, paths, booklists):
@ -783,7 +799,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
else: else:
eof = True eof = True
else: else:
raise IOError(_('request for book data failed')) raise ControlError('request for book data failed')
@synchronous('sync_lock') @synchronous('sync_lock')
def set_plugboards(self, plugboards, pb_func): def set_plugboards(self, plugboards, pb_func):
@ -811,6 +827,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.debug_start_time = time.time() self.debug_start_time = time.time()
self.max_book_packet_len = 0 self.max_book_packet_len = 0
self.noop_counter = 0 self.noop_counter = 0
self.connection_attempts = {}
try: try:
self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except: except:

View File

@ -7,11 +7,10 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re import re, unicodedata
from calibre.ebooks.oeb.base import (OEB_DOCS, XHTML, XHTML_NS, XML_NS, from calibre.ebooks.oeb.base import (OEB_DOCS, XHTML, XHTML_NS, XML_NS,
namespace, prefixname, urlnormalize) namespace, prefixname, urlnormalize)
from calibre.ebooks import normalize
from calibre.ebooks.mobi.mobiml import MBP_NS from calibre.ebooks.mobi.mobiml import MBP_NS
from calibre.ebooks.mobi.utils import is_guide_ref_start from calibre.ebooks.mobi.utils import is_guide_ref_start
@ -356,7 +355,9 @@ class Serializer(object):
text = text.replace(u'\u00AD', '') # Soft-hyphen text = text.replace(u'\u00AD', '') # Soft-hyphen
if quot: if quot:
text = text.replace('"', '&quot;') text = text.replace('"', '&quot;')
self.buf.write(normalize(text).encode('utf-8')) if isinstance(text, unicode):
text = unicodedata.normalize('NFC', text)
self.buf.write(text.encode('utf-8'))
def fixup_links(self): def fixup_links(self):
''' '''

View File

@ -13,7 +13,8 @@ from PyQt4.Qt import (QMenu, QAction, QActionGroup, QIcon, SIGNAL,
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, OpenFeedback from calibre.devices.errors import (UserFeedback, OpenFeedback, OpenFailed,
InitialConnectionError)
from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog
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
@ -172,6 +173,8 @@ class DeviceManager(Thread): # {{{
self.open_feedback_msg(dev.get_gui_name(), e) self.open_feedback_msg(dev.get_gui_name(), e)
self.ejected_devices.add(dev) self.ejected_devices.add(dev)
continue continue
except OpenFailed:
raise
except: except:
tb = traceback.format_exc() tb = traceback.format_exc()
if DEBUG or tb not in self.reported_errors: if DEBUG or tb not in self.reported_errors:
@ -225,24 +228,32 @@ class DeviceManager(Thread): # {{{
only_presence=True, debug=True) only_presence=True, debug=True)
self.connected_device_removed() self.connected_device_removed()
else: else:
possibly_connected_devices = [] try:
for device in self.devices: possibly_connected_devices = []
if device in self.ejected_devices: for device in self.devices:
continue if device in self.ejected_devices:
possibly_connected, detected_device = \ continue
self.scanner.is_device_connected(device) try:
if possibly_connected: possibly_connected, detected_device = \
possibly_connected_devices.append((device, detected_device)) self.scanner.is_device_connected(device)
if possibly_connected_devices: except InitialConnectionError as e:
if not self.do_connect(possibly_connected_devices, self.open_feedback_msg(device.get_gui_name(), e)
device_kind='device'): continue
if DEBUG: if possibly_connected:
prints('Connect to device failed, retrying in 5 seconds...') possibly_connected_devices.append((device, detected_device))
time.sleep(5) if possibly_connected_devices:
if not self.do_connect(possibly_connected_devices, if not self.do_connect(possibly_connected_devices,
device_kind='usb'): device_kind='device'):
if DEBUG: if DEBUG:
prints('Device connect failed again, giving up') prints('Connect to device failed, retrying in 5 seconds...')
time.sleep(5)
if not self.do_connect(possibly_connected_devices,
device_kind='usb'):
if DEBUG:
prints('Device connect failed again, giving up')
except OpenFailed as e:
if e.show_me:
traceback.print_exc()
# Mount devices that don't use USB, such as the folder device and iTunes # Mount devices that don't use USB, such as the folder device and iTunes
# This will be called on the GUI thread. Because of this, we must store # This will be called on the GUI thread. Because of this, we must store

View File

@ -568,6 +568,7 @@ class ResultCache(SearchQueryParser): # {{{
matches.add(id_) matches.add(id_)
continue continue
add_if_nothing_matches = valq == 'false'
pairs = [p.strip() for p in item[loc].split(split_char)] pairs = [p.strip() for p in item[loc].split(split_char)]
for pair in pairs: for pair in pairs:
parts = pair.split(':') parts = pair.split(':')
@ -583,10 +584,14 @@ class ResultCache(SearchQueryParser): # {{{
continue continue
elif valq == 'false': elif valq == 'false':
if v: if v:
add_if_nothing_matches = False
continue continue
elif not _match(valq, v, valq_mkind): elif not _match(valq, v, valq_mkind):
continue continue
matches.add(id_) matches.add(id_)
if add_if_nothing_matches:
matches.add(id_)
return matches return matches
def _matchkind(self, query): def _matchkind(self, query):

View File

@ -561,7 +561,9 @@ class OPDSServer(object):
if type_ != 'I': if type_ != 'I':
raise cherrypy.HTTPError(404, 'Non id categories not supported') raise cherrypy.HTTPError(404, 'Non id categories not supported')
ids = self.db.get_books_for_category(category, which) q = category
if q == 'news': q = 'tags'
ids = self.db.get_books_for_category(q, which)
sort_by = 'series' if category == 'series' else 'title' sort_by = 'series' if category == 'series' else 'title'
return self.get_opds_acquisition_feed(ids, offset, page_url, return self.get_opds_acquisition_feed(ids, offset, page_url,