mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Disable on-device editing of metadata in the GUI for devices that don't support it (all the non SONY devices)
This commit is contained in:
parent
2a7c0bab5e
commit
03870b6a68
@ -24,6 +24,8 @@ class Device(object):
|
||||
# it can be a list of the BCD numbers of all devices supported by this driver.
|
||||
BCD = None
|
||||
THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device
|
||||
# Whether the metadata on books can be set via the GUI.
|
||||
CAN_SET_METADATA = True
|
||||
|
||||
def __init__(self, key='-1', log_packets=False, report_progress=None) :
|
||||
"""
|
||||
|
@ -22,17 +22,17 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
### Usage Type Data
|
||||
### wMaxPacketSize 0x0040 1x 64 bytes
|
||||
### bInterval 0
|
||||
###
|
||||
###
|
||||
### Endpoint 0x81 is device->host and endpoint 0x02 is host->device.
|
||||
###
|
||||
### Endpoint 0x81 is device->host and endpoint 0x02 is host->device.
|
||||
### You can establish Stream pipes to/from these endpoints for Bulk transfers.
|
||||
### Has two configurations 1 is the USB charging config 2 is the self-powered
|
||||
### Has two configurations 1 is the USB charging config 2 is the self-powered
|
||||
### config. I think config management is automatic. Endpoints are the same
|
||||
"""
|
||||
Contains the logic for communication with the device (a SONY PRS-500).
|
||||
|
||||
The public interface of class L{PRS500} defines the
|
||||
methods for performing various tasks.
|
||||
The public interface of class L{PRS500} defines the
|
||||
methods for performing various tasks.
|
||||
"""
|
||||
import sys, os
|
||||
from tempfile import TemporaryFile
|
||||
@ -49,12 +49,12 @@ from calibre.devices.prs500.books import BookList, fix_ids
|
||||
from calibre import __author__, __appname__
|
||||
|
||||
# Protocol versions this driver has been tested with
|
||||
KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L]
|
||||
KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L]
|
||||
|
||||
|
||||
class File(object):
|
||||
"""
|
||||
Wrapper that allows easy access to all information about files/directories
|
||||
"""
|
||||
Wrapper that allows easy access to all information about files/directories
|
||||
"""
|
||||
def __init__(self, _file):
|
||||
self.is_dir = _file[1].is_dir #: True if self is a directory
|
||||
@ -63,9 +63,9 @@ class File(object):
|
||||
self.ctime = _file[1].ctime #: Creation time of self as a epoch
|
||||
self.wtime = _file[1].wtime #: Creation time of self as an epoch
|
||||
path = _file[0]
|
||||
if path.endswith("/"):
|
||||
if path.endswith("/"):
|
||||
path = path[:-1]
|
||||
self.path = path #: Path to self
|
||||
self.path = path #: Path to self
|
||||
self.name = path[path.rfind("/")+1:].rstrip() #: Name of self
|
||||
|
||||
def __repr__(self):
|
||||
@ -80,7 +80,7 @@ class PRS500(Device):
|
||||
|
||||
"""
|
||||
Implements the backend for communication with the SONY Reader.
|
||||
Each method decorated by C{safe} performs a task.
|
||||
Each method decorated by C{safe} performs a task.
|
||||
"""
|
||||
|
||||
VENDOR_ID = 0x054c #: SONY Vendor Id
|
||||
@ -92,33 +92,33 @@ class PRS500(Device):
|
||||
BULK_IN_EP = 0x81 #: Endpoint for Bulk reads
|
||||
BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes
|
||||
# Location of media.xml file on device
|
||||
MEDIA_XML = "/Data/database/cache/media.xml"
|
||||
MEDIA_XML = "/Data/database/cache/media.xml"
|
||||
# Location of cache.xml on storage card in device
|
||||
CACHE_XML = "/Sony Reader/database/cache.xml"
|
||||
CACHE_XML = "/Sony Reader/database/cache.xml"
|
||||
# Ordered list of supported formats
|
||||
FORMATS = ["lrf", "lrx", "rtf", "pdf", "txt"]
|
||||
FORMATS = ["lrf", "lrx", "rtf", "pdf", "txt"]
|
||||
# Height for thumbnails of books/images on the device
|
||||
THUMBNAIL_HEIGHT = 68
|
||||
# Directory on card to which books are copied
|
||||
CARD_PATH_PREFIX = __appname__
|
||||
_packet_number = 0 #: Keep track of the packet number for packet tracing
|
||||
|
||||
|
||||
def log_packet(self, packet, header, stream=sys.stderr):
|
||||
"""
|
||||
Log C{packet} to stream C{stream}.
|
||||
Header should be a small word describing the type of packet.
|
||||
"""
|
||||
"""
|
||||
Log C{packet} to stream C{stream}.
|
||||
Header should be a small word describing the type of packet.
|
||||
"""
|
||||
self._packet_number += 1
|
||||
print >> stream, str(self._packet_number), header, "Type:", \
|
||||
packet.__class__.__name__
|
||||
print >> stream, packet
|
||||
print >> stream, "--"
|
||||
|
||||
|
||||
@classmethod
|
||||
def validate_response(cls, res, _type=0x00, number=0x00):
|
||||
"""
|
||||
Raise a ProtocolError if the type and number of C{res}
|
||||
is not the same as C{type} and C{number}.
|
||||
"""
|
||||
Raise a ProtocolError if the type and number of C{res}
|
||||
is not the same as C{type} and C{number}.
|
||||
"""
|
||||
if _type != res.type or number != res.rnumber:
|
||||
raise ProtocolError("Inavlid response.\ntype: expected=" + \
|
||||
@ -127,31 +127,31 @@ class PRS500(Device):
|
||||
" actual="+hex(res.rnumber))
|
||||
|
||||
@classmethod
|
||||
def signature(cls):
|
||||
def signature(cls):
|
||||
""" Return a two element tuple (vendor id, product id) """
|
||||
return (cls.VENDOR_ID, cls.PRODUCT_ID )
|
||||
|
||||
def safe(func):
|
||||
"""
|
||||
Decorator that wraps a call to C{func} to ensure that
|
||||
exceptions are handled correctly. It also calls L{open} to claim
|
||||
"""
|
||||
Decorator that wraps a call to C{func} to ensure that
|
||||
exceptions are handled correctly. It also calls L{open} to claim
|
||||
the interface and initialize the Reader if needed.
|
||||
|
||||
As a convenience, C{safe} automatically sends the a
|
||||
L{EndSession} after calling func, unless func has
|
||||
a keyword argument named C{end_session} set to C{False}.
|
||||
|
||||
An L{ArgumentError} will cause the L{EndSession} command to
|
||||
An L{ArgumentError} will cause the L{EndSession} command to
|
||||
be sent to the device, unless end_session is set to C{False}.
|
||||
An L{usb.USBError} will cause the library to release control of the
|
||||
An L{usb.USBError} will cause the library to release control of the
|
||||
USB interface via a call to L{close}.
|
||||
"""
|
||||
@wraps(func)
|
||||
def run_session(*args, **kwargs):
|
||||
dev = args[0]
|
||||
dev = args[0]
|
||||
res = None
|
||||
try:
|
||||
if not dev.handle:
|
||||
if not dev.handle:
|
||||
dev.open()
|
||||
if not dev.in_session:
|
||||
dev.send_validated_command(BeginEndSession(end=False))
|
||||
@ -161,19 +161,19 @@ class PRS500(Device):
|
||||
if not kwargs.has_key("end_session") or kwargs["end_session"]:
|
||||
dev.send_validated_command(BeginEndSession(end=True))
|
||||
dev.in_session = False
|
||||
raise
|
||||
raise
|
||||
except USBError, err:
|
||||
if "No such device" in str(err):
|
||||
raise DeviceError()
|
||||
elif "Connection timed out" in str(err):
|
||||
elif "Connection timed out" in str(err):
|
||||
dev.close()
|
||||
raise TimeoutError(func.__name__)
|
||||
elif "Protocol error" in str(err):
|
||||
elif "Protocol error" in str(err):
|
||||
dev.close()
|
||||
raise ProtocolError("There was an unknown error in the"+\
|
||||
" protocol. Contact " + __author__)
|
||||
dev.close()
|
||||
raise
|
||||
raise
|
||||
if not kwargs.has_key("end_session") or kwargs["end_session"]:
|
||||
dev.send_validated_command(BeginEndSession(end=True))
|
||||
dev.in_session = False
|
||||
@ -182,15 +182,15 @@ class PRS500(Device):
|
||||
return run_session
|
||||
|
||||
def __init__(self, key='-1', log_packets=False, report_progress=None) :
|
||||
"""
|
||||
"""
|
||||
@param key: The key to unlock the device
|
||||
@param log_packets: If true the packet stream to/from the device is logged
|
||||
@param report_progress: Function that is called with a % progress
|
||||
@param log_packets: If true the packet stream to/from the device is logged
|
||||
@param report_progress: Function that is called with a % progress
|
||||
(number between 0 and 100) for various tasks
|
||||
If it is called with -1 that means that the
|
||||
If it is called with -1 that means that the
|
||||
task does not have any progress information
|
||||
"""
|
||||
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||
# Handle that is used to communicate with device. Setup in L{open}
|
||||
self.handle = None
|
||||
self.in_session = False
|
||||
@ -204,14 +204,14 @@ class PRS500(Device):
|
||||
|
||||
def reconnect(self):
|
||||
""" Only recreates the device node and deleted the connection handle """
|
||||
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||
self.handle = None
|
||||
|
||||
@classmethod
|
||||
def is_connected(cls, helper=None):
|
||||
"""
|
||||
This method checks to see whether the device is physically connected.
|
||||
It does not return any information about the validity of the
|
||||
"""
|
||||
This method checks to see whether the device is physically connected.
|
||||
It does not return any information about the validity of the
|
||||
software connection. You may need to call L{reconnect} if you keep
|
||||
getting L{DeviceError}.
|
||||
"""
|
||||
@ -222,15 +222,15 @@ class PRS500(Device):
|
||||
|
||||
def set_progress_reporter(self, report_progress):
|
||||
self.report_progress = report_progress
|
||||
|
||||
|
||||
def open(self) :
|
||||
"""
|
||||
Claim an interface on the device for communication.
|
||||
Claim an interface on the device for communication.
|
||||
Requires write privileges to the device file.
|
||||
Also initialize the device.
|
||||
Also initialize the device.
|
||||
See the source code for the sequence of initialization commands.
|
||||
"""
|
||||
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||
if not self.device:
|
||||
raise DeviceError()
|
||||
configs = self.device.configurations
|
||||
@ -238,7 +238,7 @@ class PRS500(Device):
|
||||
self.handle = self.device.open()
|
||||
config = configs[0]
|
||||
try:
|
||||
self.handle.set_configuration(configs[0])
|
||||
self.handle.set_configuration(configs[0])
|
||||
except USBError:
|
||||
self.handle.set_configuration(configs[1])
|
||||
config = configs[1]
|
||||
@ -250,13 +250,13 @@ class PRS500(Device):
|
||||
else:
|
||||
red, wed = ed2, ed1
|
||||
self.bulk_read_max_packet_size = red.MaxPacketSize
|
||||
self.bulk_write_max_packet_size = wed.MaxPacketSize
|
||||
self.bulk_write_max_packet_size = wed.MaxPacketSize
|
||||
self.handle.claim_interface(self.INTERFACE_ID)
|
||||
except USBError, err:
|
||||
raise DeviceBusy(str(err))
|
||||
# Large timeout as device may still be initializing
|
||||
res = self.send_validated_command(GetUSBProtocolVersion(), timeout=20000)
|
||||
if res.code != 0:
|
||||
res = self.send_validated_command(GetUSBProtocolVersion(), timeout=20000)
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Unable to get USB Protocol version.")
|
||||
version = self._bulk_read(24, data_type=USBProtocolVersion)[0].version
|
||||
if version not in KNOWN_USB_PROTOCOL_VERSIONS:
|
||||
@ -265,16 +265,16 @@ class PRS500(Device):
|
||||
res = self.send_validated_command(SetBulkSize(\
|
||||
chunk_size = 512*self.bulk_read_max_packet_size, \
|
||||
unknown = 2))
|
||||
if res.code != 0:
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Unable to set bulk size.")
|
||||
res = self.send_validated_command(UnlockDevice(key=self.key))#0x312d))
|
||||
if res.code != 0:
|
||||
res = self.send_validated_command(UnlockDevice(key=self.key))#0x312d))
|
||||
if res.code != 0:
|
||||
raise DeviceLocked()
|
||||
res = self.send_validated_command(SetTime())
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Could not set time on device")
|
||||
|
||||
def close(self):
|
||||
def close(self):
|
||||
""" Release device interface """
|
||||
try:
|
||||
self.handle.reset()
|
||||
@ -285,16 +285,16 @@ class PRS500(Device):
|
||||
self.in_session = False
|
||||
|
||||
def _send_command(self, command, response_type=Response, timeout=1000):
|
||||
"""
|
||||
Send L{command<Command>} to device and return its L{response<Response>}.
|
||||
"""
|
||||
Send L{command<Command>} to device and return its L{response<Response>}.
|
||||
|
||||
@param command: an object of type Command or one of its derived classes
|
||||
@param response_type: an object of type 'type'. The return packet
|
||||
from the device is returned as an object of type response_type.
|
||||
@param timeout: The time to wait for a response from the
|
||||
from the device is returned as an object of type response_type.
|
||||
@param timeout: The time to wait for a response from the
|
||||
device, in milliseconds. If there is no response, a L{usb.USBError} is raised.
|
||||
"""
|
||||
if self.log_packets:
|
||||
if self.log_packets:
|
||||
self.log_packet(command, "Command")
|
||||
bytes_sent = self.handle.control_msg(0x40, 0x80, command)
|
||||
if bytes_sent != len(command):
|
||||
@ -302,19 +302,19 @@ class PRS500(Device):
|
||||
+ str(command))
|
||||
response = response_type(self.handle.control_msg(0xc0, 0x81, \
|
||||
Response.SIZE, timeout=timeout))
|
||||
if self.log_packets:
|
||||
if self.log_packets:
|
||||
self.log_packet(response, "Response")
|
||||
return response
|
||||
|
||||
def send_validated_command(self, command, cnumber=None, \
|
||||
response_type=Response, timeout=1000):
|
||||
"""
|
||||
Wrapper around L{_send_command} that checks if the
|
||||
C{Response.rnumber == cnumber or
|
||||
"""
|
||||
Wrapper around L{_send_command} that checks if the
|
||||
C{Response.rnumber == cnumber or
|
||||
command.number if cnumber==None}. Also check that
|
||||
C{Response.type == Command.type}.
|
||||
"""
|
||||
if cnumber == None:
|
||||
if cnumber == None:
|
||||
cnumber = command.number
|
||||
res = self._send_command(command, response_type=response_type, \
|
||||
timeout=timeout)
|
||||
@ -322,18 +322,18 @@ class PRS500(Device):
|
||||
return res
|
||||
|
||||
def _bulk_write(self, data, packet_size=0x1000):
|
||||
"""
|
||||
"""
|
||||
Send data to device via a bulk transfer.
|
||||
@type data: Any listable type supporting __getslice__
|
||||
@param packet_size: Size of packets to be sent to device.
|
||||
@param packet_size: Size of packets to be sent to device.
|
||||
C{data} is broken up into packets to be sent to device.
|
||||
"""
|
||||
def bulk_write_packet(packet):
|
||||
self.handle.bulk_write(self.BULK_OUT_EP, packet)
|
||||
if self.log_packets:
|
||||
if self.log_packets:
|
||||
self.log_packet(Answer(packet), "Answer h->d")
|
||||
|
||||
bytes_left = len(data)
|
||||
bytes_left = len(data)
|
||||
if bytes_left + 16 <= packet_size:
|
||||
packet_size = bytes_left +16
|
||||
first_packet = Answer(bytes_left+16)
|
||||
@ -355,11 +355,11 @@ class PRS500(Device):
|
||||
pos = endpos
|
||||
res = Response(self.handle.control_msg(0xc0, 0x81, Response.SIZE, \
|
||||
timeout=5000))
|
||||
if self.log_packets:
|
||||
if self.log_packets:
|
||||
self.log_packet(res, "Response")
|
||||
if res.rnumber != 0x10005 or res.code != 0:
|
||||
raise ProtocolError("Sending via Bulk Transfer failed with response:\n"\
|
||||
+str(res))
|
||||
+str(res))
|
||||
if res.data_size != len(data):
|
||||
raise ProtocolError("Unable to transfer all data to device. "+\
|
||||
"Response packet:\n"\
|
||||
@ -368,12 +368,12 @@ class PRS500(Device):
|
||||
|
||||
def _bulk_read(self, bytes, command_number=0x00, packet_size=0x1000, \
|
||||
data_type=Answer):
|
||||
"""
|
||||
Read in C{bytes} bytes via a bulk transfer in
|
||||
packets of size S{<=} C{packet_size}
|
||||
@param data_type: an object of type type.
|
||||
The data packet is returned as an object of type C{data_type}.
|
||||
@return: A list of packets read from the device.
|
||||
"""
|
||||
Read in C{bytes} bytes via a bulk transfer in
|
||||
packets of size S{<=} C{packet_size}
|
||||
@param data_type: an object of type type.
|
||||
The data packet is returned as an object of type C{data_type}.
|
||||
@return: A list of packets read from the device.
|
||||
Each packet is of type data_type
|
||||
"""
|
||||
msize = self.bulk_read_max_packet_size
|
||||
@ -392,7 +392,7 @@ class PRS500(Device):
|
||||
bytes_left = bytes
|
||||
packets = []
|
||||
while bytes_left > 0:
|
||||
if packet_size > bytes_left:
|
||||
if packet_size > bytes_left:
|
||||
packet_size = bytes_left
|
||||
packet = bulk_read_packet(data_type=data_type, size=packet_size)
|
||||
bytes_left -= len(packet)
|
||||
@ -404,8 +404,8 @@ class PRS500(Device):
|
||||
|
||||
@safe
|
||||
def get_device_information(self, end_session=True):
|
||||
"""
|
||||
Ask device for device information. See L{DeviceInfoQuery}.
|
||||
"""
|
||||
Ask device for device information. See L{DeviceInfoQuery}.
|
||||
@return: (device name, device version, software version on device, mime type)
|
||||
"""
|
||||
size = self.send_validated_command(DeviceInfoQuery()).data[2] + 16
|
||||
@ -416,21 +416,21 @@ class PRS500(Device):
|
||||
|
||||
@safe
|
||||
def path_properties(self, path, end_session=True):
|
||||
"""
|
||||
Send command asking device for properties of C{path}.
|
||||
Return L{FileProperties}.
|
||||
"""
|
||||
Send command asking device for properties of C{path}.
|
||||
Return L{FileProperties}.
|
||||
"""
|
||||
res = self.send_validated_command(PathQuery(path), \
|
||||
response_type=ListResponse)
|
||||
data = self._bulk_read(0x28, data_type=FileProperties, \
|
||||
command_number=PathQuery.NUMBER)[0]
|
||||
if path.endswith('/') and path != '/':
|
||||
if path.endswith('/') and path != '/':
|
||||
path = path[:-1]
|
||||
if res.path_not_found :
|
||||
raise PathError(path + " does not exist on device")
|
||||
if res.is_invalid:
|
||||
if res.is_invalid:
|
||||
raise PathError(path + " is not a valid path")
|
||||
if res.is_unmounted:
|
||||
if res.is_unmounted:
|
||||
raise PathError(path + " is not mounted")
|
||||
if res.permission_denied:
|
||||
raise PathError('Permission denied for: ' + path + '\nYou can only '+\
|
||||
@ -443,20 +443,20 @@ class PRS500(Device):
|
||||
@safe
|
||||
def get_file(self, path, outfile, end_session=True):
|
||||
"""
|
||||
Read the file at path on the device and write it to outfile.
|
||||
Read the file at path on the device and write it to outfile.
|
||||
|
||||
The data is fetched in chunks of size S{<=} 32K. Each chunk is
|
||||
The data is fetched in chunks of size S{<=} 32K. Each chunk is
|
||||
made of packets of size S{<=} 4K. See L{FileOpen},
|
||||
L{FileRead} and L{FileClose} for details on the command packets used.
|
||||
L{FileRead} and L{FileClose} for details on the command packets used.
|
||||
|
||||
@param outfile: file object like C{sys.stdout} or the result of an C{open} call
|
||||
"""
|
||||
if path.endswith("/"):
|
||||
if path.endswith("/"):
|
||||
path = path[:-1] # We only copy files
|
||||
cp = self.card_prefix(False)
|
||||
path = path.replace('card:/', cp if cp else '')
|
||||
_file = self.path_properties(path, end_session=False)
|
||||
if _file.is_dir:
|
||||
if _file.is_dir:
|
||||
raise PathError("Cannot read as " + path + " is a directory")
|
||||
bytes = _file.file_size
|
||||
res = self.send_validated_command(FileOpen(path))
|
||||
@ -464,12 +464,12 @@ class PRS500(Device):
|
||||
raise PathError("Unable to open " + path + \
|
||||
" for reading. Response code: " + hex(res.code))
|
||||
_id = self._bulk_read(20, data_type=IdAnswer, \
|
||||
command_number=FileOpen.NUMBER)[0].id
|
||||
command_number=FileOpen.NUMBER)[0].id
|
||||
# The first 16 bytes from the device are meta information on the packet stream
|
||||
bytes_left, chunk_size = bytes, 512 * self.bulk_read_max_packet_size -16
|
||||
packet_size, pos = 64 * self.bulk_read_max_packet_size, 0
|
||||
while bytes_left > 0:
|
||||
if chunk_size > bytes_left:
|
||||
while bytes_left > 0:
|
||||
if chunk_size > bytes_left:
|
||||
chunk_size = bytes_left
|
||||
res = self.send_validated_command(FileIO(_id, pos, chunk_size))
|
||||
if res.code != 0:
|
||||
@ -477,21 +477,21 @@ class PRS500(Device):
|
||||
raise ProtocolError("Error while reading from " + path + \
|
||||
". Response code: " + hex(res.code))
|
||||
packets = self._bulk_read(chunk_size+16, \
|
||||
command_number=FileIO.RNUMBER, packet_size=packet_size)
|
||||
command_number=FileIO.RNUMBER, packet_size=packet_size)
|
||||
try:
|
||||
outfile.write("".join(map(chr, packets[0][16:])))
|
||||
for i in range(1, len(packets)):
|
||||
for i in range(1, len(packets)):
|
||||
outfile.write("".join(map(chr, packets[i])))
|
||||
except IOError, err:
|
||||
self.send_validated_command(FileClose(_id))
|
||||
raise ArgumentError("File get operation failed. " + \
|
||||
"Could not write to local location: " + str(err))
|
||||
"Could not write to local location: " + str(err))
|
||||
bytes_left -= chunk_size
|
||||
pos += chunk_size
|
||||
if self.report_progress:
|
||||
if self.report_progress:
|
||||
self.report_progress(int(100*((1.*pos)/bytes)))
|
||||
self.send_validated_command(FileClose(_id))
|
||||
# Not going to check response code to see if close was successful
|
||||
self.send_validated_command(FileClose(_id))
|
||||
# Not going to check response code to see if close was successful
|
||||
# as there's not much we can do if it wasnt
|
||||
|
||||
@safe
|
||||
@ -503,26 +503,26 @@ class PRS500(Device):
|
||||
@type path: string
|
||||
@param path: The path to list
|
||||
@type recurse: boolean
|
||||
@param recurse: If true do a recursive listing
|
||||
@return: A list of tuples. The first element of each tuple is a path.
|
||||
The second element is a list of L{Files<File>}.
|
||||
The path is the path we are listing, the C{Files} are the
|
||||
files/directories in that path. If it is a recursive list, then the first
|
||||
element will be (C{path}, children), the next will be
|
||||
(child, its children) and so on. If it is not recursive the length of the
|
||||
@param recurse: If true do a recursive listing
|
||||
@return: A list of tuples. The first element of each tuple is a path.
|
||||
The second element is a list of L{Files<File>}.
|
||||
The path is the path we are listing, the C{Files} are the
|
||||
files/directories in that path. If it is a recursive list, then the first
|
||||
element will be (C{path}, children), the next will be
|
||||
(child, its children) and so on. If it is not recursive the length of the
|
||||
outermost list will be 1.
|
||||
"""
|
||||
def _list(path):
|
||||
def _list(path):
|
||||
""" Do a non recursive listsing of path """
|
||||
if not path.endswith("/"):
|
||||
if not path.endswith("/"):
|
||||
path += "/" # Initially assume path is a directory
|
||||
cp = self.card_prefix(False)
|
||||
path = path.replace('card:/', cp if cp else '')
|
||||
files = []
|
||||
candidate = self.path_properties(path, end_session=False)
|
||||
if not candidate.is_dir:
|
||||
if not candidate.is_dir:
|
||||
path = path[:-1]
|
||||
data = self.path_properties(path, end_session=False)
|
||||
data = self.path_properties(path, end_session=False)
|
||||
files = [ File((path, data)) ]
|
||||
else:
|
||||
# Get query ID used to ask for next element in list
|
||||
@ -536,20 +536,20 @@ class PRS500(Device):
|
||||
next = DirRead(_id)
|
||||
items = []
|
||||
while True:
|
||||
res = self.send_validated_command(next, response_type=ListResponse)
|
||||
res = self.send_validated_command(next, response_type=ListResponse)
|
||||
size = res.data_size + 16
|
||||
data = self._bulk_read(size, data_type=ListAnswer, \
|
||||
command_number=DirRead.NUMBER)[0]
|
||||
# path_not_found seems to happen if the usb server
|
||||
# path_not_found seems to happen if the usb server
|
||||
# doesn't have the permissions to access the directory
|
||||
if res.is_eol or res.path_not_found:
|
||||
break
|
||||
if res.is_eol or res.path_not_found:
|
||||
break
|
||||
elif res.code != 0:
|
||||
raise ProtocolError("Unknown error occured while "+\
|
||||
"reading contents of directory " + path + \
|
||||
". Response code: " + hex(res.code))
|
||||
items.append(data.name)
|
||||
self.send_validated_command(DirClose(_id))
|
||||
self.send_validated_command(DirClose(_id))
|
||||
# Ignore res.code as we cant do anything if close fails
|
||||
for item in items:
|
||||
ipath = path + item
|
||||
@ -568,23 +568,23 @@ class PRS500(Device):
|
||||
|
||||
@safe
|
||||
def total_space(self, end_session=True):
|
||||
"""
|
||||
"""
|
||||
Get total space available on the mountpoints:
|
||||
1. Main memory
|
||||
2. Memory Stick
|
||||
3. SD Card
|
||||
|
||||
@return: A 3 element list with total space in bytes of (1, 2, 3)
|
||||
"""
|
||||
"""
|
||||
data = []
|
||||
for path in ("/Data/", "a:/", "b:/"):
|
||||
# Timeout needs to be increased as it takes time to read card
|
||||
res = self.send_validated_command(TotalSpaceQuery(path), \
|
||||
timeout=5000)
|
||||
timeout=5000)
|
||||
buffer_size = 16 + res.data[2]
|
||||
pkt = self._bulk_read(buffer_size, data_type=TotalSpaceAnswer, \
|
||||
command_number=TotalSpaceQuery.NUMBER)[0]
|
||||
data.append( pkt.total )
|
||||
data.append( pkt.total )
|
||||
return data
|
||||
|
||||
@safe
|
||||
@ -600,26 +600,26 @@ class PRS500(Device):
|
||||
return path
|
||||
except PathError:
|
||||
return None
|
||||
|
||||
|
||||
@safe
|
||||
def free_space(self, end_session=True):
|
||||
"""
|
||||
"""
|
||||
Get free space available on the mountpoints:
|
||||
1. Main memory
|
||||
2. Memory Stick
|
||||
3. SD Card
|
||||
|
||||
@return: A 3 element list with free space in bytes of (1, 2, 3)
|
||||
"""
|
||||
"""
|
||||
data = []
|
||||
for path in ("/", "a:/", "b:/"):
|
||||
# Timeout needs to be increased as it takes time to read card
|
||||
self.send_validated_command(FreeSpaceQuery(path), \
|
||||
timeout=5000)
|
||||
timeout=5000)
|
||||
pkt = self._bulk_read(FreeSpaceAnswer.SIZE, \
|
||||
data_type=FreeSpaceAnswer, \
|
||||
command_number=FreeSpaceQuery.NUMBER)[0]
|
||||
data.append( pkt.free )
|
||||
data.append( pkt.free )
|
||||
return data
|
||||
|
||||
def _exists(self, path):
|
||||
@ -628,21 +628,21 @@ class PRS500(Device):
|
||||
try:
|
||||
dest = self.path_properties(path, end_session=False)
|
||||
except PathError, err:
|
||||
if "does not exist" in str(err) or "not mounted" in str(err):
|
||||
if "does not exist" in str(err) or "not mounted" in str(err):
|
||||
return (False, None)
|
||||
else: raise
|
||||
else: raise
|
||||
return (True, dest)
|
||||
|
||||
@safe
|
||||
def touch(self, path, end_session=True):
|
||||
"""
|
||||
Create a file at path
|
||||
@todo: Update file modification time if it exists.
|
||||
"""
|
||||
Create a file at path
|
||||
@todo: Update file modification time if it exists.
|
||||
Opening the file in write mode and then closing it doesn't work.
|
||||
"""
|
||||
cp = self.card_prefix(False)
|
||||
path = path.replace('card:/', cp if cp else '')
|
||||
if path.endswith("/") and len(path) > 1:
|
||||
if path.endswith("/") and len(path) > 1:
|
||||
path = path[:-1]
|
||||
exists, _file = self._exists(path)
|
||||
if exists and _file.is_dir:
|
||||
@ -651,18 +651,18 @@ class PRS500(Device):
|
||||
res = self.send_validated_command(FileCreate(path))
|
||||
if res.code != 0:
|
||||
raise PathError("Could not create file " + path + \
|
||||
". Response code: " + str(hex(res.code)))
|
||||
". Response code: " + str(hex(res.code)))
|
||||
|
||||
@safe
|
||||
def put_file(self, infile, path, replace_file=False, end_session=True):
|
||||
"""
|
||||
Put infile onto the devoce at path
|
||||
@param infile: An open file object. infile must have a name attribute.
|
||||
@param infile: An open file object. infile must have a name attribute.
|
||||
If you are using a StringIO object set its name attribute manually.
|
||||
@param path: The path on the device at which to put infile.
|
||||
@param path: The path on the device at which to put infile.
|
||||
It should point to an existing directory.
|
||||
@param replace_file: If True and path points to a file that already exists, it is replaced
|
||||
"""
|
||||
"""
|
||||
pos = infile.tell()
|
||||
infile.seek(0, 2)
|
||||
bytes = infile.tell() - pos
|
||||
@ -673,12 +673,12 @@ class PRS500(Device):
|
||||
exists, dest = self._exists(path)
|
||||
if exists:
|
||||
if dest.is_dir:
|
||||
if not path.endswith("/"):
|
||||
if not path.endswith("/"):
|
||||
path += "/"
|
||||
path += os.path.basename(infile.name)
|
||||
return self.put_file(infile, path, replace_file=replace_file, end_session=False)
|
||||
else:
|
||||
if not replace_file:
|
||||
if not replace_file:
|
||||
raise PathError("Cannot write to " + \
|
||||
path + " as it already exists", path=path)
|
||||
_file = self.path_properties(path, end_session=False)
|
||||
@ -693,7 +693,7 @@ class PRS500(Device):
|
||||
raise ProtocolError("Unable to open " + path + \
|
||||
" for writing. Response code: " + hex(res.code))
|
||||
_id = self._bulk_read(20, data_type=IdAnswer, \
|
||||
command_number=FileOpen.NUMBER)[0].id
|
||||
command_number=FileOpen.NUMBER)[0].id
|
||||
|
||||
while data_left:
|
||||
data = array('B')
|
||||
@ -704,7 +704,7 @@ class PRS500(Device):
|
||||
data.fromstring(ind)
|
||||
if len(ind) < chunk_size:
|
||||
raise EOFError
|
||||
except EOFError:
|
||||
except EOFError:
|
||||
data_left = False
|
||||
res = self.send_validated_command(FileIO(_id, pos, len(data), \
|
||||
mode=FileIO.WNUMBER))
|
||||
@ -715,7 +715,7 @@ class PRS500(Device):
|
||||
pos += len(data)
|
||||
if self.report_progress:
|
||||
self.report_progress( int(100*(pos-start_pos)/(1.*bytes)) )
|
||||
self.send_validated_command(FileClose(_id))
|
||||
self.send_validated_command(FileClose(_id))
|
||||
# Ignore res.code as cant do anything if close fails
|
||||
_file = self.path_properties(path, end_session=False)
|
||||
if _file.file_size != pos:
|
||||
@ -727,7 +727,7 @@ class PRS500(Device):
|
||||
def del_file(self, path, end_session=True):
|
||||
""" Delete C{path} from device iff path is a file """
|
||||
data = self.path_properties(path, end_session=False)
|
||||
if data.is_dir:
|
||||
if data.is_dir:
|
||||
raise PathError("Cannot delete directories")
|
||||
res = self.send_validated_command(FileDelete(path), \
|
||||
response_type=ListResponse)
|
||||
@ -741,7 +741,7 @@ class PRS500(Device):
|
||||
if path.startswith('card:/'):
|
||||
cp = self.card_prefix(False)
|
||||
path = path.replace('card:/', cp if cp else '')
|
||||
if not path.endswith("/"):
|
||||
if not path.endswith("/"):
|
||||
path += "/"
|
||||
error_prefix = "Cannot create directory " + path
|
||||
res = self.send_validated_command(DirCreate(path)).data[0]
|
||||
@ -764,8 +764,8 @@ class PRS500(Device):
|
||||
if not dir.is_dir:
|
||||
self.del_file(path, end_session=False)
|
||||
else:
|
||||
if not path.endswith("/"):
|
||||
path += "/"
|
||||
if not path.endswith("/"):
|
||||
path += "/"
|
||||
res = self.send_validated_command(DirDelete(path))
|
||||
if res.code == PathResponseCodes.HAS_CHILDREN:
|
||||
raise PathError("Cannot delete directory " + path + \
|
||||
@ -778,24 +778,24 @@ class PRS500(Device):
|
||||
def card(self, end_session=True):
|
||||
""" Return path prefix to installed card or None """
|
||||
card = None
|
||||
if self._exists("a:/")[0]:
|
||||
if self._exists("a:/")[0]:
|
||||
card = "a:"
|
||||
if self._exists("b:/")[0]:
|
||||
card = "b:"
|
||||
if self._exists("b:/")[0]:
|
||||
card = "b:"
|
||||
return card
|
||||
|
||||
@safe
|
||||
def books(self, oncard=False, end_session=True):
|
||||
"""
|
||||
"""
|
||||
Return a list of ebooks on the device.
|
||||
@param oncard: If True return a list of ebooks on the storage card,
|
||||
@param oncard: If True return a list of ebooks on the storage card,
|
||||
otherwise return list of ebooks in main memory of device
|
||||
|
||||
@return: L{BookList}
|
||||
"""
|
||||
"""
|
||||
root = "/Data/media/"
|
||||
tfile = TemporaryFile()
|
||||
if oncard:
|
||||
if oncard:
|
||||
try:
|
||||
self.get_file("a:"+self.CACHE_XML, tfile, end_session=False)
|
||||
root = "a:/"
|
||||
@ -804,9 +804,9 @@ class PRS500(Device):
|
||||
self.get_file("b:"+self.CACHE_XML, tfile, end_session=False)
|
||||
root = "b:/"
|
||||
except PathError: pass
|
||||
if tfile.tell() == 0:
|
||||
if tfile.tell() == 0:
|
||||
tfile = None
|
||||
else:
|
||||
else:
|
||||
self.get_file(self.MEDIA_XML, tfile, end_session=False)
|
||||
bl = BookList(root=root, sfile=tfile)
|
||||
paths = bl.purge_corrupted_files()
|
||||
@ -822,26 +822,26 @@ class PRS500(Device):
|
||||
"""
|
||||
Remove the books specified by paths from the device. The metadata
|
||||
cache on the device should also be updated.
|
||||
"""
|
||||
"""
|
||||
for path in paths:
|
||||
self.del_file(path, end_session=False)
|
||||
fix_ids(booklists[0], booklists[1])
|
||||
self.sync_booklists(booklists, end_session=False)
|
||||
|
||||
|
||||
@safe
|
||||
def sync_booklists(self, booklists, end_session=True):
|
||||
'''
|
||||
Upload bookslists to device.
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
(L{books}(oncard=False), L{books}(oncard=True)).
|
||||
'''
|
||||
fix_ids(*booklists)
|
||||
self.upload_book_list(booklists[0], end_session=False)
|
||||
if booklists[1].root:
|
||||
self.upload_book_list(booklists[1], end_session=False)
|
||||
|
||||
|
||||
@safe
|
||||
def upload_books(self, files, names, on_card=False, end_session=True,
|
||||
def upload_books(self, files, names, on_card=False, end_session=True,
|
||||
metadata=None):
|
||||
card = self.card(end_session=False)
|
||||
prefix = card + '/' + self.CARD_PATH_PREFIX +'/' if on_card else '/Data/media/books/'
|
||||
@ -856,21 +856,21 @@ class PRS500(Device):
|
||||
space = self.free_space(end_session=False)
|
||||
mspace = space[0]
|
||||
cspace = space[1] if space[1] >= space[2] else space[2]
|
||||
if on_card and size > cspace - 1024*1024:
|
||||
if on_card and size > cspace - 1024*1024:
|
||||
raise FreeSpaceError("There is insufficient free space "+\
|
||||
"on the storage card")
|
||||
if not on_card and size > mspace - 2*1024*1024:
|
||||
if not on_card and size > mspace - 2*1024*1024:
|
||||
raise FreeSpaceError("There is insufficient free space " +\
|
||||
"in main memory")
|
||||
|
||||
|
||||
for infile in infiles:
|
||||
infile.seek(0)
|
||||
infile.seek(0)
|
||||
name = names.next()
|
||||
paths.append(prefix+name)
|
||||
self.put_file(infile, paths[-1], replace_file=True, end_session=False)
|
||||
ctimes.append(self.path_properties(paths[-1], end_session=False).ctime)
|
||||
return zip(paths, sizes, ctimes)
|
||||
|
||||
|
||||
@classmethod
|
||||
def add_books_to_metadata(cls, locations, metadata, booklists):
|
||||
metadata = iter(metadata)
|
||||
@ -882,35 +882,35 @@ class PRS500(Device):
|
||||
name = (cls.CARD_PATH_PREFIX+'/' if on_card else 'books/') + name
|
||||
booklists[on_card].add_book(info, name, *location[1:])
|
||||
fix_ids(*booklists)
|
||||
|
||||
|
||||
@safe
|
||||
def delete_books(self, paths, end_session=True):
|
||||
for path in paths:
|
||||
self.del_file(path, end_session=False)
|
||||
|
||||
|
||||
@classmethod
|
||||
def remove_books_from_metadata(cls, paths, booklists):
|
||||
for path in paths:
|
||||
on_card = 1 if path[1] == ':' else 0
|
||||
booklists[on_card].remove_book(path)
|
||||
fix_ids(*booklists)
|
||||
|
||||
|
||||
@safe
|
||||
def add_book(self, infile, name, info, booklists, oncard=False, \
|
||||
sync_booklists=False, end_session=True):
|
||||
"""
|
||||
Add a book to the device. If oncard is True then the book is copied
|
||||
to the card rather than main memory.
|
||||
Add a book to the device. If oncard is True then the book is copied
|
||||
to the card rather than main memory.
|
||||
|
||||
@param infile: The source file, should be opened in "rb" mode
|
||||
@param name: The name of the book file when uploaded to the
|
||||
device. The extension of name must be one of
|
||||
@param name: The name of the book file when uploaded to the
|
||||
device. The extension of name must be one of
|
||||
the supported formats for this device.
|
||||
@param info: A dictionary that must have the keys "title", "authors", "cover".
|
||||
@param info: A dictionary that must have the keys "title", "authors", "cover".
|
||||
C{info["cover"]} should be a three element tuple (width, height, data)
|
||||
where data is the image data in JPEG format as a string
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
(L{books}(oncard=False), L{books}(oncard=True)).
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
(L{books}(oncard=False), L{books}(oncard=True)).
|
||||
"""
|
||||
infile.seek(0, 2)
|
||||
size = infile.tell()
|
||||
@ -922,11 +922,11 @@ class PRS500(Device):
|
||||
if oncard and size > cspace - 1024*1024:
|
||||
raise FreeSpaceError("There is insufficient free space "+\
|
||||
"on the storage card")
|
||||
if not oncard and size > mspace - 1024*1024:
|
||||
if not oncard and size > mspace - 1024*1024:
|
||||
raise FreeSpaceError("There is insufficient free space " +\
|
||||
"in main memory")
|
||||
prefix = "/Data/media/"
|
||||
if oncard:
|
||||
if oncard:
|
||||
prefix = card + "/"
|
||||
else: name = "books/"+name
|
||||
path = prefix + name
|
||||
@ -943,12 +943,12 @@ class PRS500(Device):
|
||||
path = self.MEDIA_XML
|
||||
if not booklist.prefix:
|
||||
card = self.card(end_session=True)
|
||||
if not card:
|
||||
if not card:
|
||||
raise ArgumentError("Cannot upload list to card as "+\
|
||||
"card is not present")
|
||||
path = card + self.CACHE_XML
|
||||
f = StringIO()
|
||||
f = StringIO()
|
||||
booklist.write(f)
|
||||
f.seek(0)
|
||||
self.put_file(f, path, replace_file=True, end_session=False)
|
||||
f.close()
|
||||
f.close()
|
||||
|
@ -35,6 +35,7 @@ class USBMS(Device):
|
||||
EBOOK_DIR_MAIN = ''
|
||||
EBOOK_DIR_CARD = ''
|
||||
SUPPORTS_SUB_DIRS = False
|
||||
CAN_SET_METADATA = False
|
||||
|
||||
def __init__(self, key='-1', log_packets=False, report_progress=None):
|
||||
Device.__init__(self, key=key, log_packets=log_packets,
|
||||
|
@ -706,6 +706,9 @@ class BooksView(TableView):
|
||||
|
||||
def close(self):
|
||||
self._model.close()
|
||||
|
||||
def set_editable(self, editable):
|
||||
self._model.set_editable(editable)
|
||||
|
||||
def connect_to_search_box(self, sb):
|
||||
QObject.connect(sb, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'),
|
||||
@ -783,7 +786,7 @@ class DeviceBooksModel(BooksModel):
|
||||
self.unknown = str(self.trUtf8('Unknown'))
|
||||
self.marked_for_deletion = {}
|
||||
self.search_engine = OnDeviceSearch(self)
|
||||
|
||||
self.editable = True
|
||||
|
||||
def mark_for_deletion(self, job, rows):
|
||||
self.marked_for_deletion[job] = self.indices(rows)
|
||||
@ -791,7 +794,6 @@ class DeviceBooksModel(BooksModel):
|
||||
indices = self.row_indices(row)
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), indices[0], indices[-1])
|
||||
|
||||
|
||||
def deletion_done(self, job, succeeded=True):
|
||||
if not self.marked_for_deletion.has_key(job):
|
||||
return
|
||||
@ -816,7 +818,7 @@ class DeviceBooksModel(BooksModel):
|
||||
if self.map[index.row()] in self.indices_to_be_deleted():
|
||||
return Qt.ItemIsUserCheckable # Can't figure out how to get the disabled flag in python
|
||||
flags = QAbstractTableModel.flags(self, index)
|
||||
if index.isValid():
|
||||
if index.isValid() and self.editable:
|
||||
if index.column() in [0, 1] or (index.column() == 4 and self.db.supports_tags()):
|
||||
flags |= Qt.ItemIsEditable
|
||||
return flags
|
||||
@ -997,6 +999,10 @@ class DeviceBooksModel(BooksModel):
|
||||
self.sort(col, self.sorted_on[1])
|
||||
done = True
|
||||
return done
|
||||
|
||||
def set_editable(self, editable):
|
||||
self.editable = editable
|
||||
|
||||
|
||||
class SearchBox(QLineEdit):
|
||||
|
||||
|
@ -644,7 +644,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
return
|
||||
mainlist, cardlist = job.result
|
||||
self.memory_view.set_database(mainlist)
|
||||
self.memory_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA)
|
||||
self.card_view.set_database(cardlist)
|
||||
self.card_view.set_editable(self.device_manager.device_class.CAN_SET_METADATA)
|
||||
for view in (self.memory_view, self.card_view):
|
||||
view.sortByColumn(3, Qt.DescendingOrder)
|
||||
if not view.restore_column_widths():
|
||||
|
Loading…
x
Reference in New Issue
Block a user