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
'''
import socket, select, json, os, traceback, time, sys, random
import socket, select, json, os, traceback, time, sys, random, cPickle
import posixpath
import hashlib, threading
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.ptempfile import PersistentTemporaryFile
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.mdns import (publish as publish_zeroconf, unpublish as
unpublish_zeroconf, get_all_ips)
@ -108,7 +109,7 @@ class ConnectionListener(Thread):
try:
packet = self.driver.broadcast_socket.recvfrom(100)
remote = packet[1]
content_server_port = b'';
content_server_port = b''
try :
content_server_port = \
str(content_server_config().parse().port)
@ -214,11 +215,11 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
# Some network protocol constants
BASE_PACKET_LEN = 4096
PROTOCOL_VERSION = 1
MAX_CLIENT_COMM_TIMEOUT = 300.0 # Wait at most N seconds for an answer
MAX_CLIENT_COMM_TIMEOUT = 300.0 # Wait at most N seconds for an answer
MAX_UNSUCCESSFUL_CONNECTS = 5
SEND_NOOP_EVERY_NTH_PROBE = 5
DISCONNECT_AFTER_N_SECONDS = 30*60 # 30 minutes
DISCONNECT_AFTER_N_SECONDS = 30*60 # 30 minutes
ZEROCONF_CLIENT_STRING = b'calibre wireless device client'
@ -330,7 +331,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
OPT_OVERWRITE_BOOKS_UUID = 12
OPT_COMPRESSION_QUALITY = 13
def __init__(self, path):
self.sync_lock = threading.RLock()
self.noop_counter = 0
@ -457,7 +457,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
else:
for c in tag.split('/'):
c = sanitize(c)
if not c: continue
if not c:
continue
extra_components.append(c)
extra_components.append(name)
@ -521,7 +522,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
# Things get trashed if we don't make a copy of the data.
v = self._read_binary_from_net(2)
if len(v) == 0:
return '' # documentation says the socket is broken permanently.
return '' # documentation says the socket is broken permanently.
data += v
total_len = int(data[:dex])
data = data[dex:]
@ -529,7 +530,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
while pos < total_len:
v = self._read_binary_from_net(total_len - pos)
if len(v) == 0:
return '' # documentation says the socket is broken permanently.
return '' # documentation says the socket is broken permanently.
data += v
pos += len(v)
return data
@ -553,7 +554,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._debug('socket error', e, e.errno)
if e.args[0] != EAGAIN and e.args[0] != EINTR:
raise
time.sleep(0.1) # lets not hammer the OS too hard
time.sleep(0.1) # lets not hammer the OS too hard
def _call_client(self, op, arg, print_debug_info=True, wait_for_response=True):
if op != 'NOOP':
@ -601,7 +602,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
if v:
v = json.loads(v, object_hook=from_json)
if print_debug_info and extra_debug:
self._debug('receive after decode') #, v)
self._debug('receive after decode') # , v)
return (self.reverse_opcodes[v[0]], v[1])
self._debug('protocol error -- empty json string')
except socket.timeout:
@ -680,6 +681,18 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
else:
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):
v = self.known_metadata.get(book.lpath, None)
if v is not None:
@ -701,6 +714,32 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
except:
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):
lpath = book.lpath
ext = os.path.splitext(lpath)[1]
@ -721,6 +760,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
except:
pass
self.device_socket = None
self._write_metadata_cache()
self.is_connected = False
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.client_can_delete_multiple = result.get('canDeleteMultipleBooks', False)
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._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._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.BASE_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._debug('Device wants UUID file names', self.client_wants_uuid_file_names)
config = self._configProxy()
config['format_map'] = exts
self._debug('selected formats', config['format_map'])
@ -933,8 +977,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
except:
pass
self.known_metadata = {}
self.known_uuids = {}
return True
except socket.timeout:
self._close_device_socket()
@ -1019,13 +1061,41 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._debug(oncard)
if oncard is not None:
return CollectionsBookList(None, None, None)
opcode, result = self._call_client('GET_BOOK_COUNT', {'canStream':True,
'canScan':True})
opcode, result = self._call_client('GET_BOOK_COUNT',
{'canStream':True,
'canScan':True,
'willUseCachedMetadata': self.client_can_use_metadata_cache})
bl = CollectionsBookList(None, self.PREFIX, self.settings)
if opcode == 'OK':
count = result['count']
will_stream = 'willStream' 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):
if (i % 100) == 0:
self._debug('getting book metadata. Done', i, 'of', count)
@ -1086,7 +1156,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
books_to_send.append(book)
count = len(books_to_send)
self._call_client('SEND_BOOKLISTS', { 'count': count,
self._call_client('SEND_BOOKLISTS', {'count': count,
'collections': coldict,
'willStreamMetadata': self.client_can_stream_metadata},
wait_for_response=not self.client_can_stream_metadata)
@ -1209,7 +1279,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.report_progress(1.0, _('Removing books from device metadata listing...'))
self._debug('finished removing metadata for %d books' % (len(paths)))
@synchronous('sync_lock')
def get_file(self, path, outfile, end_session=True, this_book=None, total_books=None):
if self.settings().extra_customization[self.OPT_EXTRA_DEBUG]:
@ -1340,7 +1409,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self._close_listen_socket()
return message
else:
while i < 100: # try 9090 then up to 99 random port numbers
while i < 100: # try 9090 then up to 99 random port numbers
i += 1
port = self._attach_to_port(self.listen_socket,
9090 if i == 1 else random.randint(8192, 32000))
@ -1397,6 +1466,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
self.connection_queue = Queue.Queue(1)
self.connection_listener = ConnectionListener(self)
self.connection_listener.start()
self._read_metadata_cache()
return message
@synchronous('sync_lock')