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
## with this program; if not, write to the Free Software Foundation, Inc.,
## 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
import xml.dom.minidom as dom
from base64 import b64decode as decode
from base64 import b64encode as encode
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):
""" Represents metadata stored as an attribute """
def __init__(self, attr, formatter=None, setter=None):
self.attr = attr
self.formatter = formatter
@ -29,25 +38,36 @@ class book_metadata_field(object):
def __get__(self, obj, typ=None):
""" 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):
""" Set the attribute """
val = self.setter(val) if self.setter else val
obj.elem.setAttribute(self.attr, str(val))
class Book(object):
""" Provides a view onto the XML element that represents a book """
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")
rpath = book_metadata_field("path")
id = book_metadata_field("id", formatter=int)
sourceid = book_metadata_field("sourceid", formatter=int)
size = book_metadata_field("size", formatter=int)
# 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
def thumbnail():
doc = \
"""
The thumbnail. Should be a height 68 image.
Setting is not supported.
"""
def fget(self):
th = self.elem.getElementsByTagName(self.prefix + "thumbnail")
if len(th):
@ -57,14 +77,17 @@ class Book(object):
break
rc = ""
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 property(**locals())
return property(fget=fget, doc=doc)
@apply
def path():
def fget(self): return self.root + self.rpath
return property(**locals())
doc = """ Absolute path to book on device. Setting not supported. """
def fget(self):
return self.root + self.rpath
return property(fget=fget, doc=doc)
def __init__(self, node, prefix="xs1:", root="/Data/media/"):
self.elem = node
@ -79,57 +102,77 @@ class Book(object):
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:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
child.setAttribute("id", str(id))
id += 1
mmaxid = id - 1
id = mmaxid + 2
if child.nodeType == child.ELEMENT_NODE and \
child.hasAttribute("id"):
child.setAttribute("id", str(cid))
cid += 1
mmaxid = cid - 1
cid = mmaxid + 2
if len(cache):
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("id", str(id))
id += 1
media.document.documentElement.setAttribute("nextID", str(id))
child.setAttribute("id", str(cid))
cid += 1
media.document.documentElement.setAttribute("nextID", str(cid))
class BookList(list):
"""
A list of L{Book}s. Created from an XML file. Can write list
to an XML file.
"""
__getslice__ = 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)
if file:
if sfile:
self.prefix = prefix
self.proot = root
file.seek(0)
self.document = dom.parse(file)
self.root = self.document.documentElement #: The root element containing all records
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))
sfile.seek(0)
self.document = dom.parse(sfile)
# The root element containing all records
self.root = self.document.documentElement
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):
id = -1
""" Highest id in underlying XML file """
cid = -1
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"))
if c > id: id = c
return id
if c > cid:
cid = c
return cid
def has_id(self, id):
""" Check if a book with id C{ == id} exists already. This *does not* check if id exists in the underlying XML file """
def has_id(self, cid):
"""
Check if a book with id C{ == cid} exists already.
This *does not* check if id exists in the underlying XML file
"""
ans = False
for book in self:
if book.id == id:
if book.id == cid:
ans = True
break
return ans
def delete_book(self, id):
def delete_book(self, cid):
""" Remove DOM node corresponding to book with C{id == cid}."""
node = None
for book in self:
if book.id == id:
if book.id == cid:
node = book
self.remove(book)
break
@ -137,11 +180,15 @@ class BookList(list):
node.elem.unlink()
def add_book(self, info, name, size, ctime):
""" Add a node into DOM tree representing a book """
node = self.document.createElement(self.prefix + "text")
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"
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():
node.setAttributeNode(self.document.createAttribute(attr))
node.setAttribute(attr, attrs[attr])
@ -160,4 +207,5 @@ class BookList(list):
self.append(book)
def write(self, stream):
""" Write XML representation of DOM tree to C{stream} """
PrettyPrint(self.document, stream)

View File

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

View File

@ -17,7 +17,6 @@ Defines the errors that libprs500 generates.
G{classtree ProtocolError}
"""
from exceptions import Exception
class ProtocolError(Exception):
""" The base class for all exceptions in this package """
@ -27,12 +26,15 @@ class ProtocolError(Exception):
class TimeoutError(ProtocolError):
""" There was a timeout during communication """
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):
""" Raised when device is not found """
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):
""" Raised when device is busy """
@ -56,7 +58,7 @@ class ControlError(ProtocolError):
def __init__(self, query=None, response=None, desc=None):
self.query = query
self.response = response
Exception.__init__(self, desc)
ProtocolError.__init__(self, desc)
def __str__(self):
if self.query and self.response:

