Wireless driver: Remove old non-streaming netowrking code. Apps that still use it must be updated. A message will popup notifying the user of the need to update if such an app is encountered.

Wireless driver: Fix a bug that could cause the wrong path information to be sent to the device in some circumstances.

Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
Kovid Goyal 2013-11-12 18:26:16 +05:30
commit 5161246eb2

View File

@ -13,14 +13,13 @@ from collections import defaultdict
import hashlib, threading import hashlib, threading
import Queue import Queue
from base64 import b64encode, b64decode
from functools import wraps from functools import wraps
from errno import EAGAIN, EINTR from errno import EAGAIN, EINTR
from threading import Thread from threading import Thread
from calibre import prints from calibre import prints
from calibre.constants import numeric_version, DEBUG, cache_dir from calibre.constants import numeric_version, DEBUG, cache_dir
from calibre.devices.errors import (OpenFailed, ControlError, TimeoutError, from calibre.devices.errors import (OpenFailed, OpenFeedback, ControlError, TimeoutError,
InitialConnectionError, PacketError) InitialConnectionError, PacketError)
from calibre.devices.interface import DevicePlugin from calibre.devices.interface import DevicePlugin
from calibre.devices.usbms.books import Book, CollectionsBookList from calibre.devices.usbms.books import Book, CollectionsBookList
@ -159,6 +158,7 @@ class ConnectionListener(Thread):
class SDBook(Book): class SDBook(Book):
def __init__(self, prefix, lpath, size=None, other=None): def __init__(self, prefix, lpath, size=None, other=None):
Book.__init__(self, prefix, lpath, size=size, other=other) Book.__init__(self, prefix, lpath, size=size, other=other)
path = getattr(self, 'path', lpath) path = getattr(self, 'path', lpath)
@ -255,6 +255,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
} }
reverse_opcodes = dict([(v, k) for k,v in opcodes.iteritems()]) reverse_opcodes = dict([(v, k) for k,v in opcodes.iteritems()])
MESSAGE_PASSWORD_ERROR = 1
MESSAGE_UPDATE_NEEDED = 2
ALL_BY_TITLE = _('All by title') ALL_BY_TITLE = _('All by title')
ALL_BY_AUTHOR = _('All by author') ALL_BY_AUTHOR = _('All by author')
ALL_BY_SOMETHING = _('All by something') ALL_BY_SOMETHING = _('All by something')
@ -344,8 +347,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.noop_counter = 0 self.noop_counter = 0
self.debug_start_time = time.time() self.debug_start_time = time.time()
self.debug_time = time.time() self.debug_time = time.time()
self.client_can_stream_books = False
self.client_can_stream_metadata = False
def _debug(self, *args): def _debug(self, *args):
# manual synchronization so we don't lose the calling method name # manual synchronization so we don't lose the calling method name
@ -405,8 +406,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
try: try:
# If we have already seen this book's UUID, use the existing path # If we have already seen this book's UUID, use the existing path
if self.settings().extra_customization[self.OPT_OVERWRITE_BOOKS_UUID]: if self.settings().extra_customization[self.OPT_OVERWRITE_BOOKS_UUID]:
existing_book = self._uuid_already_on_device(mdata.uuid, ext) existing_book = self._uuid_in_cache(mdata.uuid, ext)
if existing_book and existing_book.lpath: if (existing_book and existing_book.lpath and
self.known_metadata.get(existing_book.lpath, None)):
return existing_book.lpath return existing_book.lpath
# If the device asked for it, try to use the UUID as the file name. # If the device asked for it, try to use the UUID as the file name.
@ -628,7 +630,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
raise raise
raise ControlError(desc='Device responded with incorrect information') raise ControlError(desc='Device responded with incorrect information')
# Write a file as a series of base64-encoded strings. # Write a file to the device as a series of binary 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):
close_ = False close_ = False
if not hasattr(infile, 'read'): if not hasattr(infile, 'read'):
@ -641,10 +643,10 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
opcode, result = self._call_client('SEND_BOOK', {'lpath': lpath, 'length': length, opcode, result = self._call_client('SEND_BOOK', {'lpath': lpath, 'length': length,
'metadata': book_metadata, 'thisBook': this_book, 'metadata': book_metadata, 'thisBook': this_book,
'totalBooks': total_books, 'totalBooks': total_books,
'willStreamBooks': self.client_can_stream_books, 'willStreamBooks': True,
'willStreamBinary' : self.client_can_receive_book_binary}, 'willStreamBinary' : True},
print_debug_info=False, print_debug_info=False,
wait_for_response=(not self.client_can_stream_books)) wait_for_response=False)
self._set_known_metadata(book_metadata) self._set_known_metadata(book_metadata)
pos = 0 pos = 0
@ -655,21 +657,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
blen = len(b) blen = len(b)
if not b: if not b:
break break
if self.client_can_stream_books and self.client_can_receive_book_binary: self._send_byte_string(self.device_socket, b)
self._send_byte_string(self.device_socket, b)
else:
b = b64encode(b)
opcode, result = self._call_client('BOOK_DATA',
{'lpath': lpath, 'position': pos, 'data': b},
print_debug_info=False,
wait_for_response=(not self.client_can_stream_books))
pos += blen pos += blen
if not self.client_can_stream_books and opcode != 'OK':
self._debug('protocol error', opcode)
failed = True
break
if not (self.client_can_stream_books and self.client_can_receive_book_binary):
self._call_client('BOOK_DONE', {'lpath': lpath})
self.time = None self.time = None
if close_: if close_:
infile.close() infile.close()
@ -722,7 +711,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
traceback.print_exc() traceback.print_exc()
return False return False
def _uuid_already_on_device(self, uuid, ext): def _uuid_in_cache(self, uuid, ext):
try: try:
return self.known_uuids[uuid + ext]['book'] return self.known_uuids[uuid + ext]['book']
except: except:
@ -774,7 +763,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
except: except:
traceback.print_exc() traceback.print_exc()
def _write_metadata_cache(self): def _write_metadata_cache(self):
from calibre.utils.config import to_json from calibre.utils.config import to_json
cache_file_name = os.path.join(cache_dir(), cache_file_name = os.path.join(cache_dir(),
@ -968,18 +956,28 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._debug('Protocol error - bogus book packet length') self._debug('Protocol error - bogus book packet length')
self._close_device_socket() self._close_device_socket()
return False return False
self._debug('App version #:', result.get('ccVersionNumber', 'unknown'))
self.client_can_stream_books = result.get('canStreamBooks', False) client_can_stream_books = result.get('canStreamBooks', False)
self._debug('Device can stream books', self.client_can_stream_books) self._debug('Device can stream books', client_can_stream_books)
self.client_can_stream_metadata = result.get('canStreamMetadata', False) client_can_stream_metadata = result.get('canStreamMetadata', False)
self._debug('Device can stream metadata', self.client_can_stream_metadata) self._debug('Device can stream metadata', client_can_stream_metadata)
self.client_can_receive_book_binary = result.get('canReceiveBookBinary', False) client_can_receive_book_binary = result.get('canReceiveBookBinary', False)
self._debug('Device can receive book binary', self.client_can_stream_metadata) self._debug('Device can receive book binary', client_can_receive_book_binary)
self.client_can_delete_multiple = result.get('canDeleteMultipleBooks', False) client_can_delete_multiple = result.get('canDeleteMultipleBooks', False)
self._debug('Device can delete multiple books', self.client_can_delete_multiple) self._debug('Device can delete multiple books', client_can_delete_multiple)
if not (client_can_stream_books and
client_can_stream_metadata and
client_can_receive_book_binary and
client_can_delete_multiple):
self._debug('Software on device too old')
self._close_device_socket()
raise OpenFeedback(_('The app on your device is too old and is no '
'longer supported. Update it to a newer version.'))
self.client_can_use_metadata_cache = result.get('canUseCachedMetadata', False) self.client_can_use_metadata_cache = result.get('canUseCachedMetadata', False)
self._debug('Device can use cached metadata', self.client_can_use_metadata_cache) self._debug('Device can use cached metadata', self.client_can_use_metadata_cache)
if not self.settings().extra_customization[self.OPT_USE_METADATA_CACHE]: if not self.settings().extra_customization[self.OPT_USE_METADATA_CACHE]:
self.client_can_use_metadata_cache = False self.client_can_use_metadata_cache = False
self._debug('metadata caching disabled by option') self._debug('metadata caching disabled by option')
@ -992,6 +990,18 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.client_app_name = result.get('appName', "") self.client_app_name = result.get('appName', "")
self._debug('Client app name', self.client_app_name) self._debug('Client app name', self.client_app_name)
self.app_version_number = result.get('ccVersionNumber', '0')
self._debug('App version #:', self.app_version_number)
try:
if (self.client_app_name == 'CalibreCompanion' and
self.app_version_number < 62):
self._debug('Telling client to update')
self._call_client("DISPLAY_MESSAGE",
{'messageKind': self.MESSAGE_UPDATE_NEEDED,
'lastestKnownAppVersion': 62})
except:
pass
self.max_book_packet_len = result.get('maxBookContentPacketLen', self.max_book_packet_len = result.get('maxBookContentPacketLen',
self.BASE_PACKET_LEN) self.BASE_PACKET_LEN)
@ -1035,7 +1045,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._debug('password mismatch') self._debug('password mismatch')
try: try:
self._call_client("DISPLAY_MESSAGE", self._call_client("DISPLAY_MESSAGE",
{'messageKind':1, {'messageKind': self.MESSAGE_PASSWORD_ERROR,
'currentLibraryName': self.current_library_name, 'currentLibraryName': self.current_library_name,
'currentLibraryUUID': library_uuid}) 'currentLibraryUUID': library_uuid})
except: except:
@ -1141,8 +1151,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
bl = CollectionsBookList(None, self.PREFIX, self.settings) bl = CollectionsBookList(None, self.PREFIX, self.settings)
if opcode == 'OK': if opcode == 'OK':
count = result['count'] count = result['count']
will_stream = 'willStream' in result
will_scan = 'willScan' in result
will_use_cache = self.client_can_use_metadata_cache will_use_cache = self.client_can_use_metadata_cache
if will_use_cache: if will_use_cache:
@ -1172,11 +1180,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
for i in range(0, count): for i in range(0, count):
if (i % 100) == 0: if (i % 100) == 0:
self._debug('getting book metadata. Done', i, 'of', count) self._debug('getting book metadata. Done', i, 'of', count)
if will_stream: opcode, result = self._receive_from_client(print_debug_info=False)
opcode, result = self._receive_from_client(print_debug_info=False)
else:
opcode, result = self._call_client('GET_BOOK_METADATA', {'index': i},
print_debug_info=False)
if opcode == 'OK': if opcode == 'OK':
if '_series_sort_' in result: if '_series_sort_' in result:
del result['_series_sort_'] del result['_series_sort_']
@ -1189,20 +1193,19 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
else: else:
raise ControlError(desc='book metadata not returned') raise ControlError(desc='book metadata not returned')
if will_scan: total = 0
total = 0 for book in bl:
for book in bl: if book.get('_new_book_', None):
if book.get('_new_book_', None): total += 1
total += 1 count = 0
count = 0 for book in bl:
for book in bl: if book.get('_new_book_', None):
if book.get('_new_book_', None): paths = [book.lpath]
paths = [book.lpath] self._set_known_metadata(book, remove=True)
self._set_known_metadata(book, remove=True) self.prepare_addable_books(paths, this_book=count, total_books=total)
self.prepare_addable_books(paths, this_book=count, total_books=total) book.smart_update(self._read_file_metadata(paths[0]))
book.smart_update(self._read_file_metadata(paths[0])) del book._new_book_
del book._new_book_ count += 1
count += 1
self._debug('finished getting book metadata') self._debug('finished getting book metadata')
return bl return bl
@ -1231,8 +1234,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
count = len(books_to_send) count = len(books_to_send)
self._call_client('SEND_BOOKLISTS', {'count': count, self._call_client('SEND_BOOKLISTS', {'count': count,
'collections': coldict, 'collections': coldict,
'willStreamMetadata': self.client_can_stream_metadata}, 'willStreamMetadata': True},
wait_for_response=not self.client_can_stream_metadata) wait_for_response=False)
if count: if count:
for i,book in enumerate(books_to_send): for i,book in enumerate(books_to_send):
@ -1242,10 +1245,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
'SEND_BOOK_METADATA', 'SEND_BOOK_METADATA',
{'index': i, 'count': count, 'data': book}, {'index': i, 'count': count, 'data': book},
print_debug_info=False, print_debug_info=False,
wait_for_response=not self.client_can_stream_metadata) wait_for_response=False)
if not self.client_can_stream_metadata and opcode != 'OK':
self._debug('protocol error', opcode, i)
raise ControlError(desc='sync_booklists')
@synchronous('sync_lock') @synchronous('sync_lock')
def eject(self): def eject(self):
@ -1315,24 +1315,14 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
else: else:
self._debug() self._debug()
if self.client_can_delete_multiple: new_paths = []
new_paths = [] for path in paths:
for path in paths: new_paths.append(self._strip_prefix(path))
new_paths.append(self._strip_prefix(path)) opcode, result = self._call_client('DELETE_BOOK', {'lpaths': new_paths})
opcode, result = self._call_client('DELETE_BOOK', {'lpaths': new_paths}) for i in range(0, len(new_paths)):
for i in range(0, len(new_paths)): opcode, result = self._receive_from_client(False)
opcode, result = self._receive_from_client(False) self._debug('removed book with UUID', result['uuid'])
self._debug('removed book with UUID', result['uuid']) self._debug('removed', len(new_paths), 'books')
self._debug('removed', len(new_paths), 'books')
else:
for path in paths:
# the path has the prefix on it (I think)
path = self._strip_prefix(path)
opcode, result = self._call_client('DELETE_BOOK', {'lpath': path})
if opcode == 'OK':
self._debug('removed book with UUID', result['uuid'])
else:
raise ControlError(desc='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):
@ -1368,31 +1358,14 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
'canStream':True, 'canStreamBinary': True}, 'canStream':True, 'canStreamBinary': True},
print_debug_info=False) print_debug_info=False)
if opcode == 'OK': if opcode == 'OK':
client_will_stream = 'willStream' in result length = result.get('fileLength')
client_will_stream_binary = 'willStreamBinary' in result remaining = length
if (client_will_stream_binary): while remaining > 0:
length = result.get('fileLength') v = self._read_binary_from_net(min(remaining, self.max_book_packet_len))
remaining = length outfile.write(v)
remaining -= len(v)
while remaining > 0: eof = True
v = self._read_binary_from_net(min(remaining, self.max_book_packet_len))
outfile.write(v)
remaining -= len(v)
eof = True
else:
while not eof:
if not result['eof']:
data = b64decode(result['data'])
if len(data) != result['next_position'] - position:
self._debug('position mismatch', result['next_position'], position)
position = result['next_position']
outfile.write(data)
opcode, result = self._receive_from_client(print_debug_info=True)
else:
eof = True
if not client_will_stream:
break
else: else:
raise ControlError(desc='request for book data failed') raise ControlError(desc='request for book data failed')
@ -1438,8 +1411,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.max_book_packet_len = 0 self.max_book_packet_len = 0
self.noop_counter = 0 self.noop_counter = 0
self.connection_attempts = {} self.connection_attempts = {}
self.client_can_stream_books = False
self.client_can_stream_metadata = False
self.client_wants_uuid_file_names = False self.client_wants_uuid_file_names = False
compression_quality_ok = True compression_quality_ok = True