mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactored code using decorators and descriptors
time for ls -lR / reduced from 45s to 12s Added write support (touch, rm, mkdir, cp host->device)
This commit is contained in:
parent
7e1ca9880b
commit
6f1767a717
@ -20,6 +20,6 @@ the following rule in C{/etc/udev/rules.d/90-local.rules} ::
|
|||||||
BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"
|
BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"
|
||||||
You may have to adjust the GROUP and the location of the rules file to suit your distribution.
|
You may have to adjust the GROUP and the location of the rules file to suit your distribution.
|
||||||
"""
|
"""
|
||||||
VERSION = "0.1.1"
|
VERSION = "0.2"
|
||||||
__docformat__ = "epytext"
|
__docformat__ = "epytext"
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
@ -44,7 +44,7 @@ Contains the logic for communication with the device (a SONY PRS-500).
|
|||||||
|
|
||||||
The public interface of class L{PRS500Device} defines the methods for performing various tasks.
|
The public interface of class L{PRS500Device} defines the methods for performing various tasks.
|
||||||
"""
|
"""
|
||||||
import usb, sys
|
import usb, sys, os, time
|
||||||
from array import array
|
from array import array
|
||||||
|
|
||||||
from prstypes import *
|
from prstypes import *
|
||||||
@ -52,12 +52,13 @@ from errors import *
|
|||||||
|
|
||||||
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
|
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
|
||||||
_packet_number = 0 #: Keep track of the packet number of packet tracing
|
_packet_number = 0 #: Keep track of the packet number of packet tracing
|
||||||
|
KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L] #: Protocol versions libprs500 has been tested with
|
||||||
|
|
||||||
def _log_packet(packet, header, stream=sys.stderr):
|
def _log_packet(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. """
|
||||||
global _packet_number
|
global _packet_number
|
||||||
_packet_number += 1
|
_packet_number += 1
|
||||||
print >>stream, header, "(Packet #", str(_packet_number) + ")\n"
|
print >>stream, str(_packet_number), header, "Type:", packet.__class__.__name__
|
||||||
print >>stream, packet
|
print >>stream, packet
|
||||||
print >>stream, "--"
|
print >>stream, "--"
|
||||||
|
|
||||||
@ -76,7 +77,10 @@ class File(object):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
""" Return path to self """
|
""" Return path to self """
|
||||||
return self.path
|
return "File:"+self.path
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class DeviceDescriptor:
|
class DeviceDescriptor:
|
||||||
@ -124,6 +128,34 @@ class PRS500Device(object):
|
|||||||
PRS500_BULK_IN_EP = 0x81 #: Endpoint for Bulk reads
|
PRS500_BULK_IN_EP = 0x81 #: Endpoint for Bulk reads
|
||||||
PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes
|
PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes
|
||||||
|
|
||||||
|
def safe(func):
|
||||||
|
"""
|
||||||
|
Decorator that wraps a call to C{func} to ensure that exceptions are handled correctly.
|
||||||
|
|
||||||
|
As a convenience, C{safe} automatically sends the a L{USBConnect} after calling func, unless func has
|
||||||
|
a keyword argument named C{end_session} set to C{False}.
|
||||||
|
|
||||||
|
An L{ArgumentError} will cause the L{USBConnect} 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 USB interface via a call to L{close}.
|
||||||
|
"""
|
||||||
|
def run_session(*args, **kwargs):
|
||||||
|
dev = args[0]
|
||||||
|
res = None
|
||||||
|
try:
|
||||||
|
res = func(*args, **kwargs)
|
||||||
|
except ArgumentError, e:
|
||||||
|
if not kwargs.has_key("end_session") or kwargs["end_session"]:
|
||||||
|
dev._send_validated_command(USBConnect())
|
||||||
|
raise e
|
||||||
|
except usb.USBError, e:
|
||||||
|
dev.close()
|
||||||
|
raise e
|
||||||
|
if not kwargs.has_key("end_session") or kwargs["end_session"]:
|
||||||
|
dev._send_validated_command(USBConnect())
|
||||||
|
return res
|
||||||
|
|
||||||
|
return run_session
|
||||||
|
|
||||||
def __init__(self, log_packets=False) :
|
def __init__(self, log_packets=False) :
|
||||||
""" @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 """
|
||||||
self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID,
|
self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID,
|
||||||
@ -143,7 +175,9 @@ class PRS500Device(object):
|
|||||||
def open(self) :
|
def open(self) :
|
||||||
"""
|
"""
|
||||||
Claim an interface on the device for communication. Requires write privileges to the device file.
|
Claim an interface on the device for communication. Requires write privileges to the device file.
|
||||||
|
Also initialize the device. See the source code for the sequenceof initialization commands.
|
||||||
|
|
||||||
|
@todo: Implement unlocking of the device
|
||||||
@todo: Check this on Mac OSX
|
@todo: Check this on Mac OSX
|
||||||
"""
|
"""
|
||||||
self.device = self.device_descriptor.getDevice()
|
self.device = self.device_descriptor.getDevice()
|
||||||
@ -152,11 +186,22 @@ class PRS500Device(object):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
self.handle = self.device.open()
|
self.handle = self.device.open()
|
||||||
if sys.platform == 'darwin' :
|
if sys.platform == 'darwin' :
|
||||||
# XXX : For some reason, Mac OS X doesn't set the
|
# For some reason, Mac OS X doesn't set the
|
||||||
# configuration automatically like Linux does.
|
# configuration automatically like Linux does.
|
||||||
self.handle.setConfiguration(1) # TODO: Check on Mac OSX
|
self.handle.setConfiguration(1)
|
||||||
self.handle.claimInterface(self.device_descriptor.interface_id)
|
self.handle.claimInterface(self.device_descriptor.interface_id)
|
||||||
self.handle.reset()
|
self.handle.reset()
|
||||||
|
res = self._send_validated_command(GetUSBProtocolVersion())
|
||||||
|
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:
|
||||||
|
print >>sys.stderr, "WARNING: Usb protocol version " + hex(version) + " is unknown"
|
||||||
|
res = self._send_validated_command(SetBulkSize(size=0x028000))
|
||||||
|
if res.code != 0: raise ProtocolError("Unable to set bulk size.")
|
||||||
|
self._send_validated_command(UnlockDevice(key=0x312d))
|
||||||
|
if res.code != 0:
|
||||||
|
raise ProtocolError("Unlocking of device not implemented. Remove locking and retry.")
|
||||||
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
""" Release device interface """
|
""" Release device interface """
|
||||||
@ -189,115 +234,113 @@ class PRS500Device(object):
|
|||||||
PRS500Device._validate_response(res, type=command.type, number=cnumber)
|
PRS500Device._validate_response(res, type=command.type, number=cnumber)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _bulk_read_packet(self, data_type=Answer, size=4096):
|
def _bulk_write(self, data, packet_size=0x1000):
|
||||||
"""
|
"""
|
||||||
Read in a data packet via a Bulk Read.
|
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. C{data} is broken up into packets to be sent to device.
|
||||||
|
"""
|
||||||
|
def bulk_write_packet(packet):
|
||||||
|
self.handle.bulkWrite(PRS500Device.PRS500_BULK_OUT_EP, packet)
|
||||||
|
if self._log_packets: _log_packet(Answer(packet), "Answer h->d")
|
||||||
|
|
||||||
|
bytes_left = len(data)
|
||||||
|
if bytes_left + 16 <= packet_size:
|
||||||
|
packet_size = bytes_left +16
|
||||||
|
first_packet = Answer(bytes_left+16)
|
||||||
|
first_packet[16:] = data
|
||||||
|
first_packet.length = len(data)
|
||||||
|
else:
|
||||||
|
first_packet = Answer(packet_size)
|
||||||
|
first_packet[16:] = data[0:packet_size-16]
|
||||||
|
first_packet.length = packet_size-16
|
||||||
|
first_packet.number = 0x10005
|
||||||
|
bulk_write_packet(first_packet)
|
||||||
|
pos = first_packet.length
|
||||||
|
bytes_left -= first_packet.length
|
||||||
|
while bytes_left > 0:
|
||||||
|
endpos = pos + packet_size if pos + packet_size <= len(data) else len(data)
|
||||||
|
bulk_write_packet(data[pos:endpos])
|
||||||
|
bytes_left -= endpos - pos
|
||||||
|
pos = endpos
|
||||||
|
res = Response(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=5000))
|
||||||
|
if self._log_packets: _log_packet(res, "Response")
|
||||||
|
if res.rnumber != 0x10005 or res.code != 0:
|
||||||
|
raise ProtocolError("Sending via Bulk Transfer failed with response:\n"+str(res))
|
||||||
|
if res.data_size != len(data):
|
||||||
|
raise ProtocolError("Unable to transfer all data to device. Response packet:\n"+str(res))
|
||||||
|
|
||||||
@param data_type: an object of type type. The data packet is returned as an object of type C{data_type}.
|
|
||||||
@param size: the expected size of the data packet.
|
|
||||||
"""
|
|
||||||
data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size))
|
|
||||||
if self._log_packets: _log_packet(data, "Answer d->h")
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _bulk_read(self, bytes, command_number=0x00, packet_size=4096, data_type=Answer):
|
def _bulk_read(self, bytes, command_number=0x00, packet_size=4096, data_type=Answer):
|
||||||
""" Read in C{bytes} bytes via a bulk transfer in packets of size S{<=} C{packet_size} """
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
def bulk_read_packet(data_type=Answer, size=0x1000):
|
||||||
|
data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size))
|
||||||
|
if self._log_packets: _log_packet(data, "Answer d->h")
|
||||||
|
return data
|
||||||
|
|
||||||
bytes_left = bytes
|
bytes_left = bytes
|
||||||
packets = []
|
packets = []
|
||||||
while bytes_left > 0:
|
while bytes_left > 0:
|
||||||
if packet_size > bytes_left: packet_size = bytes_left
|
if packet_size > bytes_left: packet_size = bytes_left
|
||||||
packet = self._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)
|
||||||
packets.append(packet)
|
packets.append(packet)
|
||||||
self._send_validated_command(AcknowledgeBulkRead(packets[0].number), cnumber=command_number)
|
self._send_validated_command(AcknowledgeBulkRead(packets[0].number), cnumber=command_number)
|
||||||
return packets
|
return packets
|
||||||
|
|
||||||
def _test_bulk_reads(self):
|
@safe
|
||||||
""" Carries out a test of bulk reading as part of session initialization. """
|
def get_device_information(self, end_session=True):
|
||||||
self._send_validated_command( ShortCommand(number=0x00, type=0x01, command=0x00) )
|
|
||||||
self._bulk_read(24, command_number=0x00)
|
|
||||||
|
|
||||||
def _start_session(self):
|
|
||||||
"""
|
"""
|
||||||
Send the initialization sequence to the device. See the code for details.
|
Ask device for device information. See L{DeviceInfoQuery}.
|
||||||
This method should be called before any real work is done. Though most things seem to work without it.
|
@return: (device name, device version, software version on device, mime type)
|
||||||
"""
|
"""
|
||||||
self.handle.reset()
|
|
||||||
self._test_bulk_reads()
|
|
||||||
self._send_validated_command( ShortCommand(number=0x0107, command=0x028000, type=0x01) ) # TODO: Figure out the meaning of this command
|
|
||||||
self._test_bulk_reads()
|
|
||||||
self._send_validated_command( ShortCommand(number=0x0106, type=0x01, command=0x312d) ) # TODO: Figure out the meaning of this command
|
|
||||||
self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x01) )
|
|
||||||
|
|
||||||
def _end_session(self):
|
|
||||||
""" Send the end session command to the device. Causes the device to change status from "Do not disconnect" to "USB Connected" """
|
|
||||||
self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x00) )
|
|
||||||
|
|
||||||
def _run_session(self, *args):
|
|
||||||
"""
|
|
||||||
Wrapper that automatically calls L{_start_session} and L{_end_session}.
|
|
||||||
|
|
||||||
@param args: An array whose first element is the method to call and whose remaining arguments are passed to that mathos as an array.
|
|
||||||
"""
|
|
||||||
self._start_session()
|
|
||||||
res = None
|
|
||||||
try:
|
|
||||||
res = args[0](args[1:])
|
|
||||||
except ArgumentError, e:
|
|
||||||
self._end_session()
|
|
||||||
raise e
|
|
||||||
self._end_session()
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _get_device_information(self, args):
|
|
||||||
""" Ask device for device information. See L{DeviceInfoQuery}. """
|
|
||||||
size = self._send_validated_command(DeviceInfoQuery()).data[2] + 16
|
size = self._send_validated_command(DeviceInfoQuery()).data[2] + 16
|
||||||
data = self._bulk_read(size, command_number=DeviceInfoQuery.NUMBER, data_type=DeviceInfo)[0]
|
data = self._bulk_read(size, command_number=DeviceInfoQuery.NUMBER, data_type=DeviceInfo)[0]
|
||||||
return (data.device_name, data.device_version, data.software_version, data.mime_type)
|
return (data.device_name, data.device_version, data.software_version, data.mime_type)
|
||||||
|
|
||||||
def get_device_information(self):
|
@safe
|
||||||
""" Return (device name, device version, software version on device, mime type). See L{_get_device_information} """
|
def path_properties(self, path, end_session=True):
|
||||||
return self._run_session(self._get_device_information)
|
""" Send command asking device for properties of C{path}. Return L{FileProperties}. """
|
||||||
|
res = self._send_validated_command(PathQuery(path), response_type=ListResponse)
|
||||||
def _get_path_properties(self, path):
|
|
||||||
""" Send command asking device for properties of C{path}. Return (L{Response}, L{Answer}). """
|
|
||||||
res = self._send_validated_command(PathQuery(path), response_type=ListResponse)
|
|
||||||
data = self._bulk_read(0x28, data_type=FileProperties, command_number=PathQuery.NUMBER)[0]
|
data = self._bulk_read(0x28, data_type=FileProperties, command_number=PathQuery.NUMBER)[0]
|
||||||
if path.endswith("/"): path = path[:-1]
|
if path.endswith("/"): path = path[:-1]
|
||||||
if res.path_not_found : raise PathError(path + " does not exist on device")
|
if res.path_not_found : raise PathError(path + " does not exist on device")
|
||||||
if res.is_invalid : raise PathError(path + " is not a valid path")
|
if res.is_invalid : raise PathError(path + " is not a valid path")
|
||||||
if res.is_unmounted : raise PathError(path + " is not mounted")
|
if res.is_unmounted : raise PathError(path + " is not mounted")
|
||||||
return (res, data)
|
if res.code not in (0, PathResponseCodes.IS_FILE):
|
||||||
|
raise PathError(path + " has an unknown error. Code: " + hex(res.code))
|
||||||
|
return data
|
||||||
|
|
||||||
def get_file(self, path, outfile):
|
@safe
|
||||||
|
def get_file(self, path, outfile, end_session=True):
|
||||||
"""
|
"""
|
||||||
Read the file at path on the device and write it to outfile. For the logic see L{_get_file}.
|
Read the file at path on the device and write it to outfile. For the logic see L{_get_file}.
|
||||||
|
|
||||||
@param outfile: file object like C{sys.stdout} or the result of an C{open} call
|
|
||||||
"""
|
|
||||||
self._run_session(self._get_file, path, outfile)
|
|
||||||
|
|
||||||
def _get_file(self, args):
|
|
||||||
"""
|
|
||||||
Fetch a file from the device and write it to an output stream.
|
|
||||||
|
|
||||||
The data is fetched in chunks of size S{<=} 32K. Each chunk is make of packets of size S{<=} 4K. See L{FileOpen},
|
The data is fetched in chunks of size S{<=} 32K. Each chunk is make 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 args: C{path, outfile = arg[0], arg[1]}
|
@param outfile: file object like C{sys.stdout} or the result of an C{open} call
|
||||||
"""
|
"""
|
||||||
path, outfile = args[0], args[1]
|
|
||||||
if path.endswith("/"): path = path[:-1] # We only copy files
|
if path.endswith("/"): path = path[:-1] # We only copy files
|
||||||
res, data = self._get_path_properties(path)
|
file = self.path_properties(path, end_session=False)
|
||||||
if data.is_dir: raise PathError("Cannot read as " + path + " is a directory")
|
if file.is_dir: raise PathError("Cannot read as " + path + " is a directory")
|
||||||
bytes = data.file_size
|
bytes = file.file_size
|
||||||
self._send_validated_command(FileOpen(path))
|
res = self._send_validated_command(FileOpen(path))
|
||||||
|
if res.code != 0:
|
||||||
|
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
|
id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id
|
||||||
bytes_left, chunk_size, pos = bytes, 0x8000, 0
|
bytes_left, chunk_size, pos = bytes, 0x8000, 0
|
||||||
while bytes_left > 0:
|
while bytes_left > 0:
|
||||||
if chunk_size > bytes_left: chunk_size = bytes_left
|
if chunk_size > bytes_left: chunk_size = bytes_left
|
||||||
res = self._send_validated_command(FileRead(id, pos, chunk_size))
|
res = self._send_validated_command(FileIO(id, pos, chunk_size))
|
||||||
packets = self._bulk_read(chunk_size+16, command_number=FileRead.NUMBER, packet_size=4096)
|
if res.code != 0:
|
||||||
|
self._send_validated_command(FileClose(id))
|
||||||
|
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=4096)
|
||||||
try:
|
try:
|
||||||
array('B', packets[0][16:]).tofile(outfile) # The first 16 bytes are meta information on the packet stream
|
array('B', packets[0][16:]).tofile(outfile) # The first 16 bytes are meta information on the packet stream
|
||||||
for i in range(1, len(packets)):
|
for i in range(1, len(packets)):
|
||||||
@ -308,70 +351,69 @@ class PRS500Device(object):
|
|||||||
bytes_left -= chunk_size
|
bytes_left -= chunk_size
|
||||||
pos += chunk_size
|
pos += chunk_size
|
||||||
self._send_validated_command(FileClose(id))
|
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
|
||||||
|
|
||||||
|
|
||||||
def _list(self, args):
|
|
||||||
|
@safe
|
||||||
|
def list(self, path, recurse=False, end_session=True):
|
||||||
"""
|
"""
|
||||||
Ask the device to list a path. See the code for details. See L{DirOpen},
|
Return a listing of path. See the code for details. See L{DirOpen},
|
||||||
L{DirRead} and L{DirClose} for details on the command packets used.
|
L{DirRead} and L{DirClose} for details on the command packets used.
|
||||||
|
|
||||||
@param args: C{path=args[0]}
|
|
||||||
@return: A list of tuples. The first element of each tuple is a string, the path. The second is a L{FileProperties}.
|
|
||||||
If the path points to a file, the list will have length 1.
|
|
||||||
"""
|
|
||||||
path = args[0]
|
|
||||||
if not path.endswith("/"): path += "/" # Initially assume path is a directory
|
|
||||||
files = []
|
|
||||||
res, data = self._get_path_properties(path)
|
|
||||||
if res.is_file:
|
|
||||||
path = path[:-1]
|
|
||||||
res, data = self._get_path_properties(path)
|
|
||||||
files = [ (path, data) ]
|
|
||||||
else:
|
|
||||||
# Get query ID used to ask for next element in list
|
|
||||||
self._send_validated_command(DirOpen(path), response_type=ListResponse)
|
|
||||||
id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id
|
|
||||||
# Create command asking for next element in list
|
|
||||||
next = DirRead(id)
|
|
||||||
items = []
|
|
||||||
while True:
|
|
||||||
res = self._send_validated_command(next, response_type=ListResponse)
|
|
||||||
size = res.data[2] + 16
|
|
||||||
data = self._bulk_read(size, data_type=ListAnswer, command_number=DirRead.NUMBER)[0]
|
|
||||||
# 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
|
|
||||||
items.append(data.name)
|
|
||||||
self._send_validated_command(DirClose(id))
|
|
||||||
for item in items:
|
|
||||||
ipath = path + item
|
|
||||||
res, data = self._get_path_properties(ipath)
|
|
||||||
files.append( (ipath, data) )
|
|
||||||
files.sort()
|
|
||||||
return files
|
|
||||||
|
|
||||||
def list(self, path, recurse=False):
|
|
||||||
"""
|
|
||||||
Return a listing of path.
|
|
||||||
|
|
||||||
See L{_list} for the communication logic.
|
|
||||||
|
|
||||||
@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. The second element is a list of L{Files<File>}.
|
@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
|
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.
|
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.
|
||||||
"""
|
"""
|
||||||
files = self._run_session(self._list, path)
|
def _list(path): # Do a non recursive listsing of path
|
||||||
files = [ File(file) for file in files ]
|
if not path.endswith("/"): path += "/" # Initially assume path is a directory
|
||||||
|
files = []
|
||||||
|
candidate = self.path_properties(path, end_session=False)
|
||||||
|
if not candidate.is_dir:
|
||||||
|
path = path[:-1]
|
||||||
|
data = self.path_properties(path, end_session=False)
|
||||||
|
files = [ File((path, data)) ]
|
||||||
|
else:
|
||||||
|
# Get query ID used to ask for next element in list
|
||||||
|
res = self._send_validated_command(DirOpen(path))
|
||||||
|
if res.code != 0:
|
||||||
|
raise PathError("Unable to open directory " + path + " for reading. Response code: " + hex(res.code))
|
||||||
|
id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id
|
||||||
|
# Create command asking for next element in list
|
||||||
|
next = DirRead(id)
|
||||||
|
items = []
|
||||||
|
while True:
|
||||||
|
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 doesn't have the permissions to access the directory
|
||||||
|
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: " + haex(res.code))
|
||||||
|
items.append(data.name)
|
||||||
|
self._send_validated_command(DirClose(id)) # Ignore res.code as we cant do anything if close fails
|
||||||
|
for item in items:
|
||||||
|
ipath = path + item
|
||||||
|
data = self.path_properties(ipath, end_session=False)
|
||||||
|
files.append( File( (ipath, data) ) )
|
||||||
|
files.sort()
|
||||||
|
return files
|
||||||
|
|
||||||
|
files = _list(path)
|
||||||
dirs = [(path, files)]
|
dirs = [(path, files)]
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")):
|
if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")):
|
||||||
dirs[len(dirs):] = self.list(file.path, recurse=True)
|
dirs[len(dirs):] = self.list(file.path, recurse=True, end_session=False)
|
||||||
return dirs
|
return dirs
|
||||||
|
|
||||||
def available_space(self):
|
@safe
|
||||||
|
def available_space(self, end_session=True):
|
||||||
"""
|
"""
|
||||||
Get free space available on the mountpoints:
|
Get free space available on the mountpoints:
|
||||||
1. /Data/ Device memory
|
1. /Data/ Device memory
|
||||||
@ -380,10 +422,6 @@ class PRS500Device(object):
|
|||||||
|
|
||||||
@return: A list of tuples. Each tuple has form ("location", free space, total space)
|
@return: A list of tuples. Each tuple has form ("location", free space, total space)
|
||||||
"""
|
"""
|
||||||
return self._run_session(self._available_space)
|
|
||||||
|
|
||||||
def _available_space(self, args):
|
|
||||||
""" L{available_space} """
|
|
||||||
data = []
|
data = []
|
||||||
for path in ("/Data/", "a:/", "b:/"):
|
for path in ("/Data/", "a:/", "b:/"):
|
||||||
res = self._send_validated_command(FreeSpaceQuery(path),timeout=5000) # Timeout needs to be increased as it takes time to read card
|
res = self._send_validated_command(FreeSpaceQuery(path),timeout=5000) # Timeout needs to be increased as it takes time to read card
|
||||||
@ -392,3 +430,113 @@ class PRS500Device(object):
|
|||||||
data.append( (path, pkt.free_space, pkt.total) )
|
data.append( (path, pkt.free_space, pkt.total) )
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def _exists(self, path):
|
||||||
|
""" Return (True, FileProperties) if path exists or (False, None) otherwise """
|
||||||
|
dest = None
|
||||||
|
try:
|
||||||
|
dest = self.path_properties(path, end_session=False)
|
||||||
|
except PathError, e:
|
||||||
|
if "does not exist" in str(e): return (False, None)
|
||||||
|
else: raise e
|
||||||
|
return (True, dest)
|
||||||
|
|
||||||
|
@safe
|
||||||
|
def touch(self, path, end_session=True):
|
||||||
|
"""
|
||||||
|
Create a file at path
|
||||||
|
|
||||||
|
@todo: Update file modification time if it exists
|
||||||
|
"""
|
||||||
|
if path.endswith("/") and len(path) > 1: path = path[:-1]
|
||||||
|
exists, file = self._exists(path)
|
||||||
|
if exists and file.is_dir:
|
||||||
|
raise PathError("Cannot touch directories")
|
||||||
|
if not exists:
|
||||||
|
res = self._send_validated_command(FileCreate(path))
|
||||||
|
if res.code != 0:
|
||||||
|
raise PathError("Could not create file " + path + ". Response code: " + str(hex(res.code)))
|
||||||
|
## res = self._send_validated_command(SetFileInfo(path))
|
||||||
|
## if res.code != 0:
|
||||||
|
## raise ProtocolError("Unable to touch " + path + ". Response code: " + hex(res.code))
|
||||||
|
## file.wtime = int(time.time())
|
||||||
|
## self._bulk_write(file[16:])
|
||||||
|
|
||||||
|
|
||||||
|
@safe
|
||||||
|
def put_file(self, infile, path, end_session=True):
|
||||||
|
exists, dest = self._exists(path)
|
||||||
|
if exists:
|
||||||
|
if not dest.is_dir: raise PathError("Cannot write to " + path + " as it already exists")
|
||||||
|
if not path.endswith("/"): path += "/"
|
||||||
|
path += os.path.basename(infile.name)
|
||||||
|
exists, dest = self._exists(path)
|
||||||
|
if exists: raise PathError("Cannot write to " + path + " as it already exists")
|
||||||
|
res = self._send_validated_command(FileCreate(path))
|
||||||
|
if res.code != 0:
|
||||||
|
raise ProtocolError("There was an error creating device:"+path+". Response code: "+hex(res.code))
|
||||||
|
chunk_size = 0x8000
|
||||||
|
data_left = True
|
||||||
|
res = self._send_validated_command(FileOpen(path, mode=FileOpen.WRITE))
|
||||||
|
if res.code != 0:
|
||||||
|
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
|
||||||
|
pos = 0
|
||||||
|
while data_left:
|
||||||
|
data = array('B')
|
||||||
|
try:
|
||||||
|
data.fromfile(infile, chunk_size)
|
||||||
|
except EOFError:
|
||||||
|
data_left = False
|
||||||
|
res = self._send_validated_command(FileIO(id, pos, len(data), mode=FileIO.WNUMBER))
|
||||||
|
if res.code != 0:
|
||||||
|
raise ProtocolError("Unable to write to " + path + ". Response code: " + hex(res.code))
|
||||||
|
self._bulk_write(data)
|
||||||
|
pos += len(data)
|
||||||
|
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:
|
||||||
|
raise ProtocolError("Copying to device failed. The file was truncated by " + str(data.file_size - pos) + " bytes")
|
||||||
|
|
||||||
|
@safe
|
||||||
|
def del_file(self, path, end_session=True):
|
||||||
|
data = self.path_properties(path, end_session=False)
|
||||||
|
if data.is_dir: raise PathError("Cannot delete directories")
|
||||||
|
res = self._send_validated_command(FileDelete(path), response_type=ListResponse)
|
||||||
|
if res.code != 0:
|
||||||
|
raise ProtocolError("Unable to delete " + path + " with response:\n" + str(res))
|
||||||
|
|
||||||
|
@safe
|
||||||
|
def mkdir(self, path, end_session=True):
|
||||||
|
if not path.endswith("/"): path += "/"
|
||||||
|
error_prefix = "Cannot create directory " + path
|
||||||
|
res = self._send_validated_command(DirCreate(path)).data[0]
|
||||||
|
if res == 0xffffffcc:
|
||||||
|
raise PathError(error_prefix + " as it already exists")
|
||||||
|
elif res == PathResponseCodes.NOT_FOUND:
|
||||||
|
raise PathError(error_prefix + " as " + path[0:path[:-1].rfind("/")] + " does not exist ")
|
||||||
|
elif res == PathResponseCodes.INVALID:
|
||||||
|
raise PathError(error_prefix + " as " + path + " is invalid")
|
||||||
|
elif res != 0:
|
||||||
|
raise PathError(error_prefix + ". Response code: " + hex(res))
|
||||||
|
|
||||||
|
@safe
|
||||||
|
def rm(self, path, end_session=True):
|
||||||
|
""" Delete path from device if it is a file or an empty directory """
|
||||||
|
dir = self.path_properties(path, end_session=False)
|
||||||
|
if not dir.is_dir:
|
||||||
|
self.del_file(path, end_session=False)
|
||||||
|
else:
|
||||||
|
if not path.endswith("/"): path += "/"
|
||||||
|
res = self._send_validated_command(DirDelete(path))
|
||||||
|
if res.code == PathResponseCodes.HAS_CHILDREN:
|
||||||
|
raise PathError("Cannot delete directory " + path + " as it is not empty")
|
||||||
|
if res.code != 0:
|
||||||
|
raise ProtocolError("Failed to delete directory " + path + ". Response code: " + hex(res.code))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#dev = PRS500Device(log_packets=False)
|
||||||
|
#dev.open()
|
||||||
|
#print dev.get_file("/etc/sysctl.conf", sys.stdout)
|
||||||
|
#dev.close()
|
||||||
|
|
||||||
|
@ -44,6 +44,14 @@ DWORD = "<I" #: Unsigned integer little endian encoded in 4 bytes
|
|||||||
DDWORD = "<Q" #: Unsigned long long little endian encoded in 8 bytes
|
DDWORD = "<Q" #: Unsigned long long little endian encoded in 8 bytes
|
||||||
|
|
||||||
|
|
||||||
|
class PathResponseCodes(object):
|
||||||
|
""" Known response commands to path related commands """
|
||||||
|
NOT_FOUND = 0xffffffd7
|
||||||
|
INVALID = 0xfffffff9
|
||||||
|
IS_FILE = 0xffffffd2
|
||||||
|
HAS_CHILDREN = 0xffffffcc
|
||||||
|
|
||||||
|
|
||||||
class TransferBuffer(list):
|
class TransferBuffer(list):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -333,6 +341,27 @@ class DirClose(ShortCommand):
|
|||||||
""" @param id: The identifier returned as a result of a L{DirOpen} command """
|
""" @param id: The identifier returned as a result of a L{DirOpen} command """
|
||||||
ShortCommand.__init__(self, number=DirClose.NUMBER, type=0x01, command=id)
|
ShortCommand.__init__(self, number=DirClose.NUMBER, type=0x01, command=id)
|
||||||
|
|
||||||
|
class USBConnect(ShortCommand):
|
||||||
|
""" Ask device to change status to 'USB connected' i.e., tell the device that the present sequence of commands is complete """
|
||||||
|
NUMBER=0x1 #: Command number
|
||||||
|
def __init__(self):
|
||||||
|
ShortCommand.__init__(self, number=USBConnect.NUMBER, type=0x01, command=0x00)
|
||||||
|
|
||||||
|
class GetUSBProtocolVersion(ShortCommand):
|
||||||
|
""" Get USB Protocol version used by device """
|
||||||
|
NUMBER=0x0 #: Command number
|
||||||
|
def __init__(self):
|
||||||
|
ShortCommand.__init__(self, number=GetUSBProtocolVersion.NUMBER, type=0x01, command=0x00)
|
||||||
|
|
||||||
|
class SetBulkSize(ShortCommand):
|
||||||
|
NUMBER = 0x107 #: Command number
|
||||||
|
def __init__(self, size=0x028000):
|
||||||
|
ShortCommand.__init__(self, number=SetBulkSize.NUMBER, type=0x01, command=size)
|
||||||
|
|
||||||
|
class UnlockDevice(ShortCommand):
|
||||||
|
NUMBER = 0x106 #: Command number
|
||||||
|
def __init__(self, key=0x312d):
|
||||||
|
ShortCommand.__init__(self, number=UnlockDevice.NUMBER, type=0x01, command=key)
|
||||||
|
|
||||||
class LongCommand(Command):
|
class LongCommand(Command):
|
||||||
|
|
||||||
@ -390,6 +419,12 @@ class FreeSpaceQuery(PathCommand):
|
|||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
PathCommand.__init__(self, path, FreeSpaceQuery.NUMBER)
|
PathCommand.__init__(self, path, FreeSpaceQuery.NUMBER)
|
||||||
|
|
||||||
|
class DirCreate(PathCommand):
|
||||||
|
""" Create a directory """
|
||||||
|
NUMBER = 0x30
|
||||||
|
def __init__(self, path):
|
||||||
|
PathCommand.__init__(self, path, DirCreate.NUMBER)
|
||||||
|
|
||||||
class DirOpen(PathCommand):
|
class DirOpen(PathCommand):
|
||||||
""" Open a directory for reading its contents """
|
""" Open a directory for reading its contents """
|
||||||
NUMBER = 0x33 #: Command number
|
NUMBER = 0x33 #: Command number
|
||||||
@ -417,6 +452,24 @@ class FileClose(ShortCommand):
|
|||||||
def __init__(self, id):
|
def __init__(self, id):
|
||||||
ShortCommand.__init__(self, number=FileClose.NUMBER, type=0x01, command=id)
|
ShortCommand.__init__(self, number=FileClose.NUMBER, type=0x01, command=id)
|
||||||
|
|
||||||
|
class FileCreate(PathCommand):
|
||||||
|
""" Create a file """
|
||||||
|
NUMBER=0x1a #: Command number
|
||||||
|
def __init__(self, path):
|
||||||
|
PathCommand.__init__(self, path, FileCreate.NUMBER)
|
||||||
|
|
||||||
|
class FileDelete(PathCommand):
|
||||||
|
""" Delete a file """
|
||||||
|
NUMBER=0x1B
|
||||||
|
def __init__(self, path):
|
||||||
|
PathCommand.__init__(self, path, FileDelete.NUMBER)
|
||||||
|
|
||||||
|
class DirDelete(PathCommand):
|
||||||
|
""" Delete a directory """
|
||||||
|
NUMBER=0x31
|
||||||
|
def __init__(self, path):
|
||||||
|
PathCommand.__init__(self, path, DirDelete.NUMBER)
|
||||||
|
|
||||||
class FileOpen(PathCommand):
|
class FileOpen(PathCommand):
|
||||||
""" File open command """
|
""" File open command """
|
||||||
NUMBER = 0x10 #: Command number
|
NUMBER = 0x10 #: Command number
|
||||||
@ -442,13 +495,14 @@ class FileOpen(PathCommand):
|
|||||||
return property(**locals())
|
return property(**locals())
|
||||||
|
|
||||||
|
|
||||||
class FileRead(Command):
|
class FileIO(Command):
|
||||||
""" Command to read from an open file """
|
""" Command to read/write from an open file """
|
||||||
NUMBER = 0x16 #: Command number to read from a file
|
RNUMBER = 0x16 #: Command number to read from a file
|
||||||
|
WNUMBER = 0x17 #: Command number to write to a file
|
||||||
id = field(start=16, fmt=DWORD) #: The file ID returned by a FileOpen command
|
id = field(start=16, fmt=DWORD) #: The file ID returned by a FileOpen command
|
||||||
offset = field(start=20, fmt=DDWORD) #: offset in the file at which to read
|
offset = field(start=20, fmt=DDWORD) #: offset in the file at which to read
|
||||||
size = field(start=28, fmt=DWORD) #: The number of bytes to reead from file.
|
size = field(start=28, fmt=DWORD) #: The number of bytes to reead from file.
|
||||||
def __init__(self, id, offset, size):
|
def __init__(self, id, offset, size, mode=0x16):
|
||||||
"""
|
"""
|
||||||
@param id: File identifier returned by a L{FileOpen} command
|
@param id: File identifier returned by a L{FileOpen} command
|
||||||
@type id: C{unsigned int}
|
@type id: C{unsigned int}
|
||||||
@ -456,9 +510,10 @@ class FileRead(Command):
|
|||||||
@type offset: C{unsigned long long}
|
@type offset: C{unsigned long long}
|
||||||
@param size: number of bytes to read
|
@param size: number of bytes to read
|
||||||
@type size: C{unsigned int}
|
@type size: C{unsigned int}
|
||||||
|
@param mode: Either L{FileIO.RNUMBER} or L{File.WNUMBER}
|
||||||
"""
|
"""
|
||||||
Command.__init__(self, 32)
|
Command.__init__(self, 32)
|
||||||
self.number=FileRead.NUMBER
|
self.number=mode
|
||||||
self.type = 0x01
|
self.type = 0x01
|
||||||
self.length = 16
|
self.length = 16
|
||||||
self.id = id
|
self.id = id
|
||||||
@ -472,6 +527,11 @@ class PathQuery(PathCommand):
|
|||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
PathCommand.__init__(self, path, PathQuery.NUMBER)
|
PathCommand.__init__(self, path, PathQuery.NUMBER)
|
||||||
|
|
||||||
|
class SetFileInfo(PathCommand):
|
||||||
|
""" Set File information """
|
||||||
|
NUMBER = 0x19 #: Command number
|
||||||
|
def __init__(self, path):
|
||||||
|
PathCommand.__init__(self, path, SetFileInfo.NUMBER)
|
||||||
|
|
||||||
class Response(Command):
|
class Response(Command):
|
||||||
"""
|
"""
|
||||||
@ -480,8 +540,10 @@ class Response(Command):
|
|||||||
C{Response} inherits from C{Command} as the first 16 bytes have the same structure.
|
C{Response} inherits from C{Command} as the first 16 bytes have the same structure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SIZE = 32 #: Size of response packets in the SONY protocol
|
SIZE = 32 #: Size of response packets in the SONY protocol
|
||||||
rnumber = field(start=16, fmt=DWORD) #: Response number, the command number of a command packet sent sometime before this packet was received
|
rnumber = field(start=16, fmt=DWORD) #: Response number, the command number of a command packet sent sometime before this packet was received
|
||||||
|
code = field(start=20, fmt=DWORD) #: Used to indicate error conditions. A value of 0 means there was no error
|
||||||
|
data_size = field(start=28, fmt=DWORD) #: Used to indicate the size of the next bulk read
|
||||||
|
|
||||||
def __init__(self, packet):
|
def __init__(self, packet):
|
||||||
""" C{len(packet) == Response.SIZE} """
|
""" C{len(packet) == Response.SIZE} """
|
||||||
@ -513,8 +575,6 @@ class ListResponse(Response):
|
|||||||
IS_EOL = 0xfffffffa #: There are no more entries in the list
|
IS_EOL = 0xfffffffa #: There are no more entries in the list
|
||||||
PATH_NOT_FOUND = 0xffffffd7 #: Queried path is not found
|
PATH_NOT_FOUND = 0xffffffd7 #: Queried path is not found
|
||||||
|
|
||||||
code = field(start=20, fmt=DWORD) #: Used to indicate conditions like EOL/Error/IsFile etc.
|
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def is_file():
|
def is_file():
|
||||||
""" True iff queried path is a file """
|
""" True iff queried path is a file """
|
||||||
@ -553,11 +613,16 @@ class ListResponse(Response):
|
|||||||
class Answer(TransferBuffer):
|
class Answer(TransferBuffer):
|
||||||
""" Defines the structure of packets sent to host via a bulk transfer (i.e., bulk reads) """
|
""" Defines the structure of packets sent to host via a bulk transfer (i.e., bulk reads) """
|
||||||
|
|
||||||
number = field(start=0, fmt=DWORD) #: Answer identifier, should be sent in an acknowledgement packet
|
number = field(start=0, fmt=DWORD) #: Answer identifier
|
||||||
|
length = field(start=12, fmt=DWORD) #: Length of data to follow
|
||||||
|
|
||||||
def __init__(self, packet):
|
def __init__(self, packet):
|
||||||
""" @param packet: C{len(packet)} S{>=} C{16} """
|
""" @param packet: C{len(packet)} S{>=} C{16} """
|
||||||
if len(packet) < 16 : raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
|
if "__len__" in dir(packet):
|
||||||
|
if len(packet) < 16 :
|
||||||
|
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
|
||||||
|
elif packet < 16:
|
||||||
|
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
|
||||||
TransferBuffer.__init__(self, packet)
|
TransferBuffer.__init__(self, packet)
|
||||||
|
|
||||||
|
|
||||||
@ -565,38 +630,30 @@ class FileProperties(Answer):
|
|||||||
|
|
||||||
""" Defines the structure of packets that contain size, date and permissions information about files/directories. """
|
""" Defines the structure of packets that contain size, date and permissions information about files/directories. """
|
||||||
|
|
||||||
file_size = field(start=16, fmt=DDWORD)
|
file_size = field(start=16, fmt=DDWORD) #: Size in bytes of the file
|
||||||
ctime = field(start=28, fmt=DDWORD) #: Creation time
|
file_type = field(start=24, fmt=DWORD) #: 1 == file, 2 == dir
|
||||||
wtime = field(start=16, fmt=DDWORD) #: Modification time
|
ctime = field(start=28, fmt=DWORD) #: Creation time
|
||||||
|
wtime = field(start=32, fmt=DWORD) #: Modification time
|
||||||
|
permissions = field(start=36, fmt=DWORD) #: 0 = default permissions, 4 = read only
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def is_dir():
|
def is_dir():
|
||||||
doc =\
|
doc = """True if path points to a directory, False if it points to a file."""
|
||||||
"""
|
|
||||||
True if path points to a directory, False if it points to a file. C{unsigned int} stored in 4 bytes at byte 24.
|
|
||||||
|
|
||||||
Value of 1 == file and 2 == dir
|
|
||||||
"""
|
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return (self.unpack(start=24, fmt=DWORD)[0] == 2)
|
return (self.file_type == 2)
|
||||||
|
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
if val: val = 2
|
if val: val = 2
|
||||||
else: val = 1
|
else: val = 1
|
||||||
self.pack(val, start=24, fmt=DWORD)
|
self.file_type = val
|
||||||
|
|
||||||
return property(**locals())
|
return property(**locals())
|
||||||
|
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def is_readonly():
|
def is_readonly():
|
||||||
doc =\
|
doc = """ Whether this file is readonly."""
|
||||||
"""
|
|
||||||
Whether this file is readonly. C{unsigned int} stored in 4 bytes at byte 36.
|
|
||||||
|
|
||||||
A value of 0 corresponds to read/write and 4 corresponds to read-only. The device doesn't send full permissions information.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return self.unpack(start=36, fmt=DWORD)[0] != 0
|
return self.unpack(start=36, fmt=DWORD)[0] != 0
|
||||||
@ -608,6 +665,10 @@ class FileProperties(Answer):
|
|||||||
|
|
||||||
return property(**locals())
|
return property(**locals())
|
||||||
|
|
||||||
|
|
||||||
|
class USBProtocolVersion(Answer):
|
||||||
|
version = field(start=16, fmt=DDWORD)
|
||||||
|
|
||||||
class IdAnswer(Answer):
|
class IdAnswer(Answer):
|
||||||
|
|
||||||
""" Defines the structure of packets that contain identifiers for queries. """
|
""" Defines the structure of packets that contain identifiers for queries. """
|
||||||
@ -637,8 +698,8 @@ class FreeSpaceAnswer(Answer):
|
|||||||
total = field(start=24, fmt=DDWORD)
|
total = field(start=24, fmt=DDWORD)
|
||||||
free_space = field(start=32, fmt=DDWORD)
|
free_space = field(start=32, fmt=DDWORD)
|
||||||
|
|
||||||
class ListAnswer(Answer):
|
|
||||||
|
|
||||||
|
class ListAnswer(Answer):
|
||||||
""" Defines the structure of packets that contain items in a list. """
|
""" Defines the structure of packets that contain items in a list. """
|
||||||
name_length = field(start=20, fmt=DWORD)
|
name_length = field(start=20, fmt=DWORD)
|
||||||
name = stringfield(name_length, start=24)
|
name = stringfield(name_length, start=24)
|
||||||
|
@ -73,14 +73,14 @@ class FileFormatter(object):
|
|||||||
def modification_time():
|
def modification_time():
|
||||||
doc=""" Last modified time in the Linux ls -l format """
|
doc=""" Last modified time in the Linux ls -l format """
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return time.strftime("%Y-%m-%d %H:%M", time.gmtime(self.wtime))
|
return time.strftime("%Y-%m-%d %H:%M", time.localtime(self.wtime))
|
||||||
return property(**locals())
|
return property(**locals())
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def creation_time():
|
def creation_time():
|
||||||
doc=""" Last modified time in the Linux ls -l format """
|
doc=""" Last modified time in the Linux ls -l format """
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return time.strftime("%Y-%m-%d %H:%M", time.gmtime(self.ctime))
|
return time.strftime("%Y-%m-%d %H:%M", time.localtime(self.ctime))
|
||||||
return property(**locals())
|
return property(**locals())
|
||||||
|
|
||||||
def info(dev):
|
def info(dev):
|
||||||
@ -165,9 +165,11 @@ def main():
|
|||||||
term = TerminalController()
|
term = TerminalController()
|
||||||
cols = term.COLS
|
cols = term.COLS
|
||||||
|
|
||||||
parser = OptionParser(usage="usage: %prog [options] command args\n\ncommand is one of: info, df, ls, cp, cat or rm\n\n"+
|
parser = OptionParser(usage="usage: %prog [options] command args\n\ncommand is one of: info, df, ls, cp, mkdir, touch, cat or rm\n\n"+
|
||||||
"For help on a particular command: %prog command", version="libprs500 version: " + VERSION)
|
"For help on a particular command: %prog command", version="libprs500 version: " + VERSION)
|
||||||
parser.add_option("--log-packets", help="print out packet stream to stdout", dest="log_packets", action="store_true", default=False)
|
parser.add_option("--log-packets", help="print out packet stream to stdout. "+\
|
||||||
|
"The numbers in the left column are byte offsets that allow the packet size to be read off easily.",
|
||||||
|
dest="log_packets", action="store_true", default=False)
|
||||||
parser.remove_option("-h")
|
parser.remove_option("-h")
|
||||||
parser.disable_interspersed_args() # Allow unrecognized options
|
parser.disable_interspersed_args() # Allow unrecognized options
|
||||||
options, args = parser.parse_args()
|
options, args = parser.parse_args()
|
||||||
@ -178,80 +180,111 @@ def main():
|
|||||||
command = args[0]
|
command = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
dev = PRS500Device(log_packets=options.log_packets)
|
dev = PRS500Device(log_packets=options.log_packets)
|
||||||
if command == "df":
|
dev.open()
|
||||||
dev.open()
|
try:
|
||||||
data = dev.available_space()
|
if command == "df":
|
||||||
dev.close()
|
data = dev.available_space()
|
||||||
print "Filesystem\tSize \tUsed \tAvail \tUse%"
|
print "Filesystem\tSize \tUsed \tAvail \tUse%"
|
||||||
for datum in data:
|
for datum in data:
|
||||||
total, free, used, percent = human_readable(datum[2]), human_readable(datum[1]), human_readable(datum[2]-datum[1]), \
|
total, free, used, percent = human_readable(datum[2]), human_readable(datum[1]), human_readable(datum[2]-datum[1]), \
|
||||||
str(0 if datum[2]==0 else int(100*(datum[2]-datum[1])/(datum[2]*1.)))+"%"
|
str(0 if datum[2]==0 else int(100*(datum[2]-datum[1])/(datum[2]*1.)))+"%"
|
||||||
print "%-10s\t%s\t%s\t%s\t%s"%(datum[0], total, used, free, percent)
|
print "%-10s\t%s\t%s\t%s\t%s"%(datum[0], total, used, free, percent)
|
||||||
elif command == "ls":
|
elif command == "mkdir":
|
||||||
parser = OptionParser(usage="usage: %prog ls [options] path\n\npath must begin with /,a:/ or b:/")
|
parser = OptionParser(usage="usage: %prog mkdir [options] path\n\npath must begin with /,a:/ or b:/")
|
||||||
parser.add_option("--color", help="show ls output in color", dest="color", action="store_true", default=False)
|
if len(args) != 1:
|
||||||
parser.add_option("-l", help="In addition to the name of each file, print the file type, permissions, and timestamp (the modification time unless other times are selected)", dest="ll", action="store_true", default=False)
|
parser.print_help()
|
||||||
parser.add_option("-R", help="Recursively list subdirectories encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False)
|
sys.exit(1)
|
||||||
parser.remove_option("-h")
|
dev.mkdir(args[0])
|
||||||
parser.add_option("-h", "--human-readable", help="show sizes in human readable format", dest="hrs", action="store_true", default=False)
|
elif command == "ls":
|
||||||
options, args = parser.parse_args(args)
|
parser = OptionParser(usage="usage: %prog ls [options] path\n\npath must begin with /,a:/ or b:/")
|
||||||
if len(args) < 1:
|
parser.add_option("--color", help="show ls output in color", dest="color", action="store_true", default=False)
|
||||||
parser.print_help()
|
parser.add_option("-l", help="In addition to the name of each file, print the file type, permissions, and timestamp (the modification time unless other times are selected). Times are local.", dest="ll", action="store_true", default=False)
|
||||||
sys.exit(1)
|
parser.add_option("-R", help="Recursively list subdirectories encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False)
|
||||||
dev.open()
|
parser.remove_option("-h")
|
||||||
try:
|
parser.add_option("-h", "--human-readable", help="show sizes in human readable format", dest="hrs", action="store_true", default=False)
|
||||||
|
options, args = parser.parse_args(args)
|
||||||
|
if len(args) != 1:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
dev.open()
|
||||||
print ls(dev, args[0], term, color=options.color, recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
|
print ls(dev, args[0], term, color=options.color, recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
|
||||||
except ArgumentError, e:
|
elif command == "info":
|
||||||
print >> sys.stderr, e
|
|
||||||
sys.exit(1)
|
|
||||||
finally:
|
|
||||||
dev.close()
|
|
||||||
elif command == "info":
|
|
||||||
dev.open()
|
|
||||||
try:
|
|
||||||
info(dev)
|
info(dev)
|
||||||
finally: dev.close()
|
elif command == "cp":
|
||||||
elif command == "cp":
|
usage="usage: %prog cp [options] source destination\n\n"+\
|
||||||
parser = OptionParser(usage="usage: %prog cp [options] source destination\n\nsource is a path on the device and must begin with /,a:/ or b:/"+
|
"One of source or destination must be a path on the device. Device paths have the form:\n"+\
|
||||||
"\n\ndestination is a path on your computer and can point to either a file or a directory")
|
"device:mountpoint/my/path\n"+\
|
||||||
options, args = parser.parse_args(args)
|
"where mountpoint is one of /, a: or b:\n"+\
|
||||||
if len(args) < 2:
|
"source must point to a file for which you have read permissions\n"+\
|
||||||
parser.print_help()
|
"destination must point to a file or directory for which you have write permissions"
|
||||||
sys.exit(1)
|
parser = OptionParser(usage=usage)
|
||||||
if args[0].endswith("/"): path = args[0][:-1]
|
options, args = parser.parse_args(args)
|
||||||
else: path = args[0]
|
if len(args) != 2:
|
||||||
outfile = args[1]
|
parser.print_help()
|
||||||
if os.path.isdir(outfile):
|
sys.exit(1)
|
||||||
outfile = os.path.join(outfile, path[path.rfind("/")+1:])
|
if args[0].startswith("device:"):
|
||||||
outfile = open(outfile, "w")
|
outfile = args[1]
|
||||||
dev.open()
|
path = args[0][7:]
|
||||||
try:
|
if path.endswith("/"): path = path[:-1]
|
||||||
|
if os.path.isdir(outfile):
|
||||||
|
outfile = os.path.join(outfile, path[path.rfind("/")+1:])
|
||||||
|
try:
|
||||||
|
outfile = open(outfile, "w")
|
||||||
|
except IOError, e:
|
||||||
|
print >> sys.stderr, e
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
dev.get_file(path, outfile)
|
||||||
|
outfile.close()
|
||||||
|
elif args[1].startswith("device:"):
|
||||||
|
try:
|
||||||
|
infile = open(args[0], "r")
|
||||||
|
except IOError, e:
|
||||||
|
print >> sys.stderr, e
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
dev.put_file(infile, args[1][7:])
|
||||||
|
infile.close()
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
elif command == "cat":
|
||||||
|
outfile = sys.stdout
|
||||||
|
parser = OptionParser(usage="usage: %prog cat path\n\npath should point to a file on the device and must begin with /,a:/ or b:/")
|
||||||
|
options, args = parser.parse_args(args)
|
||||||
|
if len(args) != 1:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
if args[0].endswith("/"): path = args[0][:-1]
|
||||||
|
else: path = args[0]
|
||||||
|
outfile = sys.stdout
|
||||||
dev.get_file(path, outfile)
|
dev.get_file(path, outfile)
|
||||||
except ArgumentError, e:
|
elif command == "rm":
|
||||||
print >>sys.stderr, e
|
parser = OptionParser(usage="usage: %prog rm path\n\npath should point to a file or empty directory on the device "+\
|
||||||
finally:
|
"and must begin with /,a:/ or b:/\n\n"+\
|
||||||
dev.close()
|
"rm will DELETE the file. Be very CAREFUL")
|
||||||
outfile.close()
|
options, args = parser.parse_args(args)
|
||||||
elif command == "cat":
|
if len(args) != 1:
|
||||||
outfile = sys.stdout
|
parser.print_help()
|
||||||
parser = OptionParser(usage="usage: %prog cat path\n\npath should point to a file on the device and must begin with /,a:/ or b:/")
|
sys.exit(1)
|
||||||
options, args = parser.parse_args(args)
|
dev.rm(args[0])
|
||||||
if len(args) < 1:
|
elif command == "touch":
|
||||||
|
parser = OptionParser(usage="usage: %prog touch path\n\npath should point to a file on the device and must begin with /,a:/ or b:/\n\n"+
|
||||||
|
"Unfortunately, I cant figure out how to update file times on the device, so if path already exists, touch does nothing" )
|
||||||
|
options, args = parser.parse_args(args)
|
||||||
|
if len(args) != 1:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
dev.touch(args[0])
|
||||||
|
else:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
if dev.handle: dev.close()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if args[0].endswith("/"): path = args[0][:-1]
|
except ArgumentError, e:
|
||||||
else: path = args[0]
|
print >>sys.stderr, e
|
||||||
outfile = sys.stdout
|
sys.exit(1)
|
||||||
dev.open()
|
finally:
|
||||||
try:
|
if dev.handle: dev.close()
|
||||||
dev.get_file(path, outfile)
|
|
||||||
except ArgumentError, e:
|
|
||||||
print >>sys.stderr, e
|
|
||||||
finally:
|
|
||||||
dev.close()
|
|
||||||
else:
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user