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