View File

@ -487,4 +487,5 @@ def main():
lock = LockFile(lock)
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
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
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
structure on the underlying data buffer. The data buffer is encoded in little-endian format, but you don't
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.
Packet structure is defined using classes and inheritance. Each class is a
view that imposes structure on the underlying data buffer.
The data buffer is encoded in little-endian format, but you don't
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>}.
C{Commands} are sent to the device on the control bus, 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.
There are three kinds of packets. L{Commands<Command>},
L{Responses<Response>}, and L{Answers<Answer>}.
C{Commands} are sent to the device on the control bus,
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}
@ -37,8 +41,10 @@ Responses inherit Command as they share header structure.
Answers are organized as follows: G{classtree Answer}
"""
import struct, time
from errors import PacketError
import struct
import time
from libprs500.errors import PacketError
DWORD = "<I" #: Unsigned integer little endian encoded in 4 bytes
DDWORD = "<Q" #: Unsigned long long little endian encoded in 8 bytes
@ -156,7 +162,8 @@ class field(object):
def __init__(self, start=16, fmt=DWORD):
"""
@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
@ -168,9 +175,13 @@ class field(object):
def __repr__(self):
typ = ""
if self._fmt == DWORD: typ = "unsigned int"
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)
if self._fmt == DWORD:
typ = "unsigned int"
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):
""" A field storing a variable length string. """
@ -188,7 +199,7 @@ class stringfield(object):
return obj.unpack(start=self._start, fmt="<"+length+"s")[0]
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")
def __repr__(self):
@ -197,107 +208,109 @@ class stringfield(object):
class Command(TransferBuffer):
""" 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)
"""
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
"""
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
# Known types are 0x00 and 0x01. Acknowledge commands are always type 0x00
type = field(start=4, fmt=DDWORD)
# Length of the data part of this packet
length = field(start=12, fmt=DWORD)
@apply
def data():
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
the significant part of the buffer. You would normally use the C{command} property of L{ShortCommand} or L{LongCommand} instead.
Setting it by default changes self.length to the length of the new
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):
return self[16:]
def fset(self, buffer):
self[16:] = buffer
self.length = len(buffer)
def fset(self, buff):
self[16:] = buff
self.length = len(buff)
return property(**locals())
return property(doc=doc, fget=fget, fset=fset)
def __init__(self, packet):
"""
@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):
raise PacketError(str(self.__class__)[7:-2] + " packets must have length atleast 16")
if ("__len__" in dir(packet) and len(packet) < 16) or\
("__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)
@ -306,8 +319,9 @@ class SetTime(Command):
Set time on device. All fields refer to time in the GMT time zone.
"""
NUMBER = 0x104
timezone = field(start=0x10, fmt=DWORD) #: -time.timezone with negative numbers encoded as int(0xffffffff +1 -time.timezone/60.)
# -time.timezone with negative numbers encoded
# as int(0xffffffff +1 -time.timezone/60.)
timezone = field(start=0x10, fmt=DWORD)
year = field(start=0x14, fmt=DWORD) #: year e.g. 2006
month = field(start=0x18, fmt=DWORD) #: month 1-12
day = field(start=0x1c, fmt=DWORD) #: day 1-31
@ -329,7 +343,9 @@ class SetTime(Command):
self.day = t[2]
self.hour = t[3]
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):
@ -337,7 +353,8 @@ class ShortCommand(Command):
""" A L{Command} whoose data section is 4 bytes long """
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):
"""
@ -354,38 +371,50 @@ class ShortCommand(Command):
class DirRead(ShortCommand):
""" The command that asks the device to send the next item in the list """
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 """
ShortCommand.__init__(self, number=DirRead.NUMBER, type=0x01, command=id)
ShortCommand.__init__(self, number=DirRead.NUMBER, type=0x01, \
command=_id)
class DirClose(ShortCommand):
""" Close a previously opened directory """
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 """
ShortCommand.__init__(self, number=DirClose.NUMBER, type=0x01, command=id)
ShortCommand.__init__(self, number=DirClose.NUMBER, type=0x01,
command=_id)
class EndSession(ShortCommand):
""" Ask device to change status to 'USB connected' i.e., tell the device that the present sequence of commands is complete """
"""
Ask device to change status to 'USB connected' i.e., tell the
device that the present sequence of commands is complete
"""
NUMBER = 0x1 #: Command number
def __init__(self):
ShortCommand.__init__(self, number=EndSession.NUMBER, type=0x01, command=0x00)
ShortCommand.__init__(self, \
number=EndSession.NUMBER, type=0x01, command=0x00)
class GetUSBProtocolVersion(ShortCommand):
""" Get USB Protocol version used by device """
NUMBER = 0x0 #: Command number
def __init__(self):
ShortCommand.__init__(self, number=GetUSBProtocolVersion.NUMBER, type=0x01, command=0x00)
ShortCommand.__init__(self, \
number=GetUSBProtocolVersion.NUMBER, \
type=0x01, command=0x00)
class SetBulkSize(ShortCommand):
""" Set size for bulk transfers in this session """
NUMBER = 0x107 #: Command number
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):
""" Unlock the device """
NUMBER = 0x106 #: Command number
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):
@ -410,7 +439,8 @@ class LongCommand(Command):
doc = \
"""
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):
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)
start += struct.calcsize(DWORD)
return property(**locals())
return property(doc=doc, fget=fget, fset=fset)
class PathCommand(Command):
""" Abstract class that defines structure common to all path related commands. """
@ -452,7 +482,8 @@ class FreeSpaceQuery(ShortCommand):
c = 0
if where.startswith('a:'): c = 1
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):
""" Create a directory """
@ -470,12 +501,16 @@ class DirOpen(PathCommand):
class AcknowledgeBulkRead(LongCommand):
""" Must be sent to device after a bulk read """
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):
""" The command used to ask for device information """
NUMBER=0x0101 #: Command number
NUMBER = 0x101 #: Command number
def __init__(self):
Command.__init__(self, 16)
self.number = DeviceInfoQuery.NUMBER
@ -484,8 +519,9 @@ class DeviceInfoQuery(Command):
class FileClose(ShortCommand):
""" File close command """
NUMBER = 0x11 #: Command number
def __init__(self, id):
ShortCommand.__init__(self, number=FileClose.NUMBER, type=0x01, command=id)
def __init__(self, _id):
ShortCommand.__init__(self, number=FileClose.NUMBER, \
type=0x01, command=_id)
class FileCreate(PathCommand):
""" Create a file """
@ -520,14 +556,17 @@ class FileOpen(PathCommand):
@apply
def mode():
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):
return self.unpack(start=16, fmt=DWORD)[0]
def fset(self, val):
self.pack(val, start=16, fmt=DWORD)
return property(**locals())
return property(doc=doc, fget=fget, fset=fset)
class FileIO(Command):
@ -537,9 +576,9 @@ class FileIO(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
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}
@param offset: Position in file at which to read
@type offset: C{unsigned long long}
@ -551,7 +590,7 @@ class FileIO(Command):
self.number = mode
self.type = 0x01
self.length = 16
self.id = id
self.id = _id
self.offset = offset
self.size = size
@ -572,81 +611,100 @@ class Response(Command):
"""
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
rnumber = field(start=16, fmt=DWORD) #: Response number, the command number of a command packet sent sometime before this packet was received
code = field(start=20, fmt=DWORD) #: Used to indicate error conditions. A value of 0 means there was no error
data_size = field(start=28, fmt=DWORD) #: Used to indicate the size of the next bulk read
# Response number, the command number of a command
# packet sent sometime before this packet was received
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):
""" C{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)
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
def data():
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):
return self.unpack(start=20, fmt="<III")
def fset(self, val):
self.pack(val, start=20, fmt="<III")
return property(**locals())
return property(doc=doc, fget=fget, fset=fset)
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_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
PATH_NOT_FOUND = 0xffffffd7 #: Queried path is not found
@apply
def is_file():
""" True iff queried path is a file """
doc = """ True iff queried path is a file """
def fget(self):
return self.code == ListResponse.IS_FILE
return property(**locals())
return property(doc=doc, fget=fget)
@apply
def is_invalid():
""" True iff queried path is invalid """
doc = """ True iff queried path is invalid """
def fget(self):
return self.code == ListResponse.IS_INVALID
return property(**locals())
return property(doc=doc, fget=fget)
@apply
def path_not_found():
""" True iff queried path is not found """
doc = """ True iff queried path is not found """
def fget(self):
return self.code == ListResponse.PATH_NOT_FOUND
return property(**locals())
return property(doc=doc, fget=fget)
@apply
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):
return self.code == ListResponse.IS_UNMOUNTED
return property(**locals())
return property(doc=doc, fget=fget)
@apply
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):
return self.code == ListResponse.IS_EOL
return property(**locals())
return property(doc=doc, fget=fget)
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
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} """
if "__len__" in dir(packet):
if len(packet) < 16 :
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
raise PacketError(str(self.__class__)[7:-2] + \
" packets must have a length of atleast 16 bytes")
elif packet < 16:
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
raise PacketError(str(self.__class__)[7:-2] + \
" packets must have a length of atleast 16 bytes")
TransferBuffer.__init__(self, packet)
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_type = field(start=24, fmt=DWORD) #: 1 == file, 2 == dir
ctime = field(start=28, fmt=DWORD) #: Creation 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
def is_dir():
@ -679,11 +743,13 @@ class FileProperties(Answer):
return (self.file_type == 2)
def fset(self, val):
if val: val = 2
else: val = 1
if val:
val = 2
else:
val = 1
self.file_type = val
return property(**locals())
return property(doc=doc, fget=fget, fset=fset)
@apply
@ -694,14 +760,17 @@ class FileProperties(Answer):
return self.unpack(start=36, fmt=DWORD)[0] != 0
def fset(self, val):
if val: val = 4
else: val = 0
if val:
val = 4
else:
val = 0
self.pack(val, start=36, fmt=DWORD)
return property(**locals())
return property(doc=doc, fget=fget, fset=fset)
class USBProtocolVersion(Answer):
""" Get USB Protocol version """
version = field(start=16, fmt=DDWORD)
class IdAnswer(Answer):
@ -711,7 +780,11 @@ class IdAnswer(Answer):
@apply
def id():
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):
return self.unpack(start=16, fmt=DWORD)[0]
@ -719,7 +792,7 @@ class IdAnswer(Answer):
def fset(self, val):
self.pack(val, start=16, fmt=DWORD)
return property(**locals())
return property(doc=doc, fget=fget, fset=fset)
class DeviceInfo(Answer):
""" Defines the structure of the packet containing information about the device """
@ -731,7 +804,8 @@ class DeviceInfo(Answer):
class TotalSpaceAnswer(Answer):
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):
SIZE = 24
@ -746,7 +820,10 @@ class ListAnswer(Answer):
@apply
def is_dir():
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):
return (self.unpack(start=16, fmt=DWORD)[0] == 2)
@ -756,6 +833,6 @@ class ListAnswer(Answer):
else: val = 1
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>