Raising pylint scores for libprs500/*.py

This commit is contained in:
Kovid Goyal 2006-12-19 01:50:00 +00:00
parent 3d7d6a862a
commit 6010e9b2ca
6 changed files with 1691 additions and 1639 deletions

View File

@ -12,16 +12,25 @@
## You should have received a copy of the GNU General Public License along ## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
""" This module contains the logic for dealing with XML book lists found in the reader cache """ """
This module contains the logic for dealing with XML book lists found
in the reader cache.
"""
from xml.dom.ext import PrettyPrint from xml.dom.ext import PrettyPrint
import xml.dom.minidom as dom import xml.dom.minidom as dom
from base64 import b64decode as decode from base64 import b64decode as decode
from base64 import b64encode as encode from base64 import b64encode as encode
import time import time
MIME_MAP = { "lrf":"application/x-sony-bbeb", "rtf":"application/rtf", "pdf":"application/pdf", "txt":"text/plain" } MIME_MAP = { \
"lrf":"application/x-sony-bbeb", \
"rtf":"application/rtf", \
"pdf":"application/pdf", \
"txt":"text/plain" \
}
class book_metadata_field(object): class book_metadata_field(object):
""" Represents metadata stored as an attribute """
def __init__(self, attr, formatter=None, setter=None): def __init__(self, attr, formatter=None, setter=None):
self.attr = attr self.attr = attr
self.formatter = formatter self.formatter = formatter
@ -29,25 +38,36 @@ class book_metadata_field(object):
def __get__(self, obj, typ=None): def __get__(self, obj, typ=None):
""" Return a string. String may be empty if self.attr is absent """ """ Return a string. String may be empty if self.attr is absent """
return self.formatter(obj.elem.getAttribute(self.attr)) if self.formatter else obj.elem.getAttribute(self.attr).strip() return self.formatter(obj.elem.getAttribute(self.attr)) if \
self.formatter else obj.elem.getAttribute(self.attr).strip()
def __set__(self, obj, val): def __set__(self, obj, val):
""" Set the attribute """
val = self.setter(val) if self.setter else val val = self.setter(val) if self.setter else val
obj.elem.setAttribute(self.attr, str(val)) obj.elem.setAttribute(self.attr, str(val))
class Book(object): class Book(object):
""" Provides a view onto the XML element that represents a book """
title = book_metadata_field("title") title = book_metadata_field("title")
author = book_metadata_field("author", formatter=lambda x: x if x.strip() else "Unknown") author = book_metadata_field("author", \
formatter=lambda x: x if x.strip() else "Unknown")
mime = book_metadata_field("mime") mime = book_metadata_field("mime")
rpath = book_metadata_field("path") rpath = book_metadata_field("path")
id = book_metadata_field("id", formatter=int) id = book_metadata_field("id", formatter=int)
sourceid = book_metadata_field("sourceid", formatter=int) sourceid = book_metadata_field("sourceid", formatter=int)
size = book_metadata_field("size", formatter=int) size = book_metadata_field("size", formatter=int)
# When setting this attribute you must use an epoch # When setting this attribute you must use an epoch
datetime = book_metadata_field("date", formatter=lambda x: time.strptime(x, "%a, %d %b %Y %H:%M:%S %Z"), setter=lambda x: time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(x))) datetime = book_metadata_field("date", \
formatter=lambda x: time.strptime(x, "%a, %d %b %Y %H:%M:%S %Z"),
setter=lambda x: time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(x)))
@apply @apply
def thumbnail(): def thumbnail():
doc = \
"""
The thumbnail. Should be a height 68 image.
Setting is not supported.
"""
def fget(self): def fget(self):
th = self.elem.getElementsByTagName(self.prefix + "thumbnail") th = self.elem.getElementsByTagName(self.prefix + "thumbnail")
if len(th): if len(th):
@ -57,14 +77,17 @@ class Book(object):
break break
rc = "" rc = ""
for node in th.childNodes: for node in th.childNodes:
if node.nodeType == node.TEXT_NODE: rc += node.data if node.nodeType == node.TEXT_NODE:
rc += node.data
return decode(rc) return decode(rc)
return property(**locals()) return property(fget=fget, doc=doc)
@apply @apply
def path(): def path():
def fget(self): return self.root + self.rpath doc = """ Absolute path to book on device. Setting not supported. """
return property(**locals()) def fget(self):
return self.root + self.rpath
return property(fget=fget, doc=doc)
def __init__(self, node, prefix="xs1:", root="/Data/media/"): def __init__(self, node, prefix="xs1:", root="/Data/media/"):
self.elem = node self.elem = node
@ -79,57 +102,77 @@ class Book(object):
def fix_ids(media, cache): def fix_ids(media, cache):
id = 0 """
Update ids in media, cache to be consistent with their
current structure
"""
cid = 0
for child in media.root.childNodes: for child in media.root.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"): if child.nodeType == child.ELEMENT_NODE and \
child.setAttribute("id", str(id)) child.hasAttribute("id"):
id += 1 child.setAttribute("id", str(cid))
mmaxid = id - 1 cid += 1
id = mmaxid + 2 mmaxid = cid - 1
cid = mmaxid + 2
if len(cache): if len(cache):
for child in cache.root.childNodes: for child in cache.root.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("sourceid"): if child.nodeType == child.ELEMENT_NODE and \
child.hasAttribute("sourceid"):
child.setAttribute("sourceid", str(mmaxid+1)) child.setAttribute("sourceid", str(mmaxid+1))
child.setAttribute("id", str(id)) child.setAttribute("id", str(cid))
id += 1 cid += 1
media.document.documentElement.setAttribute("nextID", str(id)) media.document.documentElement.setAttribute("nextID", str(cid))
class BookList(list): class BookList(list):
"""
A list of L{Book}s. Created from an XML file. Can write list
to an XML file.
"""
__getslice__ = None __getslice__ = None
__setslice__ = None __setslice__ = None
def __init__(self, prefix="xs1:", root="/Data/media/", file=None): def __init__(self, prefix="xs1:", root="/Data/media/", sfile=None):
list.__init__(self) list.__init__(self)
if file: if sfile:
self.prefix = prefix self.prefix = prefix
self.proot = root self.proot = root
file.seek(0) sfile.seek(0)
self.document = dom.parse(file) self.document = dom.parse(sfile)
self.root = self.document.documentElement #: The root element containing all records # The root element containing all records
if prefix == "xs1:": self.root = self.root.getElementsByTagName("records")[0] self.root = self.document.documentElement
for book in self.document.getElementsByTagName(self.prefix + "text"): self.append(Book(book, root=root, prefix=prefix)) if prefix == "xs1:":
self.root = self.root.getElementsByTagName("records")[0]
for book in self.document.getElementsByTagName(self.prefix + "text"):
self.append(Book(book, root=root, prefix=prefix))
def max_id(self): def max_id(self):
id = -1 """ Highest id in underlying XML file """
cid = -1
for child in self.root.childNodes: for child in self.root.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"): if child.nodeType == child.ELEMENT_NODE and \
child.hasAttribute("id"):
c = int(child.getAttribute("id")) c = int(child.getAttribute("id"))
if c > id: id = c if c > cid:
return id cid = c
return cid
def has_id(self, id): def has_id(self, cid):
""" Check if a book with id C{ == id} exists already. This *does not* check if id exists in the underlying XML file """ """
Check if a book with id C{ == cid} exists already.
This *does not* check if id exists in the underlying XML file
"""
ans = False ans = False
for book in self: for book in self:
if book.id == id: if book.id == cid:
ans = True ans = True
break break
return ans return ans
def delete_book(self, id): def delete_book(self, cid):
""" Remove DOM node corresponding to book with C{id == cid}."""
node = None node = None
for book in self: for book in self:
if book.id == id: if book.id == cid:
node = book node = book
self.remove(book) self.remove(book)
break break
@ -137,11 +180,15 @@ class BookList(list):
node.elem.unlink() node.elem.unlink()
def add_book(self, info, name, size, ctime): def add_book(self, info, name, size, ctime):
""" Add a node into DOM tree representing a book """
node = self.document.createElement(self.prefix + "text") node = self.document.createElement(self.prefix + "text")
mime = MIME_MAP[name[name.rfind(".")+1:]] mime = MIME_MAP[name[name.rfind(".")+1:]]
id = self.max_id()+1 cid = self.max_id()+1
sourceid = str(self[0].sourceid) if len(self) else "1" sourceid = str(self[0].sourceid) if len(self) else "1"
attrs = { "title":info["title"], "author":info["authors"], "page":"0", "part":"0", "scale":"0", "sourceid":sourceid, "id":str(id), "date":"", "mime":mime, "path":name, "size":str(size)} attrs = { "title":info["title"], "author":info["authors"], \
"page":"0", "part":"0", "scale":"0", \
"sourceid":sourceid, "id":str(cid), "date":"", \
"mime":mime, "path":name, "size":str(size)}
for attr in attrs.keys(): for attr in attrs.keys():
node.setAttributeNode(self.document.createAttribute(attr)) node.setAttributeNode(self.document.createAttribute(attr))
node.setAttribute(attr, attrs[attr]) node.setAttribute(attr, attrs[attr])
@ -160,4 +207,5 @@ class BookList(list):
self.append(book) self.append(book)
def write(self, stream): def write(self, stream):
""" Write XML representation of DOM tree to C{stream} """
PrettyPrint(self.document, stream) PrettyPrint(self.document, stream)

View File

@ -36,53 +36,80 @@
### bInterval 0 ### bInterval 0
### ###
### ###
### Endpoint 0x81 is device->host and endpoint 0x02 is host->device. You can establish Stream pipes to/from these endpoints for Bulk transfers. ### Endpoint 0x81 is device->host and endpoint 0x02 is host->device.
### Has two configurations 1 is the USB charging config 2 is the self-powered config. ### You can establish Stream pipes to/from these endpoints for Bulk transfers.
### I think config management is automatic. Endpoints are the same ### Has two configurations 1 is the USB charging config 2 is the self-powered
### config. I think config management is automatic. Endpoints are the same
""" """
Contains the logic for communication with the device (a SONY PRS-500). 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, os, time import usb
import sys
import os
import time
from tempfile import TemporaryFile from tempfile import TemporaryFile
from array import array from array import array
from libprs500.prstypes import * from libprs500.prstypes import *
from libprs500.errors import * from libprs500.errors import *
from libprs500.books import * from libprs500.books import BookList, fix_ids
from libprs500 import __author__ as AUTHOR from libprs500 import __author__ as AUTHOR
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
KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L] #: Protocol versions libprs500 has been tested with
def _log_packet(packet, header, stream=sys.stderr): # Protocol versions libprs500 has been tested with
""" Log C{packet} to stream C{stream}. Header should be a small word describing the type of packet. """ KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L]
global _packet_number
_packet_number += 1
print >>stream, str(_packet_number), header, "Type:", packet.__class__.__name__
print >>stream, packet
print >>stream, "--"
class Device(object):
""" Contains specific device independent methods """
_packet_number = 0 #: Keep track of the packet number for packet tracing
def log_packet(self, packet, header, stream=sys.stderr):
"""
Log C{packet} to stream C{stream}.
Header should be a small word describing the type of packet.
"""
self._packet_number += 1
print >> stream, str(self._packet_number), header, "Type:", \
packet.__class__.__name__
print >> stream, packet
print >> stream, "--"
@classmethod
def validate_response(cls, res, _type=0x00, number=0x00):
"""
Raise a ProtocolError if the type and number of C{res}
is not the same as C{type} and C{number}.
"""
if _type != res.type or number != res.rnumber:
raise ProtocolError("Inavlid response.\ntype: expected=" + \
hex(_type)+" actual=" + hex(res.type) + \
"\nrnumber: expected=" + hex(number) + \
" actual="+hex(res.rnumber))
class File(object): class File(object):
""" Wrapper that allows easy access to all information about files/directories """ """
def __init__(self, file): Wrapper that allows easy access to all information about files/directories
self.is_dir = file[1].is_dir #: True if self is a directory """
self.is_readonly = file[1].is_readonly #: True if self is readonly def __init__(self, _file):
self.size = file[1].file_size #: Size in bytes of self self.is_dir = _file[1].is_dir #: True if self is a directory
self.ctime = file[1].ctime #: Creation time of self as a epoch self.is_readonly = _file[1].is_readonly #: True if self is readonly
self.wtime = file[1].wtime #: Creation time of self as an epoch self.size = _file[1].file_size #: Size in bytes of self
path = file[0] self.ctime = _file[1].ctime #: Creation time of self as a epoch
if path.endswith("/"): path = path[:-1] self.wtime = _file[1].wtime #: Creation time of self as an epoch
path = _file[0]
if path.endswith("/"):
path = path[:-1]
self.path = path #: Path to self self.path = path #: Path to self
self.name = path[path.rfind("/")+1:].rstrip() #: Name of self self.name = path[path.rfind("/")+1:].rstrip() #: Name of self
def __repr__(self): def __repr__(self):
""" Return path to self """ """ Return path to self """
return "File:"+self.path return "File:" + self.path
def __str__(self): def __str__(self):
return self.name return self.name
@ -101,7 +128,7 @@ class DeviceDescriptor:
self.product_id = product_id self.product_id = product_id
self.interface_id = interface_id self.interface_id = interface_id
def getDevice(self) : def get_device(self) :
""" """
Return the device corresponding to the device descriptor if it is Return the device corresponding to the device descriptor if it is
available on a USB bus. Otherwise, return None. Note that the available on a USB bus. Otherwise, return None. Note that the
@ -116,7 +143,7 @@ class DeviceDescriptor:
return None return None
class PRS500Device(object): class PRS500Device(Device):
""" """
Contains the logic for performing various tasks on the reader. Contains the logic for performing various tasks on the reader.
@ -132,12 +159,17 @@ class PRS500Device(object):
PRS500_INTERFACE_ID = 0 #: The interface we use to talk to the device PRS500_INTERFACE_ID = 0 #: The interface we use to talk to the device
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
MEDIA_XML = "/Data/database/cache/media.xml" #: Location of media.xml file on device # Location of media.xml file on device
CACHE_XML = "/Sony Reader/database/cache.xml" #: Location of cache.xml on storage card in device MEDIA_XML = "/Data/database/cache/media.xml"
FORMATS = ["lrf", "rtf", "pdf", "txt"] #: Ordered list of supported formats # Location of cache.xml on storage card in device
THUMBNAIL_HEIGHT = 68 #: Height for thumbnails of books/images on the device CACHE_XML = "/Sony Reader/database/cache.xml"
# Ordered list of supported formats
FORMATS = ["lrf", "rtf", "pdf", "txt"]
# Height for thumbnails of books/images on the device
THUMBNAIL_HEIGHT = 68
device_descriptor = DeviceDescriptor(SONY_VENDOR_ID, PRS500_PRODUCT_ID, PRS500_INTERFACE_ID) device_descriptor = DeviceDescriptor(SONY_VENDOR_ID, \
PRS500_PRODUCT_ID, PRS500_INTERFACE_ID)
@classmethod @classmethod
def signature(cls): def signature(cls):
@ -146,14 +178,18 @@ class PRS500Device(object):
def safe(func): def safe(func):
""" """
Decorator that wraps a call to C{func} to ensure that exceptions are handled correctly. Decorator that wraps a call to C{func} to ensure that
It also calls L{open} to claim the interface and initialize the Reader if needed. exceptions are handled correctly. It also calls L{open} to claim
the interface and initialize the Reader if needed.
As a convenience, C{safe} automatically sends the a L{EndSession} after calling func, unless func has As a convenience, C{safe} automatically sends the a
L{EndSession} after calling func, unless func has
a keyword argument named C{end_session} set to C{False}. a keyword argument named C{end_session} set to C{False}.
An L{ArgumentError} will cause the L{EndSession} command to be sent to the device, unless end_session is set to C{False}. An L{ArgumentError} will cause the L{EndSession} command to
An L{usb.USBError} will cause the library to release control of the USB interface via a call to L{close}. 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}.
@todo: Fix handling of timeout errors @todo: Fix handling of timeout errors
""" """
@ -161,25 +197,27 @@ class PRS500Device(object):
dev = args[0] dev = args[0]
res = None res = None
try: try:
if not dev.handle: dev.open() if not dev.handle:
dev.open()
res = func(*args, **kwargs) res = func(*args, **kwargs)
except ArgumentError: except ArgumentError:
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(EndSession()) dev.send_validated_command(EndSession())
raise raise
except usb.USBError, e: except usb.USBError, err:
if "No such device" in str(e): if "No such device" in str(err):
raise DeviceError() raise DeviceError()
elif "Connection timed out" in str(e): elif "Connection timed out" in str(err):
dev.close() dev.close()
raise TimeoutError(func.__name__) raise TimeoutError(func.__name__)
elif "Protocol error" in str(e): elif "Protocol error" in str(err):
dev.close() dev.close()
raise ProtocolError("There was an unknown error in the protocol. Contact " + AUTHOR) raise ProtocolError("There was an unknown error in the"+\
" 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(EndSession()) dev.send_validated_command(EndSession())
return res return res
return run_session return run_session
@ -187,62 +225,69 @@ class PRS500Device(object):
def __init__(self, log_packets=False, report_progress=None) : def __init__(self, log_packets=False, report_progress=None) :
""" """
@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 (number between 0 and 100) for various tasks @param report_progress: Function that is called with a % progress
If it is called with -1 that means that the task does not have any progress information (number between 0 and 100) for various tasks
If it is called with -1 that means that the
task does not have any progress information
""" """
self.device = self.device_descriptor.getDevice() #: The actual device (PyUSB object) Device.__init__(self)
self.handle = None #: Handle that is used to communicate with device. Setup in L{open} # The actual device (PyUSB object)
self._log_packets = log_packets self.device = self.device_descriptor.get_device()
# Handle that is used to communicate with device. Setup in L{open}
self.handle = None
self.log_packets = log_packets
self.report_progress = report_progress self.report_progress = report_progress
def reconnect(self): def reconnect(self):
self.device = self.device_descriptor.getDevice() """ Only recreates the device node and deleted the connection handle """
self.device = self.device_descriptor.get_device()
self.handle = None self.handle = None
@classmethod @classmethod
def is_connected(cls): def is_connected(cls):
""" """
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 software connection. You may need to call L{reconnect} if you keep It does not return any information about the validity of the
software connection. You may need to call L{reconnect} if you keep
getting L{DeviceError}. getting L{DeviceError}.
""" """
return cls.device_descriptor.getDevice() != None return cls.device_descriptor.get_device() != None
@classmethod
def _validate_response(cls, res, type=0x00, number=0x00):
""" Raise a ProtocolError if the type and number of C{res} is not the same as C{type} and C{number}. """
if type != res.type or number != res.rnumber:
raise ProtocolError("Inavlid response.\ntype: expected="+hex(type)+" actual="+hex(res.type)+
"\nrnumber: expected="+hex(number)+" actual="+hex(res.rnumber))
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.
Also initialize the device. See the source code for the sequenceof initialization commands. 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: 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.get_device()
if not self.device: if not self.device:
raise DeviceError() raise DeviceError()
try: try:
self.handle = self.device.open() self.handle = self.device.open()
self.handle.claimInterface(self.device_descriptor.interface_id) self.handle.claimInterface(self.device_descriptor.interface_id)
except usb.USBError, e: except usb.USBError, err:
print >>sys.stderr, e print >> sys.stderr, err
raise DeviceBusy() raise DeviceBusy()
res = self._send_validated_command(GetUSBProtocolVersion(), timeout=20000) # Large timeout as device may still be initializing # Large timeout as device may still be initializing
if res.code != 0: raise ProtocolError("Unable to get USB Protocol version.") res = self.send_validated_command(GetUSBProtocolVersion(), timeout=20000)
if res.code != 0:
raise ProtocolError("Unable to get USB Protocol version.")
version = self._bulk_read(24, data_type=USBProtocolVersion)[0].version 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:
print >>sys.stderr, "WARNING: Usb protocol version " + hex(version) + " is unknown" print >> sys.stderr, "WARNING: Usb protocol version " + \
res = self._send_validated_command(SetBulkSize(size=0x028000)) hex(version) + " is unknown"
if res.code != 0: raise ProtocolError("Unable to set bulk size.") res = self.send_validated_command(SetBulkSize(size=0x028000))
self._send_validated_command(UnlockDevice(key=0x312d)) if res.code != 0:
raise ProtocolError("Unable to set bulk size.")
self.send_validated_command(UnlockDevice(key=0x312d))
if res.code != 0: if res.code != 0:
raise ProtocolError("Unlocking of device not implemented. Remove locking and retry.") raise ProtocolError("Unlocking of device not implemented. Remove locking and retry.")
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")
@ -251,7 +296,8 @@ class PRS500Device(object):
try: try:
self.handle.reset() self.handle.reset()
self.handle.releaseInterface() self.handle.releaseInterface()
except: pass except Exception, err:
print >> sys.stderr, err
self.handle, self.device = None, None self.handle, self.device = None, None
def _send_command(self, command, response_type=Response, timeout=1000): def _send_command(self, command, response_type=Response, timeout=1000):
@ -259,36 +305,49 @@ class PRS500Device(object):
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 from the device is returned as an object of type response_type. @param response_type: an object of type 'type'. The return packet
@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. from the device is returned as an object of type response_type.
@param timeout: The time to wait for a response from the
device, in milliseconds. If there is no response, a L{usb.USBError} is raised.
""" """
if self._log_packets: _log_packet(command, "Command") if self.log_packets:
self.log_packet(command, "Command")
bytes_sent = self.handle.controlMsg(0x40, 0x80, command) bytes_sent = self.handle.controlMsg(0x40, 0x80, command)
if bytes_sent != len(command): if bytes_sent != len(command):
raise ControlError(desc="Could not send control request to device\n" + str(query.query)) raise ControlError(desc="Could not send control request to device\n"\
response = response_type(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=timeout)) + str(command))
if self._log_packets: _log_packet(response, "Response") response = response_type(self.handle.controlMsg(0xc0, 0x81, \
Response.SIZE, timeout=timeout))
if self.log_packets:
self.log_packet(response, "Response")
return response return response
def _send_validated_command(self, command, cnumber=None, response_type=Response, timeout=1000): def send_validated_command(self, command, cnumber=None, \
response_type=Response, timeout=1000):
""" """
Wrapper around L{_send_command} that checks if the C{Response.rnumber == cnumber or command.number if cnumber==None}. Also check that Wrapper around L{_send_command} that checks if the
C{Response.rnumber == cnumber or
command.number if cnumber==None}. Also check that
C{Response.type == Command.type}. C{Response.type == Command.type}.
""" """
if cnumber == None: cnumber = command.number if cnumber == None:
res = self._send_command(command, response_type=response_type, timeout=timeout) cnumber = command.number
PRS500Device._validate_response(res, type=command.type, number=cnumber) res = self._send_command(command, response_type=response_type, \
timeout=timeout)
self.validate_response(res, _type=command.type, number=cnumber)
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. C{data} is broken up into packets to be sent to device. @param packet_size: Size of packets to be sent to device.
C{data} is broken up into packets to be sent to device.
""" """
def bulk_write_packet(packet): def bulk_write_packet(packet):
self.handle.bulkWrite(PRS500Device.PRS500_BULK_OUT_EP, packet) self.handle.bulkWrite(PRS500Device.PRS500_BULK_OUT_EP, packet)
if self._log_packets: _log_packet(Answer(packet), "Answer h->d") if self.log_packets:
self.log_packet(Answer(packet), "Answer h->d")
bytes_left = len(data) bytes_left = len(data)
if bytes_left + 16 <= packet_size: if bytes_left + 16 <= packet_size:
@ -305,38 +364,52 @@ class PRS500Device(object):
pos = first_packet.length pos = first_packet.length
bytes_left -= first_packet.length bytes_left -= first_packet.length
while bytes_left > 0: while bytes_left > 0:
endpos = pos + packet_size if pos + packet_size <= len(data) else len(data) endpos = pos + packet_size if pos + packet_size <= len(data) \
else len(data)
bulk_write_packet(data[pos:endpos]) bulk_write_packet(data[pos:endpos])
bytes_left -= endpos - pos bytes_left -= endpos - pos
pos = endpos pos = endpos
res = Response(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=5000)) res = Response(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, \
if self._log_packets: _log_packet(res, "Response") timeout=5000))
if self.log_packets:
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"+str(res)) raise ProtocolError("Sending via Bulk Transfer failed with response:\n"\
+str(res))
if res.data_size != len(data): if res.data_size != len(data):
raise ProtocolError("Unable to transfer all data to device. Response packet:\n"+str(res)) raise ProtocolError("Unable to transfer all data to device. Response packet:\n"\
+str(res))
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
@param data_type: an object of type type. The data packet is returned as an object of type C{data_type}. packets of size S{<=} C{packet_size}
@return: A list of packets read from the device. Each packet is of type data_type @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
@todo: Figure out how to make bulk reads work in OSX @todo: Figure out how to make bulk reads work in OSX
""" """
def bulk_read_packet(data_type=Answer, size=0x1000): def bulk_read_packet(data_type=Answer, size=0x1000):
data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size)) data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, \
if self._log_packets: _log_packet(data, "Answer d->h") size))
if self.log_packets:
self.log_packet(data, "Answer d->h")
return data 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 = 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
@safe @safe
@ -345,63 +418,85 @@ class PRS500Device(object):
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
data = self._bulk_read(size, command_number=DeviceInfoQuery.NUMBER, data_type=DeviceInfo)[0] data = self._bulk_read(size, command_number=\
return (data.device_name, data.device_version, data.software_version, data.mime_type) DeviceInfoQuery.NUMBER, data_type=DeviceInfo)[0]
return (data.device_name, data.device_version, \
data.software_version, data.mime_type)
@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}. Return L{FileProperties}. """ """
res = self._send_validated_command(PathQuery(path), response_type=ListResponse) Send command asking device for properties of C{path}.
data = self._bulk_read(0x28, data_type=FileProperties, command_number=PathQuery.NUMBER)[0] Return L{FileProperties}.
if path.endswith("/"): path = path[:-1] """
if res.path_not_found : raise PathError(path + " does not exist on device") res = self.send_validated_command(PathQuery(path), \
if res.is_invalid : raise PathError(path + " is not a valid path") response_type=ListResponse)
if res.is_unmounted : raise PathError(path + " is not mounted") data = self._bulk_read(0x28, data_type=FileProperties, \
command_number=PathQuery.NUMBER)[0]
if path.endswith("/"):
path = path[:-1]
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_unmounted:
raise PathError(path + " is not mounted")
if res.code not in (0, PathResponseCodes.IS_FILE): if res.code not in (0, PathResponseCodes.IS_FILE):
raise PathError(path + " has an unknown error. Code: " + hex(res.code)) raise PathError(path + " has an unknown error. Code: " + \
hex(res.code))
return data return data
@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. For the logic see L{_get_file}. 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 make of packets of size S{<=} 4K. See L{FileOpen}, The data is fetched in chunks of size S{<=} 32K. Each chunk is
made of packets of size S{<=} 4K. See L{FileOpen},
L{FileRead} and L{FileClose} for details on the command packets used. L{FileRead} and L{FileClose} for details on the command packets used.
@param outfile: file object like C{sys.stdout} or the result of an C{open} call @param outfile: file object like C{sys.stdout} or the result of an C{open} call
""" """
if path.endswith("/"): path = path[:-1] # We only copy files if path.endswith("/"):
file = self.path_properties(path, end_session=False) path = path[:-1] # We only copy files
if file.is_dir: raise PathError("Cannot read as " + path + " is a directory") _file = self.path_properties(path, end_session=False)
bytes = file.file_size if _file.is_dir:
res = self._send_validated_command(FileOpen(path)) raise PathError("Cannot read as " + path + " is a directory")
bytes = _file.file_size
res = self.send_validated_command(FileOpen(path))
if res.code != 0: if res.code != 0:
raise PathError("Unable to open " + path + " for reading. Response code: " + hex(res.code)) raise PathError("Unable to open " + path + \
id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id " for reading. Response code: " + hex(res.code))
_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:
res = self._send_validated_command(FileIO(id, pos, chunk_size)) chunk_size = bytes_left
res = self.send_validated_command(FileIO(_id, pos, chunk_size))
if res.code != 0: if res.code != 0:
self._send_validated_command(FileClose(id)) self.send_validated_command(FileClose(id))
raise ProtocolError("Error while reading from " + path + ". Response code: " + hex(res.code)) raise ProtocolError("Error while reading from " + path + \
packets = self._bulk_read(chunk_size+16, command_number=FileIO.RNUMBER, packet_size=4096) ". 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 # The first 16 bytes are meta information on the packet stream
array('B', packets[0][16:]).tofile(outfile)
for i in range(1, len(packets)): for i in range(1, len(packets)):
array('B', packets[i]).tofile(outfile) array('B', packets[i]).tofile(outfile)
except IOError, e: except IOError, err:
self._send_validated_command(FileClose(id)) self.send_validated_command(FileClose(_id))
raise ArgumentError("File get operation failed. Could not write to local location: " + str(e)) raise ArgumentError("File get operation failed. " + \
"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: self.report_progress(int(100*((1.*pos)/bytes))) if self.report_progress:
self._send_validated_command(FileClose(id)) self.report_progress(int(100*((1.*pos)/bytes)))
# Not going to check response code to see if close was successful as there's not much we can do if it wasnt self.send_validated_command(FileClose(_id))
# Not going to check response code to see if close was successful
# as there's not much we can do if it wasnt
@safe @safe
def list(self, path, recurse=False, end_session=True): def list(self, path, recurse=False, end_session=True):
@ -413,13 +508,18 @@ class PRS500Device(object):
@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 path is the path we are listing, the C{Files} are the files/directories in that path. If it is a recursive The second element is a list of L{Files<File>}.
list, then the first element will be (C{path}, children), the next will be (child, its children) and so on. If it The path is the path we are listing, the C{Files} are the
is not recursive the length of the outermost list will be 1. files/directories in that path. If it is a recursive list, then the first
element will be (C{path}, children), the next will be
(child, its children) and so on. If it is not recursive the length of the
outermost list will be 1.
""" """
def _list(path): # Do a non recursive listsing of path def _list(path):
if not path.endswith("/"): path += "/" # Initially assume path is a directory """ Do a non recursive listsing of path """
if not path.endswith("/"):
path += "/" # Initially assume path is a directory
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:
@ -428,23 +528,31 @@ class PRS500Device(object):
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
res = self._send_validated_command(DirOpen(path)) res = self.send_validated_command(DirOpen(path))
if res.code != 0: if res.code != 0:
raise PathError("Unable to open directory " + path + " for reading. Response code: " + hex(res.code)) raise PathError("Unable to open directory " + path + \
id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id " 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 # Create command asking for next element in list
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, command_number=DirRead.NUMBER)[0] data = self._bulk_read(size, data_type=ListAnswer, \
# path_not_found seems to happen if the usb server doesn't have the permissions to access the directory command_number=DirRead.NUMBER)[0]
if res.is_eol or res.path_not_found: break # 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: elif res.code != 0:
raise ProtocolError("Unknown error occured while reading contents of directory " + path + ". Response code: " + haex(res.code)) raise ProtocolError("Unknown error occured while "+\
"reading contents of directory " + path + \
". Response code: " + hex(res.code))
items.append(data.name) items.append(data.name)
self._send_validated_command(DirClose(id)) # Ignore res.code as we cant do anything if close fails self.send_validated_command(DirClose(_id))
# 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
data = self.path_properties(ipath, end_session=False) data = self.path_properties(ipath, end_session=False)
@ -455,9 +563,9 @@ class PRS500Device(object):
files = _list(path) 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, end_session=False) dirs[len(dirs):] = self.list(_file.path, recurse=True, end_session=False)
return dirs return dirs
@safe @safe
@ -472,9 +580,12 @@ class PRS500Device(object):
""" """
data = [] data = []
for path in ("/Data/", "a:/", "b:/"): for path in ("/Data/", "a:/", "b:/"):
res = self._send_validated_command(TotalSpaceQuery(path),timeout=5000) # 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), \
timeout=5000)
buffer_size = 16 + res.data[2] buffer_size = 16 + res.data[2]
pkt = self._bulk_read(buffer_size, data_type=TotalSpaceAnswer, command_number=TotalSpaceQuery.NUMBER)[0] pkt = self._bulk_read(buffer_size, data_type=TotalSpaceAnswer, \
command_number=TotalSpaceQuery.NUMBER)[0]
data.append( pkt.total ) data.append( pkt.total )
return data return data
@ -490,8 +601,12 @@ class PRS500Device(object):
""" """
data = [] data = []
for path in ("/", "a:/", "b:/"): for path in ("/", "a:/", "b:/"):
res = self._send_validated_command(FreeSpaceQuery(path),timeout=5000) # Timeout needs to be increased as it takes time to read card # Timeout needs to be increased as it takes time to read card
pkt = self._bulk_read(FreeSpaceAnswer.SIZE, data_type=FreeSpaceAnswer, command_number=FreeSpaceQuery.NUMBER)[0] self.send_validated_command(FreeSpaceQuery(path), \
timeout=5000)
pkt = self._bulk_read(FreeSpaceAnswer.SIZE, \
data_type=FreeSpaceAnswer, \
command_number=FreeSpaceQuery.NUMBER)[0]
data.append( pkt.free ) data.append( pkt.free )
return data return data
@ -500,8 +615,9 @@ class PRS500Device(object):
dest = None dest = None
try: try:
dest = self.path_properties(path, end_session=False) dest = self.path_properties(path, end_session=False)
except PathError, e: except PathError, err:
if "does not exist" in str(e) or "not mounted" in str(e): return (False, None) if "does not exist" in str(err) or "not mounted" in str(err):
return (False, None)
else: raise else: raise
return (True, dest) return (True, dest)
@ -509,49 +625,59 @@ class PRS500Device(object):
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. Opening the file in write mode and then closing it doesn't work. @todo: Update file modification time if it exists.
Opening the file in write mode and then closing it doesn't work.
""" """
if path.endswith("/") and len(path) > 1: path = path[:-1] if path.endswith("/") and len(path) > 1:
exists, file = self._exists(path) path = path[:-1]
if exists and file.is_dir: exists, _file = self._exists(path)
if exists and _file.is_dir:
raise PathError("Cannot touch directories") raise PathError("Cannot touch directories")
if not exists: if not exists:
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 + ". Response code: " + str(hex(res.code))) raise PathError("Could not create file " + path + \
". 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. If you are using a StringIO object set its name attribute manually. @param infile: An open file object. infile must have a name attribute.
@param path: The path on the device at which to put infile. It should point to an existing directory. If you are using a StringIO object set its name attribute manually.
@param path: The path on the device at which to put infile.
It should point to an existing directory.
@param replace_file: If True and path points to a file that already exists, it is replaced @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
start_pos = pos start_pos = pos
infile.seek(pos) infile.seek(pos)
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("/"): path += "/" if not path.endswith("/"):
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: raise PathError("Cannot write to " + path + " as it already exists") if not replace_file:
file = self.path_properties(path, end_session=False) raise PathError("Cannot write to " + \
if file.file_size > bytes: path + " as it already exists")
_file = self.path_properties(path, end_session=False)
if _file.file_size > bytes:
self.del_file(path, end_session=False) self.del_file(path, end_session=False)
self.touch(path, end_session=False) self.touch(path, end_session=False)
else: self.touch(path, end_session=False) else: self.touch(path, end_session=False)
chunk_size = 0x8000 chunk_size = 0x8000
data_left = True data_left = True
res = self._send_validated_command(FileOpen(path, mode=FileOpen.WRITE)) res = self.send_validated_command(FileOpen(path, mode=FileOpen.WRITE))
if res.code != 0: if res.code != 0:
raise ProtocolError("Unable to open " + path + " for writing. Response code: " + hex(res.code)) raise ProtocolError("Unable to open " + path + \
id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id " for writing. Response code: " + hex(res.code))
_id = self._bulk_read(20, data_type=IdAnswer, \
command_number=FileOpen.NUMBER)[0].id
while data_left: while data_left:
data = array('B') data = array('B')
@ -559,35 +685,47 @@ class PRS500Device(object):
data.fromfile(infile, chunk_size) data.fromfile(infile, chunk_size)
except EOFError: except EOFError:
data_left = False data_left = False
res = self._send_validated_command(FileIO(id, pos, len(data), mode=FileIO.WNUMBER)) res = self.send_validated_command(FileIO(_id, pos, len(data), \
mode=FileIO.WNUMBER))
if res.code != 0: if res.code != 0:
raise ProtocolError("Unable to write to " + path + ". Response code: " + hex(res.code)) raise ProtocolError("Unable to write to " + \
path + ". Response code: " + hex(res.code))
self._bulk_write(data) self._bulk_write(data)
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)) # Ignore res.code as cant do anything if close fails self.send_validated_command(FileClose(_id))
file = self.path_properties(path, end_session=False) # Ignore res.code as cant do anything if close fails
if file.file_size != pos: _file = self.path_properties(path, end_session=False)
raise ProtocolError("Copying to device failed. The file on the device is larger by " + str(file.file_size - pos) + " bytes") if _file.file_size != pos:
raise ProtocolError("Copying to device failed. The file " +\
"on the device is larger by " + \
str(_file.file_size - pos) + " bytes")
@safe @safe
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 """
data = self.path_properties(path, end_session=False) data = self.path_properties(path, end_session=False)
if data.is_dir: raise PathError("Cannot delete directories") if data.is_dir:
res = self._send_validated_command(FileDelete(path), response_type=ListResponse) raise PathError("Cannot delete directories")
res = self.send_validated_command(FileDelete(path), \
response_type=ListResponse)
if res.code != 0: if res.code != 0:
raise ProtocolError("Unable to delete " + path + " with response:\n" + str(res)) raise ProtocolError("Unable to delete " + path + \
" with response:\n" + str(res))
@safe @safe
def mkdir(self, path, end_session=True): def mkdir(self, path, end_session=True):
if not path.endswith("/"): path += "/" """ Make directory """
if not path.endswith("/"):
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]
if res == 0xffffffcc: if res == 0xffffffcc:
raise PathError(error_prefix + " as it already exists") raise PathError(error_prefix + " as it already exists")
elif res == PathResponseCodes.NOT_FOUND: elif res == PathResponseCodes.NOT_FOUND:
raise PathError(error_prefix + " as " + path[0:path[:-1].rfind("/")] + " does not exist ") raise PathError(error_prefix + " as " + \
path[0:path[:-1].rfind("/")] + " does not exist ")
elif res == PathResponseCodes.INVALID: elif res == PathResponseCodes.INVALID:
raise PathError(error_prefix + " as " + path + " is invalid") raise PathError(error_prefix + " as " + path + " is invalid")
elif res != 0: elif res != 0:
@ -600,74 +738,96 @@ class PRS500Device(object):
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("/"): path += "/" if not path.endswith("/"):
res = self._send_validated_command(DirDelete(path)) 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 + " as it is not empty") raise PathError("Cannot delete directory " + path + \
" as it is not empty")
if res.code != 0: if res.code != 0:
raise ProtocolError("Failed to delete directory " + path + ". Response code: " + hex(res.code)) raise ProtocolError("Failed to delete directory " + path + \
". Response code: " + hex(res.code))
@safe @safe
def card(self, end_session=True): def card(self, end_session=True):
""" Return path prefix to installed card or None """
card = None card = None
if self._exists("a:/")[0]: card = "a:" if self._exists("a:/")[0]:
if self._exists("b:/")[0]: card = "b:" card = "a:"
if self._exists("b:/")[0]:
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 ebookson the storage card, otherwise return list of ebooks in main memory of device @param oncard: If True return a list of ebooks on the storage card,
otherwise return list of ebooks in main memory of device
@return: L{BookList} @return: L{BookList}
""" """
root = "/Data/media/" root = "/Data/media/"
prefix = "xs1:" prefix = "xs1:"
file = TemporaryFile() tfile = TemporaryFile()
if oncard: if oncard:
prefix="" prefix = ""
try: try:
self.get_file("a:"+self.CACHE_XML, file, end_session=False) self.get_file("a:"+self.CACHE_XML, tfile, end_session=False)
root = "a:/" root = "a:/"
except PathError: except PathError:
try: try:
self.get_file("b:"+self.CACHE_XML, file, 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 file.tell() == 0: file = None if tfile.tell() == 0:
else: self.get_file(self.MEDIA_XML, file, end_session=False) tfile = None
return BookList(prefix=prefix, root=root, file=file) else:
self.get_file(self.MEDIA_XML, tfile, end_session=False)
return BookList(prefix=prefix, root=root, sfile=tfile)
@safe @safe
def add_book(self, infile, name, info, booklists, oncard=False, sync_booklists=False, end_session=True): def add_book(self, infile, name, info, booklists, oncard=False, \
sync_booklists=False, end_session=True):
""" """
Add a book to the device. If oncard is True then the book is copied to the card rather than main memory. Add a book to the device. If oncard is True then the book is copied
to the card rather than main memory.
@param infile: The source file, should be opened in "rb" mode @param infile: The source file, should be opened in "rb" mode
@param name: The name of the book file when uploaded to the device. The extension of name must be one of the supported formats for this device. @param name: The name of the book file when uploaded to the
device. The extension of name must be one of
the supported formats for this device.
@param info: A dictionary that must have the keys "title", "authors", "cover". @param info: A dictionary that must have the keys "title", "authors", "cover".
C{info["cover"]} should be a three element tuple (width, height, data) where data is the image data in JPEG format as a string C{info["cover"]} should be a three element tuple (width, height, data)
@param booklists: A tuple containing the result of calls to (L{books}(oncard=False), L{books}(oncard=True)). where data is the image data in JPEG format as a string
@todo: Implement syncing the booklists to the device. This would mean juggling with the nextId attribute in media.xml and renumbering ids in cache.xml? @param booklists: A tuple containing the result of calls to
(L{books}(oncard=False), L{books}(oncard=True)).
@todo: Implement syncing the booklists to the device.
This would mean juggling with the nextId attribute in
media.xml and renumbering ids in cache.xml?
""" """
infile.seek(0,2) infile.seek(0, 2)
size = infile.tell() size = infile.tell()
infile.seek(0) infile.seek(0)
card = self.card(end_session=False) card = self.card(end_session=False)
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 oncard and size > cspace - 1024*1024: raise FreeSpaceError("There is insufficient free space on the storage card") if oncard and size > cspace - 1024*1024:
if not oncard and size > mspace - 1024*1024: raise FreeSpaceError("There is insufficient free space in main memory") raise FreeSpaceError("There is insufficient free space "+\
"on the storage card")
if not oncard and size > mspace - 1024*1024:
raise FreeSpaceError("There is insufficient free space " +\
"in main memory")
prefix = "/Data/media/" prefix = "/Data/media/"
if oncard: prefix = card + "/" if oncard:
prefix = card + "/"
else: name = "books/"+name else: name = "books/"+name
path = prefix + name path = prefix + name
self.put_file(infile, path, end_session=False) self.put_file(infile, path, end_session=False)
ctime = self.path_properties(path, end_session=False).ctime ctime = self.path_properties(path, end_session=False).ctime
bl = booklists[1] if oncard else booklists[0] bkl = booklists[1] if oncard else booklists[0]
bl.add_book(info, name, size, ctime) bkl.add_book(info, name, size, ctime)
fix_ids(booklists[0], booklists[1]) fix_ids(booklists[0], booklists[1])
if sync_booklists: if sync_booklists:
self.upload_book_list(booklists[0], end_session=False) self.upload_book_list(booklists[0], end_session=False)
@ -676,11 +836,14 @@ class PRS500Device(object):
@safe @safe
def upload_book_list(self, booklist, end_session=True): def upload_book_list(self, booklist, end_session=True):
if not len(booklist): raise ArgumentError("booklist is empty") if not len(booklist):
raise ArgumentError("booklist is empty")
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: raise ArgumentError("Cannot upload list to card as card is not present") if not card:
raise ArgumentError("Cannot upload list to card as "+\
"card is not present")
path = card + self.CACHE_XML path = card + self.CACHE_XML
f = TemporaryFile() f = TemporaryFile()
booklist.write(f) booklist.write(f)

View File

@ -17,7 +17,6 @@ Defines the errors that libprs500 generates.
G{classtree ProtocolError} G{classtree ProtocolError}
""" """
from exceptions import Exception
class ProtocolError(Exception): class ProtocolError(Exception):
""" The base class for all exceptions in this package """ """ The base class for all exceptions in this package """
@ -27,12 +26,15 @@ class ProtocolError(Exception):
class TimeoutError(ProtocolError): class TimeoutError(ProtocolError):
""" There was a timeout during communication """ """ There was a timeout during communication """
def __init__(self, func_name): def __init__(self, func_name):
ProtocolError.__init__(self, "There was a timeout while communicating with the device in function: "+func_name) ProtocolError.__init__(self, \
"There was a timeout while communicating with the device in function: "\
+func_name)
class DeviceError(ProtocolError): class DeviceError(ProtocolError):
""" Raised when device is not found """ """ Raised when device is not found """
def __init__(self): def __init__(self):
ProtocolError.__init__(self, "Unable to find SONY Reader. Is it connected?") ProtocolError.__init__(self, \
"Unable to find SONY Reader. Is it connected?")
class DeviceBusy(ProtocolError): class DeviceBusy(ProtocolError):
""" Raised when device is busy """ """ Raised when device is busy """
@ -56,7 +58,7 @@ class ControlError(ProtocolError):
def __init__(self, query=None, response=None, desc=None): def __init__(self, query=None, response=None, desc=None):
self.query = query self.query = query
self.response = response self.response = response
Exception.__init__(self, desc) ProtocolError.__init__(self, desc)
def __str__(self): def __str__(self):
if self.query and self.response: if self.query and self.response:

View File

@ -487,4 +487,5 @@ def main():
lock = LockFile(lock) lock = LockFile(lock)
return app.exec_() return app.exec_()
if __name__ == "__main__": sys.exit(main()) if __name__ == "__main__":
sys.exit(main())

View File

@ -12,19 +12,23 @@
## You should have received a copy of the GNU General Public License along ## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
""" """
Defines the structure of packets that are sent to/received from the device. Defines the structure of packets that are sent to/received from the device.
Packet structure is defined using classes and inheritance. Each class is a view that imposes Packet structure is defined using classes and inheritance. Each class is a
structure on the underlying data buffer. The data buffer is encoded in little-endian format, but you don't view that imposes structure on the underlying data buffer.
have to worry about that if you are using the classes. The classes have instance variables with getter/setter functions defined The data buffer is encoded in little-endian format, but you don't
to take care of the encoding/decoding. The classes are intended to mimic C structs. have to worry about that if you are using the classes.
The classes have instance variables with getter/setter functions defined
to take care of the encoding/decoding.
The classes are intended to mimic C structs.
There are three kinds of packets. L{Commands<Command>}, L{Responses<Response>}, and L{Answers<Answer>}. There are three kinds of packets. L{Commands<Command>},
C{Commands} are sent to the device on the control bus, C{Responses} are received from the device, L{Responses<Response>}, and L{Answers<Answer>}.
also on the control bus. C{Answers} and their sub-classes represent data packets sent to/received from C{Commands} are sent to the device on the control bus,
the device via bulk transfers. C{Responses} are received from the device,
also on the control bus. C{Answers} and their sub-classes represent
data packets sent to/received from the device via bulk transfers.
Commands are organized as follows: G{classtree Command} Commands are organized as follows: G{classtree Command}
@ -37,8 +41,10 @@ Responses inherit Command as they share header structure.
Answers are organized as follows: G{classtree Answer} Answers are organized as follows: G{classtree Answer}
""" """
import struct, time import struct
from errors import PacketError import time
from libprs500.errors import PacketError
DWORD = "<I" #: Unsigned integer little endian encoded in 4 bytes 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
@ -143,7 +149,7 @@ class TransferBuffer(list):
index, sign = 2, "" index, sign = 2, ""
if num < 0: if num < 0:
index, sign = 3, "-" index, sign = 3, "-"
h=hex(num)[index:] h = hex(num)[index:]
if len(h) < 2: if len(h) < 2:
h = "0"+h h = "0"+h
return sign + h return sign + h
@ -156,7 +162,8 @@ class field(object):
def __init__(self, start=16, fmt=DWORD): def __init__(self, start=16, fmt=DWORD):
""" """
@param start: The byte at which this field is stored in the buffer @param start: The byte at which this field is stored in the buffer
@param fmt: The packing format for this field. See U{struct<http://docs.python.org/lib/module-struct.html>}. @param fmt: The packing format for this field.
See U{struct<http://docs.python.org/lib/module-struct.html>}.
""" """
self._fmt, self._start = fmt, start self._fmt, self._start = fmt, start
@ -168,9 +175,13 @@ class field(object):
def __repr__(self): def __repr__(self):
typ = "" typ = ""
if self._fmt == DWORD: typ = "unsigned int" if self._fmt == DWORD:
if self._fmt == DDWORD: typ = "unsigned long long" typ = "unsigned int"
return "An " + typ + " stored in " + str(struct.calcsize(self._fmt)) + " bytes starting at byte " + str(self._start) if self._fmt == DDWORD:
typ = "unsigned long long"
return "An " + typ + " stored in " + \
str(struct.calcsize(self._fmt)) + \
" bytes starting at byte " + str(self._start)
class stringfield(object): class stringfield(object):
""" A field storing a variable length string. """ """ A field storing a variable length string. """
@ -188,7 +199,7 @@ class stringfield(object):
return obj.unpack(start=self._start, fmt="<"+length+"s")[0] return obj.unpack(start=self._start, fmt="<"+length+"s")[0]
def __set__(self, obj, val): def __set__(self, obj, val):
if val.__class__.__name__ != 'str': val = str(val) val = str(val)
obj.pack(val, start=self._start, fmt="<"+str(len(val))+"s") obj.pack(val, start=self._start, fmt="<"+str(len(val))+"s")
def __repr__(self): def __repr__(self):
@ -197,107 +208,109 @@ class stringfield(object):
class Command(TransferBuffer): class Command(TransferBuffer):
""" Defines the structure of command packets sent to the device. """ """ Defines the structure of command packets sent to the device. """
# Command number. C{unsigned int} stored in 4 bytes at byte 0.
#
# Command numbers are:
# 0 GetUsbProtocolVersion
# 1 ReqEndSession
# 10 FskFileOpen
# 11 FskFileClose
# 12 FskGetSize
# 13 FskSetSize
# 14 FskFileSetPosition
# 15 FskGetPosition
# 16 FskFileRead
# 17 FskFileWrite
# 18 FskFileGetFileInfo
# 19 FskFileSetFileInfo
# 1A FskFileCreate
# 1B FskFileDelete
# 1C FskFileRename
# 30 FskFileCreateDirectory
# 31 FskFileDeleteDirectory
# 32 FskFileRenameDirectory
# 33 FskDirectoryIteratorNew
# 34 FskDirectoryIteratorDispose
# 35 FskDirectoryIteratorGetNext
# 52 FskVolumeGetInfo
# 53 FskVolumeGetInfoFromPath
# 80 FskFileTerminate
# 100 ConnectDevice
# 101 GetProperty
# 102 GetMediaInfo
# 103 GetFreeSpace
# 104 SetTime
# 105 DeviceBeginEnd
# 106 UnlockDevice
# 107 SetBulkSize
# 110 GetHttpRequest
# 111 SetHttpRespponse
# 112 Needregistration
# 114 GetMarlinState
# 200 ReqDiwStart
# 201 SetDiwPersonalkey
# 202 GetDiwPersonalkey
# 203 SetDiwDhkey
# 204 GetDiwDhkey
# 205 SetDiwChallengeserver
# 206 GetDiwChallengeserver
# 207 GetDiwChallengeclient
# 208 SetDiwChallengeclient
# 209 GetDiwVersion
# 20A SetDiwWriteid
# 20B GetDiwWriteid
# 20C SetDiwSerial
# 20D GetDiwModel
# 20C SetDiwSerial
# 20E GetDiwDeviceid
# 20F GetDiwSerial
# 210 ReqDiwCheckservicedata
# 211 ReqDiwCheckiddata
# 212 ReqDiwCheckserialdata
# 213 ReqDiwFactoryinitialize
# 214 GetDiwMacaddress
# 215 ReqDiwTest
# 216 ReqDiwDeletekey
# 300 UpdateChangemode
# 301 UpdateDeletePartition
# 302 UpdateCreatePartition
# 303 UpdateCreatePartitionWithImage
# 304 UpdateGetPartitionSize
number = field(start=0, fmt=DWORD) number = field(start=0, fmt=DWORD)
""" # Known types are 0x00 and 0x01. Acknowledge commands are always type 0x00
Command number. C{unsigned int} stored in 4 bytes at byte 0. type = field(start=4, fmt=DDWORD)
# Length of the data part of this packet
Command numbers are: length = field(start=12, fmt=DWORD)
0 GetUsbProtocolVersion
1 ReqEndSession
10 FskFileOpen
11 FskFileClose
12 FskGetSize
13 FskSetSize
14 FskFileSetPosition
15 FskGetPosition
16 FskFileRead
17 FskFileWrite
18 FskFileGetFileInfo
19 FskFileSetFileInfo
1A FskFileCreate
1B FskFileDelete
1C FskFileRename
30 FskFileCreateDirectory
31 FskFileDeleteDirectory
32 FskFileRenameDirectory
33 FskDirectoryIteratorNew
34 FskDirectoryIteratorDispose
35 FskDirectoryIteratorGetNext
52 FskVolumeGetInfo
53 FskVolumeGetInfoFromPath
80 FskFileTerminate
100 ConnectDevice
101 GetProperty
102 GetMediaInfo
103 GetFreeSpace
104 SetTime
105 DeviceBeginEnd
106 UnlockDevice
107 SetBulkSize
110 GetHttpRequest
111 SetHttpRespponse
112 Needregistration
114 GetMarlinState
200 ReqDiwStart
201 SetDiwPersonalkey
202 GetDiwPersonalkey
203 SetDiwDhkey
204 GetDiwDhkey
205 SetDiwChallengeserver
206 GetDiwChallengeserver
207 GetDiwChallengeclient
208 SetDiwChallengeclient
209 GetDiwVersion
20A SetDiwWriteid
20B GetDiwWriteid
20C SetDiwSerial
20D GetDiwModel
20C SetDiwSerial
20E GetDiwDeviceid
20F GetDiwSerial
210 ReqDiwCheckservicedata
211 ReqDiwCheckiddata
212 ReqDiwCheckserialdata
213 ReqDiwFactoryinitialize
214 GetDiwMacaddress
215 ReqDiwTest
216 ReqDiwDeletekey
300 UpdateChangemode
301 UpdateDeletePartition
302 UpdateCreatePartition
303 UpdateCreatePartitionWithImage
304 UpdateGetPartitionSize
"""
type = field(start=4, fmt=DDWORD) #: Known types are 0x00 and 0x01. Acknowledge commands are always type 0x00
length = field(start=12, fmt=DWORD) #: Length of the data part of this packet
@apply @apply
def data(): def data():
doc =\ doc = \
""" """
The data part of this command. Returned/set as/by a TransferBuffer. Stored at byte 16. The data part of this command. Returned/set as/by a TransferBuffer.
Stored at byte 16.
Setting it by default changes self.length to the length of the new buffer. You may have to reset it to Setting it by default changes self.length to the length of the new
the significant part of the buffer. You would normally use the C{command} property of L{ShortCommand} or L{LongCommand} instead. buffer. You may have to reset it to the significant part of the buffer.
You would normally use the C{command} property of
L{ShortCommand} or L{LongCommand} instead.
""" """
def fget(self): def fget(self):
return self[16:] return self[16:]
def fset(self, buffer): def fset(self, buff):
self[16:] = buffer self[16:] = buff
self.length = len(buffer) self.length = len(buff)
return property(**locals()) return property(doc=doc, fget=fget, fset=fset)
def __init__(self, packet): def __init__(self, packet):
""" """
@param packet: len(packet) > 15 or packet > 15 @param packet: len(packet) > 15 or packet > 15
""" """
if ("__len__" in dir(packet) and len(packet) < 16) or ("__len__" not in dir(packet) and packet < 16): if ("__len__" in dir(packet) and len(packet) < 16) or\
raise PacketError(str(self.__class__)[7:-2] + " packets must have length atleast 16") ("__len__" not in dir(packet) and packet < 16):
raise PacketError(str(self.__class__)[7:-2] + \
" packets must have length atleast 16")
TransferBuffer.__init__(self, packet) TransferBuffer.__init__(self, packet)
@ -306,8 +319,9 @@ class SetTime(Command):
Set time on device. All fields refer to time in the GMT time zone. Set time on device. All fields refer to time in the GMT time zone.
""" """
NUMBER = 0x104 NUMBER = 0x104
# -time.timezone with negative numbers encoded
timezone = field(start=0x10, fmt=DWORD) #: -time.timezone with negative numbers encoded as int(0xffffffff +1 -time.timezone/60.) # as int(0xffffffff +1 -time.timezone/60.)
timezone = field(start=0x10, fmt=DWORD)
year = field(start=0x14, fmt=DWORD) #: year e.g. 2006 year = field(start=0x14, fmt=DWORD) #: year e.g. 2006
month = field(start=0x18, fmt=DWORD) #: month 1-12 month = field(start=0x18, fmt=DWORD) #: month 1-12
day = field(start=0x1c, fmt=DWORD) #: day 1-31 day = field(start=0x1c, fmt=DWORD) #: day 1-31
@ -329,7 +343,9 @@ class SetTime(Command):
self.day = t[2] self.day = t[2]
self.hour = t[3] self.hour = t[3]
self.minute = t[4] self.minute = t[4]
self.second = t[5] if t[5] < 60 else 59 # Hack you should actually update the entire time tree is second is > 59 # Hack you should actually update the entire time tree is
# second is > 59
self.second = t[5] if t[5] < 60 else 59
class ShortCommand(Command): class ShortCommand(Command):
@ -337,7 +353,8 @@ class ShortCommand(Command):
""" A L{Command} whoose data section is 4 bytes long """ """ A L{Command} whoose data section is 4 bytes long """
SIZE = 20 #: Packet size in bytes SIZE = 20 #: Packet size in bytes
command = field(start=16, fmt=DWORD) #: Usually carries additional information # Usually carries additional information
command = field(start=16, fmt=DWORD)
def __init__(self, number=0x00, type=0x00, command=0x00): def __init__(self, number=0x00, type=0x00, command=0x00):
""" """
@ -354,38 +371,50 @@ class ShortCommand(Command):
class DirRead(ShortCommand): class DirRead(ShortCommand):
""" The command that asks the device to send the next item in the list """ """ The command that asks the device to send the next item in the list """
NUMBER = 0x35 #: Command number NUMBER = 0x35 #: Command number
def __init__(self, id): def __init__(self, _id):
""" @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=DirRead.NUMBER, type=0x01, command=id) ShortCommand.__init__(self, number=DirRead.NUMBER, type=0x01, \
command=_id)
class DirClose(ShortCommand): class DirClose(ShortCommand):
""" Close a previously opened directory """ """ Close a previously opened directory """
NUMBER = 0x34 #: Command number NUMBER = 0x34 #: Command number
def __init__(self, id): def __init__(self, _id):
""" @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 EndSession(ShortCommand): class EndSession(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 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): def __init__(self):
ShortCommand.__init__(self, number=EndSession.NUMBER, type=0x01, command=0x00) ShortCommand.__init__(self, \
number=EndSession.NUMBER, type=0x01, command=0x00)
class GetUSBProtocolVersion(ShortCommand): class GetUSBProtocolVersion(ShortCommand):
""" Get USB Protocol version used by device """ """ Get USB Protocol version used by device """
NUMBER=0x0 #: Command number NUMBER = 0x0 #: Command number
def __init__(self): def __init__(self):
ShortCommand.__init__(self, number=GetUSBProtocolVersion.NUMBER, type=0x01, command=0x00) ShortCommand.__init__(self, \
number=GetUSBProtocolVersion.NUMBER, \
type=0x01, command=0x00)
class SetBulkSize(ShortCommand): class SetBulkSize(ShortCommand):
""" Set size for bulk transfers in this session """
NUMBER = 0x107 #: Command number NUMBER = 0x107 #: Command number
def __init__(self, size=0x028000): def __init__(self, size=0x028000):
ShortCommand.__init__(self, number=SetBulkSize.NUMBER, type=0x01, command=size) ShortCommand.__init__(self, \
number=SetBulkSize.NUMBER, type=0x01, command=size)
class UnlockDevice(ShortCommand): class UnlockDevice(ShortCommand):
""" Unlock the device """
NUMBER = 0x106 #: Command number NUMBER = 0x106 #: Command number
def __init__(self, key=0x312d): def __init__(self, key=0x312d):
ShortCommand.__init__(self, number=UnlockDevice.NUMBER, type=0x01, command=key) ShortCommand.__init__(self, \
number=UnlockDevice.NUMBER, type=0x01, command=key)
class LongCommand(Command): class LongCommand(Command):
@ -407,10 +436,11 @@ class LongCommand(Command):
@apply @apply
def command(): def command():
doc =\ doc = \
""" """
Usually carries extra information needed for the command Usually carries extra information needed for the command
It is a list of C{unsigned integers} of length between 1 and 4. 4 C{unsigned int} stored in 16 bytes at byte 16. It is a list of C{unsigned integers} of length between 1 and 4. 4
C{unsigned int} stored in 16 bytes at byte 16.
""" """
def fget(self): def fget(self):
return self.unpack(start=16, fmt="<"+str(self.length/4)+"I") return self.unpack(start=16, fmt="<"+str(self.length/4)+"I")
@ -422,7 +452,7 @@ class LongCommand(Command):
self.pack(command, start=start, fmt=DWORD) self.pack(command, start=start, fmt=DWORD)
start += struct.calcsize(DWORD) start += struct.calcsize(DWORD)
return property(**locals()) return property(doc=doc, fget=fget, fset=fset)
class PathCommand(Command): class PathCommand(Command):
""" Abstract class that defines structure common to all path related commands. """ """ Abstract class that defines structure common to all path related commands. """
@ -434,7 +464,7 @@ class PathCommand(Command):
self.path_length = len(path) self.path_length = len(path)
self.path = path self.path = path
self.type = 0x01 self.type = 0x01
self.length = len(self)-16 self.length = len(self) - 16
self.number = number self.number = number
class TotalSpaceQuery(PathCommand): class TotalSpaceQuery(PathCommand):
@ -452,7 +482,8 @@ class FreeSpaceQuery(ShortCommand):
c = 0 c = 0
if where.startswith('a:'): c = 1 if where.startswith('a:'): c = 1
elif where.startswith('b:'): c = 2 elif where.startswith('b:'): c = 2
ShortCommand.__init__(self, number=FreeSpaceQuery.NUMBER, type=0x01, command=c) ShortCommand.__init__(self, \
number=FreeSpaceQuery.NUMBER, type=0x01, command=c)
class DirCreate(PathCommand): class DirCreate(PathCommand):
""" Create a directory """ """ Create a directory """
@ -470,38 +501,43 @@ class DirOpen(PathCommand):
class AcknowledgeBulkRead(LongCommand): class AcknowledgeBulkRead(LongCommand):
""" Must be sent to device after a bulk read """ """ Must be sent to device after a bulk read """
def __init__(self, bulk_read_id): def __init__(self, bulk_read_id):
""" bulk_read_id is an integer, the id of the bulk read we are acknowledging. See L{Answer.id} """ """
LongCommand.__init__(self, number=0x1000, type=0x00, command=bulk_read_id) bulk_read_id is an integer, the id of the bulk read
we are acknowledging. See L{Answer.id}
"""
LongCommand.__init__(self, number=0x1000, \
type=0x00, command=bulk_read_id)
class DeviceInfoQuery(Command): class DeviceInfoQuery(Command):
""" The command used to ask for device information """ """ The command used to ask for device information """
NUMBER=0x0101 #: Command number NUMBER = 0x101 #: Command number
def __init__(self): def __init__(self):
Command.__init__(self, 16) Command.__init__(self, 16)
self.number=DeviceInfoQuery.NUMBER self.number = DeviceInfoQuery.NUMBER
self.type=0x01 self.type = 0x01
class FileClose(ShortCommand): class FileClose(ShortCommand):
""" File close command """ """ File close command """
NUMBER = 0x11 #: Command number NUMBER = 0x11 #: Command number
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): class FileCreate(PathCommand):
""" Create a file """ """ Create a file """
NUMBER=0x1a #: Command number NUMBER = 0x1a #: Command number
def __init__(self, path): def __init__(self, path):
PathCommand.__init__(self, path, FileCreate.NUMBER) PathCommand.__init__(self, path, FileCreate.NUMBER)
class FileDelete(PathCommand): class FileDelete(PathCommand):
""" Delete a file """ """ Delete a file """
NUMBER=0x1B NUMBER = 0x1B
def __init__(self, path): def __init__(self, path):
PathCommand.__init__(self, path, FileDelete.NUMBER) PathCommand.__init__(self, path, FileDelete.NUMBER)
class DirDelete(PathCommand): class DirDelete(PathCommand):
""" Delete a directory """ """ Delete a directory """
NUMBER=0x31 NUMBER = 0x31
def __init__(self, path): def __init__(self, path):
PathCommand.__init__(self, path, DirDelete.NUMBER) PathCommand.__init__(self, path, DirDelete.NUMBER)
@ -519,15 +555,18 @@ class FileOpen(PathCommand):
@apply @apply
def mode(): def mode():
doc =\ doc = \
""" The file open mode. Is either L{FileOpen.READ} or L{FileOpen.WRITE}. C{unsigned int} stored at byte 16. """ """
The file open mode. Is either L{FileOpen.READ}
or L{FileOpen.WRITE}. C{unsigned int} stored at byte 16.
"""
def fget(self): def fget(self):
return self.unpack(start=16, fmt=DWORD)[0] return self.unpack(start=16, fmt=DWORD)[0]
def fset(self, val): def fset(self, val):
self.pack(val, start=16, fmt=DWORD) self.pack(val, start=16, fmt=DWORD)
return property(**locals()) return property(doc=doc, fget=fget, fset=fset)
class FileIO(Command): class FileIO(Command):
@ -537,9 +576,9 @@ class FileIO(Command):
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, mode=0x16): 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}
@param offset: Position in file at which to read @param offset: Position in file at which to read
@type offset: C{unsigned long long} @type offset: C{unsigned long long}
@ -548,10 +587,10 @@ class FileIO(Command):
@param mode: Either L{FileIO.RNUMBER} or L{File.WNUMBER} @param mode: Either L{FileIO.RNUMBER} or L{File.WNUMBER}
""" """
Command.__init__(self, 32) Command.__init__(self, 32)
self.number=mode self.number = mode
self.type = 0x01 self.type = 0x01
self.length = 16 self.length = 16
self.id = id self.id = _id
self.offset = offset self.offset = offset
self.size = size self.size = size
@ -572,81 +611,100 @@ class Response(Command):
""" """
Defines the structure of response packets received from the device. Defines the structure of response packets received from the device.
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 # Response number, the command number of a command
code = field(start=20, fmt=DWORD) #: Used to indicate error conditions. A value of 0 means there was no error # packet sent sometime before this packet was received
data_size = field(start=28, fmt=DWORD) #: Used to indicate the size of the next bulk read rnumber = field(start=16, fmt=DWORD)
# Used to indicate error conditions. A value of 0 means
# there was no error
code = field(start=20, fmt=DWORD)
# Used to indicate the size of the next bulk read
data_size = field(start=28, fmt=DWORD)
def __init__(self, packet): def __init__(self, packet):
""" C{len(packet) == Response.SIZE} """ """ C{len(packet) == Response.SIZE} """
if len(packet) != Response.SIZE: if len(packet) != Response.SIZE:
raise PacketError(str(self.__class__)[7:-2] + " packets must have exactly " + str(Response.SIZE) + " bytes not " + str(len(packet))) raise PacketError(str(self.__class__)[7:-2] + \
" packets must have exactly " + \
str(Response.SIZE) + " bytes not " + str(len(packet)))
Command.__init__(self, packet) Command.__init__(self, packet)
if self.number != 0x00001000: if self.number != 0x00001000:
raise PacketError("Response packets must have their number set to " + hex(0x00001000)) raise PacketError("Response packets must have their number set to " \
+ hex(0x00001000))
@apply @apply
def data(): def data():
doc =\ doc = \
""" The last 3 DWORDs (12 bytes) of data in this response packet. Returned as a list of unsigned integers. """ """
The last 3 DWORDs (12 bytes) of data in this
response packet. Returned as a list of unsigned integers.
"""
def fget(self): def fget(self):
return self.unpack(start=20, fmt="<III") return self.unpack(start=20, fmt="<III")
def fset(self, val): def fset(self, val):
self.pack(val, start=20, fmt="<III") self.pack(val, start=20, fmt="<III")
return property(**locals()) return property(doc=doc, fget=fget, fset=fset)
class ListResponse(Response): class ListResponse(Response):
""" Defines the structure of response packets received during list (ll) queries. See L{PathQuery}. """ """
Defines the structure of response packets received
during list (ll) queries. See L{PathQuery}.
"""
IS_FILE = 0xffffffd2 #: Queried path is a file IS_FILE = 0xffffffd2 #: Queried path is a file
IS_INVALID = 0xfffffff9 #: Queried path is malformed/invalid IS_INVALID = 0xfffffff9 #: Queried path is malformed/invalid
IS_UNMOUNTED = 0xffffffc8 #: Queried path is not mounted (i.e. a removed storage card/stick) # Queried path is not mounted (i.e. a removed storage card/stick)
IS_UNMOUNTED = 0xffffffc8
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
@apply @apply
def is_file(): def is_file():
""" True iff queried path is a file """ doc = """ True iff queried path is a file """
def fget(self): def fget(self):
return self.code == ListResponse.IS_FILE return self.code == ListResponse.IS_FILE
return property(**locals()) return property(doc=doc, fget=fget)
@apply @apply
def is_invalid(): def is_invalid():
""" True iff queried path is invalid """ doc = """ True iff queried path is invalid """
def fget(self): def fget(self):
return self.code == ListResponse.IS_INVALID return self.code == ListResponse.IS_INVALID
return property(**locals()) return property(doc=doc, fget=fget)
@apply @apply
def path_not_found(): def path_not_found():
""" True iff queried path is not found """ doc = """ True iff queried path is not found """
def fget(self): def fget(self):
return self.code == ListResponse.PATH_NOT_FOUND return self.code == ListResponse.PATH_NOT_FOUND
return property(**locals()) return property(doc=doc, fget=fget)
@apply @apply
def is_unmounted(): def is_unmounted():
""" True iff queried path is unmounted (i.e. removed storage card) """ doc = """ True iff queried path is unmounted (i.e. removed storage card) """
def fget(self): def fget(self):
return self.code == ListResponse.IS_UNMOUNTED return self.code == ListResponse.IS_UNMOUNTED
return property(**locals()) return property(doc=doc, fget=fget)
@apply @apply
def is_eol(): def is_eol():
""" True iff there are no more items in the list """ doc = """ True iff there are no more items in the list """
def fget(self): def fget(self):
return self.code == ListResponse.IS_EOL return self.code == ListResponse.IS_EOL
return property(**locals()) return property(doc=doc, fget=fget)
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 number = field(start=0, fmt=DWORD) #: Answer identifier
length = field(start=12, fmt=DWORD) #: Length of data to follow length = field(start=12, fmt=DWORD) #: Length of data to follow
@ -655,21 +713,27 @@ class Answer(TransferBuffer):
""" @param packet: C{len(packet)} S{>=} C{16} """ """ @param packet: C{len(packet)} S{>=} C{16} """
if "__len__" in dir(packet): if "__len__" in dir(packet):
if len(packet) < 16 : if len(packet) < 16 :
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes") raise PacketError(str(self.__class__)[7:-2] + \
" packets must have a length of atleast 16 bytes")
elif packet < 16: elif packet < 16:
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes") raise PacketError(str(self.__class__)[7:-2] + \
" packets must have a length of atleast 16 bytes")
TransferBuffer.__init__(self, packet) TransferBuffer.__init__(self, packet)
class FileProperties(Answer): 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) #: Size in bytes of the file file_size = field(start=16, fmt=DDWORD) #: Size in bytes of the file
file_type = field(start=24, fmt=DWORD) #: 1 == file, 2 == dir file_type = field(start=24, fmt=DWORD) #: 1 == file, 2 == dir
ctime = field(start=28, fmt=DWORD) #: Creation time as an epoch ctime = field(start=28, fmt=DWORD) #: Creation time as an epoch
wtime = field(start=32, fmt=DWORD) #: Modification time as an epoch wtime = field(start=32, fmt=DWORD) #: Modification time as an epoch
permissions = field(start=36, fmt=DWORD) #: 0 = default permissions, 4 = read only # 0 = default permissions, 4 = read only
permissions = field(start=36, fmt=DWORD)
@apply @apply
def is_dir(): def is_dir():
@ -679,11 +743,13 @@ class FileProperties(Answer):
return (self.file_type == 2) return (self.file_type == 2)
def fset(self, val): def fset(self, val):
if val: val = 2 if val:
else: val = 1 val = 2
else:
val = 1
self.file_type = val self.file_type = val
return property(**locals()) return property(doc=doc, fget=fget, fset=fset)
@apply @apply
@ -694,14 +760,17 @@ class FileProperties(Answer):
return self.unpack(start=36, fmt=DWORD)[0] != 0 return self.unpack(start=36, fmt=DWORD)[0] != 0
def fset(self, val): def fset(self, val):
if val: val = 4 if val:
else: val = 0 val = 4
else:
val = 0
self.pack(val, start=36, fmt=DWORD) self.pack(val, start=36, fmt=DWORD)
return property(**locals()) return property(doc=doc, fget=fget, fset=fset)
class USBProtocolVersion(Answer): class USBProtocolVersion(Answer):
""" Get USB Protocol version """
version = field(start=16, fmt=DDWORD) version = field(start=16, fmt=DDWORD)
class IdAnswer(Answer): class IdAnswer(Answer):
@ -710,8 +779,12 @@ class IdAnswer(Answer):
@apply @apply
def id(): def id():
doc =\ doc = \
""" The identifier. C{unsigned int} stored in 4 bytes at byte 16. Should be sent in commands asking for the next item in the list. """ """
The identifier. C{unsigned int} stored in 4 bytes
at byte 16. Should be sent in commands asking
for the next item in the list.
"""
def fget(self): def fget(self):
return self.unpack(start=16, fmt=DWORD)[0] return self.unpack(start=16, fmt=DWORD)[0]
@ -719,7 +792,7 @@ class IdAnswer(Answer):
def fset(self, val): def fset(self, val):
self.pack(val, start=16, fmt=DWORD) self.pack(val, start=16, fmt=DWORD)
return property(**locals()) return property(doc=doc, fget=fget, fset=fset)
class DeviceInfo(Answer): class DeviceInfo(Answer):
""" Defines the structure of the packet containing information about the device """ """ Defines the structure of the packet containing information about the device """
@ -731,7 +804,8 @@ class DeviceInfo(Answer):
class TotalSpaceAnswer(Answer): class TotalSpaceAnswer(Answer):
total = field(start=24, fmt=DDWORD) #: Total space available total = field(start=24, fmt=DDWORD) #: Total space available
free_space = field(start=32, fmt=DDWORD) #: Supposedly free space available, but it does not work for main memory # Supposedly free space available, but it does not work for main memory
free_space = field(start=32, fmt=DDWORD)
class FreeSpaceAnswer(Answer): class FreeSpaceAnswer(Answer):
SIZE = 24 SIZE = 24
@ -745,8 +819,11 @@ class ListAnswer(Answer):
@apply @apply
def is_dir(): def is_dir():
doc =\ doc = \
""" True if list item points to a directory, False if it points to a file. C{unsigned int} stored in 4 bytes at byte 16. """ """
True if list item points to a directory, False if it points to a file.
C{unsigned int} stored in 4 bytes at byte 16.
"""
def fget(self): def fget(self):
return (self.unpack(start=16, fmt=DWORD)[0] == 2) return (self.unpack(start=16, fmt=DWORD)[0] == 2)
@ -756,6 +833,6 @@ class ListAnswer(Answer):
else: val = 1 else: val = 1
self.pack(val, start=16, fmt=DWORD) self.pack(val, start=16, fmt=DWORD)
return property(**locals()) return property(doc=doc, fget=fget, fset=fset)

View File

@ -1,239 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Project SYSTEM "Project-4.0.dtd">
<!-- eric4 project file for project prs-500 -->
<!-- Saved: 2006-12-08, 23:24:29 -->
<!-- Copyright (C) 2006 Kovid Goyal, kovid@kovidgoyal.net -->
<Project version="4.0">
<ProgLanguage mixed="0">Python</ProgLanguage>
<UIType>Qt4</UIType>
<Description>Library to communicate with the Sony Reader PRS-500 via USB</Description>
<Version></Version>
<Author>Kovid Goyal</Author>
<Email>kovid@kovidgoyal.net</Email>
<Sources>
<Source>
<Dir>libprs500</Dir>
<Name>communicate.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Name>prstypes.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Name>errors.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Name>__init__.py</Name>
</Source>
<Source>
<Name>setup.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>cli</Dir>
<Name>terminfo.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>cli</Dir>
<Name>main.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>cli</Dir>
<Name>__init__.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>gui</Dir>
<Name>main.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>gui</Dir>
<Name>__init__.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>lrf</Dir>
<Name>meta.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>lrf</Dir>
<Name>__init__.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>gui</Dir>
<Name>database.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>gui</Dir>
<Name>editbook.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Name>books.py</Name>
</Source>
<Source>
<Dir>libprs500</Dir>
<Dir>gui</Dir>
<Name>widgets.py</Name>
</Source>
</Sources>
<Forms>
<Form>
<Dir>libprs500</Dir>
<Dir>gui</Dir>
<Name>main.ui</Name>
</Form>
<Form>
<Dir>libprs500</Dir>
<Dir>gui</Dir>
<Name>editbook.ui</Name>
</Form>
</Forms>
<Translations>
</Translations>
<Resources>
</Resources>
<Interfaces>
</Interfaces>
<Others>
<Other>
<Name>epydoc.conf</Name>
</Other>
<Other>
<Name>epydoc-pdf.conf</Name>
</Other>
</Others>
<MainScript>
<Dir>libprs500</Dir>
<Dir>gui</Dir>
<Name>main.py</Name>
</MainScript>
<Vcs>
<VcsType>Subversion</VcsType>
<VcsOptions>
<dict>
<key>
<string>status</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>log</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>global</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>update</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>remove</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>add</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>tag</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>export</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>commit</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>diff</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>checkout</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
<key>
<string>history</string>
</key>
<value>
<list>
<string></string>
</list>
</value>
</dict>
</VcsOptions>
<VcsOtherData>
<dict>
<key>
<string>standardLayout</string>
</key>
<value>
<bool>True</bool>
</value>
</dict>
</VcsOtherData>
</Vcs>
<FiletypeAssociations>
<FiletypeAssociation pattern="*.py" type="SOURCES" />
<FiletypeAssociation pattern="*.ui.h" type="FORMS" />
<FiletypeAssociation pattern="*.idl" type="INTERFACES" />
<FiletypeAssociation pattern="*.ui" type="FORMS" />
<FiletypeAssociation pattern="*.ptl" type="SOURCES" />
</FiletypeAssociations>
</Project>