This commit is contained in:
Kovid Goyal 2013-09-19 18:36:00 +05:30
commit 2e99bf845e

View File

@ -7,7 +7,7 @@ Created on 29 Jun 2012
@author: charles @author: charles
''' '''
import socket, select, json, os, traceback, time, sys, random import socket, select, json, os, traceback, time, sys, random, cPickle
import posixpath import posixpath
import hashlib, threading import hashlib, threading
import Queue import Queue
@ -34,7 +34,8 @@ from calibre.library import current_library_name
from calibre.library.server import server_config as content_server_config from calibre.library.server import server_config as content_server_config
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.ipc import eintr_retry_call from calibre.utils.ipc import eintr_retry_call
from calibre.utils.config_base import tweaks from calibre.utils.config_base import config_dir, tweaks
from calibre.utils.date import parse_date
from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to
from calibre.utils.mdns import (publish as publish_zeroconf, unpublish as from calibre.utils.mdns import (publish as publish_zeroconf, unpublish as
unpublish_zeroconf, get_all_ips) unpublish_zeroconf, get_all_ips)
@ -108,7 +109,7 @@ class ConnectionListener(Thread):
try: try:
packet = self.driver.broadcast_socket.recvfrom(100) packet = self.driver.broadcast_socket.recvfrom(100)
remote = packet[1] remote = packet[1]
content_server_port = b''; content_server_port = b''
try : try :
content_server_port = \ content_server_port = \
str(content_server_config().parse().port) str(content_server_config().parse().port)
@ -330,7 +331,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
OPT_OVERWRITE_BOOKS_UUID = 12 OPT_OVERWRITE_BOOKS_UUID = 12
OPT_COMPRESSION_QUALITY = 13 OPT_COMPRESSION_QUALITY = 13
def __init__(self, path): def __init__(self, path):
self.sync_lock = threading.RLock() self.sync_lock = threading.RLock()
self.noop_counter = 0 self.noop_counter = 0
@ -457,7 +457,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
else: else:
for c in tag.split('/'): for c in tag.split('/'):
c = sanitize(c) c = sanitize(c)
if not c: continue if not c:
continue
extra_components.append(c) extra_components.append(c)
extra_components.append(name) extra_components.append(name)
@ -680,6 +681,18 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
else: else:
return None return None
def _metadata_in_cache(self, uuid, ext, lastmod):
key = uuid+ext
if isinstance(lastmod, unicode):
lastmod = parse_date(lastmod)
# if key in self.known_uuids:
# self._debug(key, lastmod, self.known_uuids[key].last_modified)
# else:
# self._debug(key, 'not in known uuids')
if key in self.known_uuids and self.known_uuids[key].last_modified == lastmod:
return self.known_uuids[key].deepcopy()
return None
def _metadata_already_on_device(self, book): def _metadata_already_on_device(self, book):
v = self.known_metadata.get(book.lpath, None) v = self.known_metadata.get(book.lpath, None)
if v is not None: if v is not None:
@ -701,6 +714,32 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
except: except:
return None return None
def _read_metadata_cache(self):
cache_file_name = os.path.join(config_dir,
'device_drivers_' + self.__class__.__name__ +
'_metadata_cache.pickle')
if os.path.exists(cache_file_name):
with open(cache_file_name, mode='rb') as fd:
json_metadata = cPickle.load(fd)
for uuid,json_book in json_metadata.iteritems():
book = self.json_codec.raw_to_book(json_book, SDBook, self.PREFIX)
self.known_uuids[uuid] = book
lpath = book.get('lpath')
if lpath in self.known_metadata:
self.known_uuids.pop(uuid, None)
else:
self.known_metadata[lpath] = book
def _write_metadata_cache(self):
cache_file_name = os.path.join(config_dir,
'device_drivers_' + self.__class__.__name__ +
'_metadata_cache.pickle')
json_metadata = {}
for uuid,book in self.known_uuids.iteritems():
json_metadata[uuid] = self.json_codec.encode_book_metadata(book)
with open(cache_file_name, mode='wb') as fd:
cPickle.dump(json_metadata, fd, -1)
def _set_known_metadata(self, book, remove=False): def _set_known_metadata(self, book, remove=False):
lpath = book.lpath lpath = book.lpath
ext = os.path.splitext(lpath)[1] ext = os.path.splitext(lpath)[1]
@ -721,6 +760,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
except: except:
pass pass
self.device_socket = None self.device_socket = None
self._write_metadata_cache()
self.is_connected = False self.is_connected = False
def _attach_to_port(self, sock, port): def _attach_to_port(self, sock, port):
@ -868,6 +908,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._debug('Device can receive book binary', self.client_can_stream_metadata) self._debug('Device can receive book binary', self.client_can_stream_metadata)
self.client_can_delete_multiple = result.get('canDeleteMultipleBooks', False) self.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', self.client_can_delete_multiple)
self.client_can_use_metadata_cache = result.get('canUseCachedMetadata', False)
self._debug('Device can use cached metadata', self.client_can_use_metadata_cache)
self.client_device_kind = result.get('deviceKind', '') self.client_device_kind = result.get('deviceKind', '')
self._debug('Client device kind', self.client_device_kind) self._debug('Client device kind', self.client_device_kind)
@ -875,6 +917,9 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.client_device_name = result.get('deviceName', self.client_device_kind) self.client_device_name = result.get('deviceName', self.client_device_kind)
self._debug('Client device name', self.client_device_name) self._debug('Client device name', self.client_device_name)
self.client_app_name = result.get('appName', "")
self._debug('Client app name', self.client_app_name)
self.max_book_packet_len = result.get('maxBookContentPacketLen', self.max_book_packet_len = result.get('maxBookContentPacketLen',
self.BASE_PACKET_LEN) self.BASE_PACKET_LEN)
self._debug('max_book_packet_len', self.max_book_packet_len) self._debug('max_book_packet_len', self.max_book_packet_len)
@ -888,7 +933,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.client_wants_uuid_file_names = result.get('useUuidFileNames', False) self.client_wants_uuid_file_names = result.get('useUuidFileNames', False)
self._debug('Device wants UUID file names', self.client_wants_uuid_file_names) self._debug('Device wants UUID file names', self.client_wants_uuid_file_names)
config = self._configProxy() config = self._configProxy()
config['format_map'] = exts config['format_map'] = exts
self._debug('selected formats', config['format_map']) self._debug('selected formats', config['format_map'])
@ -933,8 +977,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
except: except:
pass pass
self.known_metadata = {}
self.known_uuids = {}
return True return True
except socket.timeout: except socket.timeout:
self._close_device_socket() self._close_device_socket()
@ -1019,13 +1061,41 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._debug(oncard) self._debug(oncard)
if oncard is not None: if oncard is not None:
return CollectionsBookList(None, None, None) return CollectionsBookList(None, None, None)
opcode, result = self._call_client('GET_BOOK_COUNT', {'canStream':True, opcode, result = self._call_client('GET_BOOK_COUNT',
'canScan':True}) {'canStream':True,
'canScan':True,
'willUseCachedMetadata': self.client_can_use_metadata_cache})
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_stream = 'willStream' in result
will_scan = 'willScan' in result will_scan = 'willScan' in result
will_use_cache = self.client_can_use_metadata_cache
if will_use_cache:
books_on_device = {}
self._debug('caching. count=', count)
for i in range(0, count):
opcode, result = self._receive_from_client(print_debug_info=False)
books_on_device[result.get('uuid')] = result
books_to_send = []
for u,v in books_on_device.iteritems():
book = self._metadata_in_cache(u, v['extension'], v['last_modified'])
if book:
bl.add_book(book, replace_metadata=True)
else:
books_to_send.append(v['priKey'])
count = len(books_to_send)
self._debug('caching. Need count from device', count)
self._call_client('NOOP', {'count': count},
print_debug_info=False, wait_for_response=False)
for priKey in books_to_send:
self._call_client('NOOP', {'priKey':priKey},
print_debug_info=False, wait_for_response=False)
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)
@ -1209,7 +1279,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.report_progress(1.0, _('Removing books from device metadata listing...')) self.report_progress(1.0, _('Removing books from device metadata listing...'))
self._debug('finished removing metadata for %d books' % (len(paths))) self._debug('finished removing metadata for %d books' % (len(paths)))
@synchronous('sync_lock') @synchronous('sync_lock')
def get_file(self, path, outfile, end_session=True, this_book=None, total_books=None): def get_file(self, path, outfile, end_session=True, this_book=None, total_books=None):
if self.settings().extra_customization[self.OPT_EXTRA_DEBUG]: if self.settings().extra_customization[self.OPT_EXTRA_DEBUG]:
@ -1397,6 +1466,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.connection_queue = Queue.Queue(1) self.connection_queue = Queue.Queue(1)
self.connection_listener = ConnectionListener(self) self.connection_listener = ConnectionListener(self)
self.connection_listener.start() self.connection_listener.start()
self._read_metadata_cache()
return message return message
@synchronous('sync_lock') @synchronous('sync_lock')