From 6010e9b2cada58f9e49859b69a25a84abb40f151 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Dec 2006 01:50:00 +0000 Subject: [PATCH] Raising pylint scores for libprs500/*.py --- libprs500/books.py | 290 ++++---- libprs500/communicate.py | 1403 +++++++++++++++++++++----------------- libprs500/errors.py | 70 +- libprs500/gui/main.py | 3 +- libprs500/prstypes.py | 1325 ++++++++++++++++++----------------- prs-500.e4p | 239 ------- 6 files changed, 1691 insertions(+), 1639 deletions(-) delete mode 100644 prs-500.e4p diff --git a/libprs500/books.py b/libprs500/books.py index 65ff4a25bd..a66ed92d19 100644 --- a/libprs500/books.py +++ b/libprs500/books.py @@ -12,152 +12,200 @@ ## 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): - def __init__(self, attr, formatter=None, setter=None): - self.attr = attr - self.formatter = formatter - self.setter = setter - - 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() - - def __set__(self, obj, val): - val = self.setter(val) if self.setter else val - obj.elem.setAttribute(self.attr, str(val)) + """ Represents metadata stored as an attribute """ + def __init__(self, attr, formatter=None, setter=None): + self.attr = attr + self.formatter = formatter + self.setter = setter + + 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() + + 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(): - def fget(self): - th = self.elem.getElementsByTagName(self.prefix + "thumbnail") - if len(th): - for n in th[0].childNodes: - if n.nodeType == n.ELEMENT_NODE: - th = n - break - rc = "" - for node in th.childNodes: - if node.nodeType == node.TEXT_NODE: rc += node.data - return decode(rc) - return property(**locals()) - + 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): + for n in th[0].childNodes: + if n.nodeType == n.ELEMENT_NODE: + th = n + break + rc = "" + for node in th.childNodes: + if node.nodeType == node.TEXT_NODE: + rc += node.data + return decode(rc) + 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 - self.prefix = prefix - self.root = root - + self.elem = node + self.prefix = prefix + self.root = root + def __repr__(self): - return self.title + " by " + self.author+ " at " + self.path - + return self.title + " by " + self.author+ " at " + self.path + def __str__(self): - return self.__repr__() - - -def fix_ids(media, cache): - id = 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 len(cache): - for child in cache.root.childNodes: - 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)) + return self.__repr__() + + +def fix_ids(media, cache): + """ + 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(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"): + child.setAttribute("sourceid", str(mmaxid+1)) + child.setAttribute("id", str(cid)) + cid += 1 + media.document.documentElement.setAttribute("nextID", str(cid)) class BookList(list): - __getslice__ = None - __setslice__ = None - - def __init__(self, prefix="xs1:", root="/Data/media/", file=None): - list.__init__(self) - if file: - 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)) - - def max_id(self): - id = -1 - for child in self.root.childNodes: - if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"): - c = int(child.getAttribute("id")) - if c > id: id = c - return id - - 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 """ - ans = False - for book in self: - if book.id == id: - ans = True - break - return ans - - def delete_book(self, id): - node = None - for book in self: - if book.id == id: - node = book - self.remove(book) - break - node.elem.parentNode.removeChild(node.elem) - node.elem.unlink() - - def add_book(self, info, name, size, ctime): - node = self.document.createElement(self.prefix + "text") - mime = MIME_MAP[name[name.rfind(".")+1:]] - id = 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)} - for attr in attrs.keys(): - node.setAttributeNode(self.document.createAttribute(attr)) - node.setAttribute(attr, attrs[attr]) - w, h, data = info["cover"] - if data: - th = self.document.createElement(self.prefix + "thumbnail") - th.setAttribute("width", str(w)) - th.setAttribute("height", str(h)) - jpeg = self.document.createElement(self.prefix + "jpeg") - jpeg.appendChild(self.document.createTextNode(encode(data))) - th.appendChild(jpeg) - node.appendChild(th) - self.root.appendChild(node) - book = Book(node, root=self.proot, prefix=self.prefix) - book.datetime = ctime - self.append(book) + """ + A list of L{Book}s. Created from an XML file. Can write list + to an XML file. + """ + __getslice__ = None + __setslice__ = None - def write(self, stream): - PrettyPrint(self.document, stream) + def __init__(self, prefix="xs1:", root="/Data/media/", sfile=None): + list.__init__(self) + if sfile: + self.prefix = prefix + self.proot = root + 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): + """ Highest id in underlying XML file """ + cid = -1 + for child in self.root.childNodes: + if child.nodeType == child.ELEMENT_NODE and \ + child.hasAttribute("id"): + c = int(child.getAttribute("id")) + if c > cid: + cid = c + return cid + + 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 == cid: + ans = True + break + return ans + + def delete_book(self, cid): + """ Remove DOM node corresponding to book with C{id == cid}.""" + node = None + for book in self: + if book.id == cid: + node = book + self.remove(book) + break + node.elem.parentNode.removeChild(node.elem) + 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:]] + 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(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]) + w, h, data = info["cover"] + if data: + th = self.document.createElement(self.prefix + "thumbnail") + th.setAttribute("width", str(w)) + th.setAttribute("height", str(h)) + jpeg = self.document.createElement(self.prefix + "jpeg") + jpeg.appendChild(self.document.createTextNode(encode(data))) + th.appendChild(jpeg) + node.appendChild(th) + self.root.appendChild(node) + book = Book(node, root=self.proot, prefix=self.prefix) + book.datetime = ctime + self.append(book) + + def write(self, stream): + """ Write XML representation of DOM tree to C{stream} """ + PrettyPrint(self.document, stream) diff --git a/libprs500/communicate.py b/libprs500/communicate.py index fc0a72ef2d..40bc1274da 100755 --- a/libprs500/communicate.py +++ b/libprs500/communicate.py @@ -36,655 +36,818 @@ ### 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__ - print >>stream, packet - print >>stream, "--" +# 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] - self.path = path #: Path to self - self.name = path[path.rfind("/")+1:].rstrip() #: Name of self - - def __repr__(self): - """ Return path to self """ - return "File:"+self.path - - def __str__(self): - return self.name + """ + 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 + + def __repr__(self): + """ Return path to self """ + return "File:" + self.path + + def __str__(self): + return self.name class DeviceDescriptor: - """ - Describes a USB device. - - A description is composed of the Vendor Id, Product Id and Interface Id. - See the U{USB spec} - """ - - def __init__(self, vendor_id, product_id, interface_id) : - self.vendor_id = vendor_id - self.product_id = product_id - self.interface_id = interface_id + """ + Describes a USB device. - def getDevice(self) : + A description is composed of the Vendor Id, Product Id and Interface Id. + See the U{USB spec} """ - Return the device corresponding to the device descriptor if it is - available on a USB bus. Otherwise, return None. Note that the - returned device has yet to be claimed or opened. + + def __init__(self, vendor_id, product_id, interface_id) : + self.vendor_id = vendor_id + self.product_id = product_id + self.interface_id = interface_id + + 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 + returned device has yet to be claimed or opened. + """ + buses = usb.busses() + for bus in buses : + for device in bus.devices : + if device.idVendor == self.vendor_id : + if device.idProduct == self.product_id : + return device + return None + + +class PRS500Device(Device): + """ - buses = usb.busses() - for bus in buses : - for device in bus.devices : - if device.idVendor == self.vendor_id : - if device.idProduct == self.product_id : - return device - return None + Contains the logic for performing various tasks on the reader. - -class PRS500Device(object): - - """ - Contains the logic for performing various tasks on the reader. - - The implemented tasks are: + The implemented tasks are: 0. Getting information about the device 1. Getting a file from the device 2. Listing of directories. See the C{list} method. - """ - - SONY_VENDOR_ID = 0x054c #: SONY Vendor Id - PRS500_PRODUCT_ID = 0x029b #: Product Id for the PRS-500 - 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 - - device_descriptor = DeviceDescriptor(SONY_VENDOR_ID, PRS500_PRODUCT_ID, PRS500_INTERFACE_ID) - - @classmethod - def signature(cls): - """ Return a two element tuple (vendor id, product id) """ - return (cls.SONY_VENDOR_ID, cls.PRS500_PRODUCT_ID ) + """ - def safe(func): - """ - Decorator that wraps a call to C{func} to ensure that exceptions are handled correctly. - It also calls L{open} to claim the interface and initialize the Reader if needed. - - As a convenience, C{safe} automatically sends the a L{EndSession} after calling func, unless func has - a keyword argument named C{end_session} set to C{False}. - - An L{ArgumentError} will cause the L{EndSession} command to 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 - """ - def run_session(*args, **kwargs): - dev = args[0] - res = None - try: - 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()) - raise - except usb.USBError, e: - if "No such device" in str(e): - raise DeviceError() - elif "Connection timed out" in str(e): - dev.close() - raise TimeoutError(func.__name__) - elif "Protocol error" in str(e): - dev.close() - 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()) - return res - - return run_session + SONY_VENDOR_ID = 0x054c #: SONY Vendor Id + PRS500_PRODUCT_ID = 0x029b #: Product Id for the PRS-500 + 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 + # 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 - 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 - """ - 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 - self.report_progress = report_progress - - def reconnect(self): - self.device = self.device_descriptor.getDevice() - 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 - getting L{DeviceError}. - """ - return cls.device_descriptor.getDevice() != 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)) + device_descriptor = DeviceDescriptor(SONY_VENDOR_ID, \ + PRS500_PRODUCT_ID, PRS500_INTERFACE_ID) + + @classmethod + def signature(cls): + """ Return a two element tuple (vendor id, product id) """ + return (cls.SONY_VENDOR_ID, cls.PRS500_PRODUCT_ID ) + + def safe(func): + """ + Decorator that wraps a call to C{func} to ensure that + exceptions are handled correctly. It also calls L{open} to claim + the interface and initialize the Reader if needed. + + As a convenience, C{safe} automatically sends the a + L{EndSession} after calling func, unless func has + a keyword argument named C{end_session} set to C{False}. + + An L{ArgumentError} will cause the L{EndSession} command to + 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 + """ + def run_session(*args, **kwargs): + dev = args[0] + res = None + try: + 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()) + raise + except usb.USBError, err: + if "No such device" in str(err): + raise DeviceError() + elif "Connection timed out" in str(err): + dev.close() + raise TimeoutError(func.__name__) + elif "Protocol error" in str(err): + dev.close() + 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()) + return res + + return run_session + + 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 + """ + 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): + """ 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 + getting L{DeviceError}. + """ + return cls.device_descriptor.get_device() != None - 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. - @todo: Implement unlocking of the device - @todo: Check this on Mac OSX - """ - self.device = self.device_descriptor.getDevice() - 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 - 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.") - version = self._bulk_read(24, data_type=USBProtocolVersion)[0].version - if version not in KNOWN_USB_PROTOCOL_VERSIONS: - print >>sys.stderr, "WARNING: Usb protocol version " + hex(version) + " is unknown" - res = self._send_validated_command(SetBulkSize(size=0x028000)) - if res.code != 0: raise ProtocolError("Unable to set bulk size.") - self._send_validated_command(UnlockDevice(key=0x312d)) - if res.code != 0: - raise ProtocolError("Unlocking of device not implemented. Remove locking and retry.") - res = self._send_validated_command(SetTime()) - if res.code != 0: - raise ProtocolError("Could not set time on device") - - def close(self): - """ Release device interface """ - try: - self.handle.reset() - self.handle.releaseInterface() - except: pass - self.handle, self.device = None, None - - def _send_command(self, command, response_type=Response, timeout=1000): - """ - Send L{command} to device and return its L{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. - """ - if self._log_packets: _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") - return response - - def _send_validated_command(self, command, cnumber=None, response_type=Response, timeout=1000): - """ - Wrapper around L{_send_command} that checks if the C{Response.rnumber == cnumber or 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) - 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. - """ - def bulk_write_packet(packet): - self.handle.bulkWrite(PRS500Device.PRS500_BULK_OUT_EP, packet) - if self._log_packets: _log_packet(Answer(packet), "Answer h->d") - - bytes_left = len(data) - if bytes_left + 16 <= packet_size: - packet_size = bytes_left +16 - first_packet = Answer(bytes_left+16) - first_packet[16:] = data - first_packet.length = len(data) - else: - first_packet = Answer(packet_size) - first_packet[16:] = data[0:packet_size-16] - first_packet.length = packet_size-16 - first_packet.number = 0x10005 - bulk_write_packet(first_packet) - pos = first_packet.length - bytes_left -= first_packet.length - while bytes_left > 0: - endpos = pos + packet_size if pos + packet_size <= len(data) else len(data) - bulk_write_packet(data[pos:endpos]) - bytes_left -= endpos - pos - pos = endpos - res = Response(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=5000)) - if self._log_packets: _log_packet(res, "Response") - if res.rnumber != 0x10005 or res.code != 0: - raise ProtocolError("Sending via Bulk Transfer failed with response:\n"+str(res)) - if res.data_size != len(data): - raise ProtocolError("Unable to transfer all data to device. Response packet:\n"+str(res)) - - - 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 - @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") - return data - - bytes_left = bytes - packets = [] - while bytes_left > 0: - 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) - return packets - - @safe - def get_device_information(self, end_session=True): - """ - 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) - - @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") - if res.code not in (0, PathResponseCodes.IS_FILE): - 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}. - - The data is fetched in chunks of size S{<=} 32K. Each chunk is make of packets of size S{<=} 4K. See L{FileOpen}, - L{FileRead} and L{FileClose} for details on the command packets used. - - @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 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 - 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 res.code != 0: - self._send_validated_command(FileClose(id)) - raise ProtocolError("Error while reading from " + path + ". Response code: " + hex(res.code)) - packets = self._bulk_read(chunk_size+16, command_number=FileIO.RNUMBER, packet_size=4096) - try: - array('B', packets[0][16:]).tofile(outfile) # The first 16 bytes are meta information on the packet stream - for i in range(1, len(packets)): - 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)) - 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 - - - - @safe - def list(self, path, recurse=False, end_session=True): - """ - Return a listing of path. See the code for details. See L{DirOpen}, - L{DirRead} and L{DirClose} for details on the command packets used. - - @type path: string - @param path: The path to list - @type recurse: boolean - @param recurse: If true do a recursive listing - @return: A list of tuples. The first element of each tuple is a path. The second element is a list of L{Files}. - 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 - files = [] - candidate = self.path_properties(path, end_session=False) - if not candidate.is_dir: - path = path[:-1] - data = self.path_properties(path, end_session=False) - files = [ File((path, data)) ] - else: - # Get query ID used to ask for next element in list - res = self._send_validated_command(DirOpen(path)) - if res.code != 0: - raise PathError("Unable to open directory " + path + " for reading. Response code: " + hex(res.code)) - id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id - # Create command asking for next element in list - next = DirRead(id) - items = [] - while True: - res = self._send_validated_command(next, response_type=ListResponse) - size = res.data_size + 16 - data = self._bulk_read(size, data_type=ListAnswer, command_number=DirRead.NUMBER)[0] - # path_not_found seems to happen if the usb server doesn't have the permissions to access the directory - if res.is_eol or res.path_not_found: break - elif res.code != 0: - raise ProtocolError("Unknown error occured while reading contents of directory " + path + ". Response code: " + haex(res.code)) - items.append(data.name) - self._send_validated_command(DirClose(id)) # Ignore res.code as we cant do anything if close fails - for item in items: - ipath = path + item - data = self.path_properties(ipath, end_session=False) - files.append( File( (ipath, data) ) ) - files.sort() - return files - - files = _list(path) - dirs = [(path, files)] - - 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 - def total_space(self, end_session=True): - """ - Get total space available on the mountpoints: - 1. Main memory - 2. Memory Stick - 3. SD Card - - @return: A 3 element list with total space in bytes of (1, 2, 3) - """ - data = [] - for path in ("/Data/", "a:/", "b:/"): - res = self._send_validated_command(TotalSpaceQuery(path),timeout=5000) # Timeout needs to be increased as it takes time to read card - buffer_size = 16 + res.data[2] - pkt = self._bulk_read(buffer_size, data_type=TotalSpaceAnswer, command_number=TotalSpaceQuery.NUMBER)[0] - data.append( pkt.total ) - return data - - @safe - def free_space(self, end_session=True): - """ - Get free space available on the mountpoints: - 1. Main memory - 2. Memory Stick - 3. SD Card - - @return: A 3 element list with free space in bytes of (1, 2, 3) - """ - data = [] - for path in ("/", "a:/", "b:/"): - 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] - data.append( pkt.free ) - return data - - def _exists(self, path): - """ Return (True, FileProperties) if path exists or (False, None) otherwise """ - dest = None - try: - dest = self.path_properties(path, end_session=False) - except PathError, e: - if "does not exist" in str(e) or "not mounted" in str(e): return (False, None) - else: raise - return (True, dest) - - @safe - def touch(self, path, end_session=True): - """ - Create a file at path - @todo: Update file modification time if it exists. 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: - raise PathError("Cannot touch directories") - if not exists: - res = self._send_validated_command(FileCreate(path)) - if res.code != 0: - raise PathError("Could not create file " + path + ". Response code: " + str(hex(res.code))) - - @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 replace_file: If True and path points to a file that already exists, it is replaced - """ - pos = infile.tell() - infile.seek(0,2) - bytes = infile.tell() - pos - start_pos = pos - infile.seek(pos) - exists, dest = self._exists(path) - if exists: - if dest.is_dir: - 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: - 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)) - 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 - - while data_left: - data = array('B') - try: - data.fromfile(infile, chunk_size) - except EOFError: - data_left = False - res = self._send_validated_command(FileIO(id, pos, len(data), mode=FileIO.WNUMBER)) - if res.code != 0: - raise ProtocolError("Unable to write to " + path + ". Response code: " + hex(res.code)) - self._bulk_write(data) - pos += len(data) - 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") - - @safe - def del_file(self, path, end_session=True): - data = self.path_properties(path, end_session=False) - if data.is_dir: raise PathError("Cannot delete directories") - res = self._send_validated_command(FileDelete(path), response_type=ListResponse) - if res.code != 0: - raise ProtocolError("Unable to delete " + path + " with response:\n" + str(res)) - - @safe - def mkdir(self, path, end_session=True): - if not path.endswith("/"): path += "/" - error_prefix = "Cannot create directory " + path - res = self._send_validated_command(DirCreate(path)).data[0] - if res == 0xffffffcc: - raise PathError(error_prefix + " as it already exists") - elif res == PathResponseCodes.NOT_FOUND: - raise PathError(error_prefix + " as " + path[0:path[:-1].rfind("/")] + " does not exist ") - elif res == PathResponseCodes.INVALID: - raise PathError(error_prefix + " as " + path + " is invalid") - elif res != 0: - raise PathError(error_prefix + ". Response code: " + hex(res)) - - @safe - def rm(self, path, end_session=True): - """ Delete path from device if it is a file or an empty directory """ - dir = self.path_properties(path, end_session=False) - if not dir.is_dir: - self.del_file(path, end_session=False) - else: - if not path.endswith("/"): path += "/" - res = self._send_validated_command(DirDelete(path)) - if res.code == PathResponseCodes.HAS_CHILDREN: - raise PathError("Cannot delete directory " + path + " as it is not empty") - if res.code != 0: - raise ProtocolError("Failed to delete directory " + path + ". Response code: " + hex(res.code)) - - @safe - def card(self, end_session=True): - card = None - 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 - - @return: L{BookList} - """ - root = "/Data/media/" - prefix = "xs1:" - file = TemporaryFile() - if oncard: - prefix="" - try: - self.get_file("a:"+self.CACHE_XML, file, end_session=False) - root = "a:/" - except PathError: + 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. + + @todo: Implement unlocking of the device + @todo: Check this on Mac OSX + """ + self.device = self.device_descriptor.get_device() + if not self.device: + raise DeviceError() try: - self.get_file("b:"+self.CACHE_XML, file, 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) - - @safe - def add_book(self, infile, name, info, booklists, oncard=False, sync_booklists=False, end_session=True): - """ - Add a book to the device. If oncard is True then the book is copied to the card rather than main memory. - - @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 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? - """ - infile.seek(0,2) - size = infile.tell() - infile.seek(0) - card = self.card(end_session=False) - 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") - prefix = "/Data/media/" - 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) - fix_ids(booklists[0], booklists[1]) - if sync_booklists: - self.upload_book_list(booklists[0], end_session=False) - if len(booklists[1]): - self.upload_book_list(booklists[1], end_session=False) - - @safe - def upload_book_list(self, booklist, end_session=True): - 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") - path = card + self.CACHE_XML - f = TemporaryFile() - booklist.write(f) - f.seek(0) - self.put_file(f, path, replace_file=True, end_session=False) - f.close() - + self.handle = self.device.open() + self.handle.claimInterface(self.device_descriptor.interface_id) + except usb.USBError, err: + print >> sys.stderr, err + raise DeviceBusy() + # 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)) + if res.code != 0: + raise ProtocolError("Unlocking of device not implemented. Remove locking and retry.") + res = self.send_validated_command(SetTime()) + if res.code != 0: + raise ProtocolError("Could not set time on device") + + def close(self): + """ Release device interface """ + try: + self.handle.reset() + self.handle.releaseInterface() + except Exception, err: + print >> sys.stderr, err + self.handle, self.device = None, None + + def _send_command(self, command, response_type=Response, timeout=1000): + """ + Send L{command} to device and return its L{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. + """ + 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(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): + """ + 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) + 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. + """ + def bulk_write_packet(packet): + self.handle.bulkWrite(PRS500Device.PRS500_BULK_OUT_EP, packet) + if self.log_packets: + self.log_packet(Answer(packet), "Answer h->d") + + bytes_left = len(data) + if bytes_left + 16 <= packet_size: + packet_size = bytes_left +16 + first_packet = Answer(bytes_left+16) + first_packet[16:] = data + first_packet.length = len(data) + else: + first_packet = Answer(packet_size) + first_packet[16:] = data[0:packet_size-16] + first_packet.length = packet_size-16 + first_packet.number = 0x10005 + bulk_write_packet(first_packet) + pos = first_packet.length + bytes_left -= first_packet.length + while bytes_left > 0: + endpos = pos + packet_size if pos + packet_size <= len(data) \ + else len(data) + bulk_write_packet(data[pos:endpos]) + bytes_left -= endpos - pos + pos = endpos + res = Response(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, \ + timeout=5000)) + if self.log_packets: + 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)) + if res.data_size != len(data): + 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): + """ + 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: + 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 + 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) + return packets + + @safe + def get_device_information(self, end_session=True): + """ + 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) + + @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") + if res.code not in (0, PathResponseCodes.IS_FILE): + 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. + + 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 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 + 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 res.code != 0: + self.send_validated_command(FileClose(id)) + raise ProtocolError("Error while reading from " + path + \ + ". Response code: " + hex(res.code)) + packets = self._bulk_read(chunk_size+16, \ + command_number=FileIO.RNUMBER, packet_size=4096) + try: + # 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, 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 + + @safe + def list(self, path, recurse=False, end_session=True): + """ + Return a listing of path. See the code for details. See L{DirOpen}, + L{DirRead} and L{DirClose} for details on the command packets used. + + @type path: string + @param path: The path to list + @type recurse: boolean + @param recurse: If true do a recursive listing + @return: A list of tuples. The first element of each tuple is a path. + The second element is a list of L{Files}. + 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 + files = [] + candidate = self.path_properties(path, end_session=False) + if not candidate.is_dir: + path = path[:-1] + data = self.path_properties(path, end_session=False) + files = [ File((path, data)) ] + else: + # Get query ID used to ask for next element in list + res = self.send_validated_command(DirOpen(path)) + if res.code != 0: + raise PathError("Unable to open directory " + path + \ + " for reading. Response code: " + hex(res.code)) + _id = self._bulk_read(0x14, data_type=IdAnswer, \ + command_number=DirOpen.NUMBER)[0].id + # Create command asking for next element in list + next = DirRead(_id) + items = [] + while True: + res = self.send_validated_command(next, response_type=ListResponse) + size = res.data_size + 16 + data = self._bulk_read(size, data_type=ListAnswer, \ + command_number=DirRead.NUMBER)[0] + # path_not_found seems to happen if the usb server + # doesn't have the permissions to access the directory + if res.is_eol or res.path_not_found: + break + elif res.code != 0: + raise ProtocolError("Unknown error occured while "+\ + "reading contents of directory " + path + \ + ". Response code: " + hex(res.code)) + items.append(data.name) + self.send_validated_command(DirClose(_id)) + # Ignore res.code as we cant do anything if close fails + for item in items: + ipath = path + item + data = self.path_properties(ipath, end_session=False) + files.append( File( (ipath, data) ) ) + files.sort() + return files + + files = _list(path) + dirs = [(path, files)] + + 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 + def total_space(self, end_session=True): + """ + Get total space available on the mountpoints: + 1. Main memory + 2. Memory Stick + 3. SD Card + + @return: A 3 element list with total space in bytes of (1, 2, 3) + """ + data = [] + for path in ("/Data/", "a:/", "b:/"): + # Timeout needs to be increased as it takes time to read card + res = self.send_validated_command(TotalSpaceQuery(path), \ + timeout=5000) + buffer_size = 16 + res.data[2] + pkt = self._bulk_read(buffer_size, data_type=TotalSpaceAnswer, \ + command_number=TotalSpaceQuery.NUMBER)[0] + data.append( pkt.total ) + return data + + @safe + def free_space(self, end_session=True): + """ + Get free space available on the mountpoints: + 1. Main memory + 2. Memory Stick + 3. SD Card + + @return: A 3 element list with free space in bytes of (1, 2, 3) + """ + data = [] + for path in ("/", "a:/", "b:/"): + # Timeout needs to be increased as it takes time to read card + self.send_validated_command(FreeSpaceQuery(path), \ + timeout=5000) + pkt = self._bulk_read(FreeSpaceAnswer.SIZE, \ + data_type=FreeSpaceAnswer, \ + command_number=FreeSpaceQuery.NUMBER)[0] + data.append( pkt.free ) + return data + + def _exists(self, path): + """ Return (True, FileProperties) if path exists or (False, None) otherwise """ + dest = None + try: + dest = self.path_properties(path, end_session=False) + except PathError, err: + if "does not exist" in str(err) or "not mounted" in str(err): + return (False, None) + else: raise + return (True, dest) + + @safe + def touch(self, path, end_session=True): + """ + Create a file at path + @todo: Update file modification time if it exists. + 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: + raise PathError("Cannot touch directories") + if not exists: + res = self.send_validated_command(FileCreate(path)) + if res.code != 0: + raise PathError("Could not create file " + path + \ + ". Response code: " + str(hex(res.code))) + + @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 replace_file: If True and path points to a file that already exists, it is replaced + """ + pos = infile.tell() + infile.seek(0, 2) + bytes = infile.tell() - pos + start_pos = pos + infile.seek(pos) + exists, dest = self._exists(path) + if exists: + if dest.is_dir: + 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: + 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)) + 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 + + while data_left: + data = array('B') + try: + data.fromfile(infile, chunk_size) + except EOFError: + data_left = False + res = self.send_validated_command(FileIO(_id, pos, len(data), \ + mode=FileIO.WNUMBER)) + if res.code != 0: + raise ProtocolError("Unable to write to " + \ + path + ". Response code: " + hex(res.code)) + self._bulk_write(data) + pos += len(data) + 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") + + @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 res.code != 0: + raise ProtocolError("Unable to delete " + path + \ + " with response:\n" + str(res)) + + @safe + def mkdir(self, path, end_session=True): + """ Make directory """ + if not path.endswith("/"): + path += "/" + error_prefix = "Cannot create directory " + path + res = self.send_validated_command(DirCreate(path)).data[0] + if res == 0xffffffcc: + raise PathError(error_prefix + " as it already exists") + elif res == PathResponseCodes.NOT_FOUND: + raise PathError(error_prefix + " as " + \ + path[0:path[:-1].rfind("/")] + " does not exist ") + elif res == PathResponseCodes.INVALID: + raise PathError(error_prefix + " as " + path + " is invalid") + elif res != 0: + raise PathError(error_prefix + ". Response code: " + hex(res)) + + @safe + def rm(self, path, end_session=True): + """ Delete path from device if it is a file or an empty directory """ + dir = self.path_properties(path, end_session=False) + if not dir.is_dir: + self.del_file(path, end_session=False) + else: + if not path.endswith("/"): + path += "/" + res = self.send_validated_command(DirDelete(path)) + if res.code == PathResponseCodes.HAS_CHILDREN: + raise PathError("Cannot delete directory " + path + \ + " as it is not empty") + if res.code != 0: + raise ProtocolError("Failed to delete directory " + path + \ + ". Response code: " + hex(res.code)) + + @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:" + return card + + @safe + def books(self, oncard=False, end_session=True): + """ + Return a list of ebooks on the device. + @param oncard: If True return a list of ebooks on the storage card, + otherwise return list of ebooks in main memory of device + + @return: L{BookList} + """ + root = "/Data/media/" + prefix = "xs1:" + tfile = TemporaryFile() + if oncard: + prefix = "" + try: + self.get_file("a:"+self.CACHE_XML, tfile, end_session=False) + root = "a:/" + except PathError: + try: + self.get_file("b:"+self.CACHE_XML, tfile, end_session=False) + root = "b:/" + except PathError: pass + 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): + """ + 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 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? + """ + infile.seek(0, 2) + size = infile.tell() + infile.seek(0) + card = self.card(end_session=False) + 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") + prefix = "/Data/media/" + 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 + 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) + if len(booklists[1]): + self.upload_book_list(booklists[1], end_session=False) + + @safe + def upload_book_list(self, booklist, end_session=True): + 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") + path = card + self.CACHE_XML + f = TemporaryFile() + booklist.write(f) + f.seek(0) + self.put_file(f, path, replace_file=True, end_session=False) + f.close() + diff --git a/libprs500/errors.py b/libprs500/errors.py index 35fea0d905..27b2e24752 100644 --- a/libprs500/errors.py +++ b/libprs500/errors.py @@ -17,53 +17,55 @@ 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 """ - def __init__(self, msg): - Exception.__init__(self, msg) - + """ The base class for all exceptions in this package """ + def __init__(self, msg): + Exception.__init__(self, msg) + 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) + """ 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) class DeviceError(ProtocolError): - """ Raised when device is not found """ - def __init__(self): - ProtocolError.__init__(self, "Unable to find SONY Reader. Is it connected?") - + """ Raised when device is not found """ + def __init__(self): + ProtocolError.__init__(self, \ + "Unable to find SONY Reader. Is it connected?") + class DeviceBusy(ProtocolError): - """ Raised when device is busy """ - def __init__(self): - ProtocolError.__init__(self, "Device is in use by another application") - + """ Raised when device is busy """ + def __init__(self): + ProtocolError.__init__(self, "Device is in use by another application") + class PacketError(ProtocolError): - """ Errors with creating/interpreting packets """ - + """ Errors with creating/interpreting packets """ + class FreeSpaceError(ProtocolError): - """ Errors caused when trying to put files onto an overcrowded device """ - + """ Errors caused when trying to put files onto an overcrowded device """ + class ArgumentError(ProtocolError): - """ Errors caused by invalid arguments to a public interface function """ - + """ Errors caused by invalid arguments to a public interface function """ + class PathError(ArgumentError): - """ When a user supplies an incorrect/invalid path """ + """ When a user supplies an incorrect/invalid path """ class ControlError(ProtocolError): - """ Errors in Command/Response pairs while communicating with the device """ - def __init__(self, query=None, response=None, desc=None): - self.query = query - self.response = response - Exception.__init__(self, desc) + """ Errors in Command/Response pairs while communicating with the device """ + def __init__(self, query=None, response=None, desc=None): + self.query = query + self.response = response + ProtocolError.__init__(self, desc) - def __str__(self): - if self.query and self.response: - return "Got unexpected response:\n" + \ + def __str__(self): + if self.query and self.response: + return "Got unexpected response:\n" + \ "query:\n"+str(self.query.query)+"\n"+\ "expected:\n"+str(self.query.response)+"\n" +\ "actual:\n"+str(self.response) - if self.desc: - return self.desc - return "Unknown control error occurred" + if self.desc: + return self.desc + return "Unknown control error occurred" diff --git a/libprs500/gui/main.py b/libprs500/gui/main.py index f251aa38bd..f05e991d4d 100644 --- a/libprs500/gui/main.py +++ b/libprs500/gui/main.py @@ -487,4 +487,5 @@ def main(): lock = LockFile(lock) return app.exec_() -if __name__ == "__main__": sys.exit(main()) +if __name__ == "__main__": + sys.exit(main()) diff --git a/libprs500/prstypes.py b/libprs500/prstypes.py index e949160cf1..fbb2645d18 100755 --- a/libprs500/prstypes.py +++ b/libprs500/prstypes.py @@ -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}, L{Responses}, and L{Answers}. -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}, +L{Responses}, and L{Answers}. +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,725 +41,798 @@ 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 = "} for communication. - It has convenience methods to read and write data from the underlying buffer. See - L{TransferBuffer.pack} and L{TransferBuffer.unpack}. - """ - - def __init__(self, packet): - """ - Create a L{TransferBuffer} from C{packet} or an empty buffer. - @type packet: integer or listable object - @param packet: If packet is a list, it is copied into the C{TransferBuffer} and then normalized (see L{TransferBuffer._normalize}). - If it is an integer, a zero buffer of that length is created. - """ - if "__len__" in dir(packet): - list.__init__(self, list(packet)) - self._normalize() - else: list.__init__(self, [0 for i in range(packet)]) - - def __add__(self, tb): - """ Return a TransferBuffer rather than a list as the sum """ - return TransferBuffer(list.__add__(self, tb)) - - def __getslice__(self, start, end): - """ Return a TransferBuffer rather than a list as the slice """ - return TransferBuffer(list.__getslice__(self, start, end)) - - def __str__(self): """ - Return a string representation of this buffer. + Represents raw (unstructured) data packets sent over the usb bus. - Packets are represented as hex strings, in 2-byte pairs, S{<=} 16 bytes to a line. An ASCII representation is included. For example:: + C{TransferBuffer} is a wrapper around the tuples used by L{PyUSB} for communication. + It has convenience methods to read and write data from the underlying buffer. See + L{TransferBuffer.pack} and L{TransferBuffer.unpack}. + """ + + def __init__(self, packet): + """ + Create a L{TransferBuffer} from C{packet} or an empty buffer. + + @type packet: integer or listable object + @param packet: If packet is a list, it is copied into the C{TransferBuffer} and then normalized (see L{TransferBuffer._normalize}). + If it is an integer, a zero buffer of that length is created. + """ + if "__len__" in dir(packet): + list.__init__(self, list(packet)) + self._normalize() + else: list.__init__(self, [0 for i in range(packet)]) + + def __add__(self, tb): + """ Return a TransferBuffer rather than a list as the sum """ + return TransferBuffer(list.__add__(self, tb)) + + def __getslice__(self, start, end): + """ Return a TransferBuffer rather than a list as the slice """ + return TransferBuffer(list.__getslice__(self, start, end)) + + def __str__(self): + """ + Return a string representation of this buffer. + + Packets are represented as hex strings, in 2-byte pairs, S{<=} 16 bytes to a line. An ASCII representation is included. For example:: 0700 0100 0000 0000 0000 0000 0c00 0000 ................ 0200 0000 0400 0000 4461 7461 ........Data - """ - ans, ascii = ": ".rjust(10,"0"), "" - for i in range(0, len(self), 2): - for b in range(2): - try: - ans += TransferBuffer.phex(self[i+b]) - ascii += chr(self[i+b]) if self[i+b] > 31 and self[i+b] < 127 else "." - except IndexError: break - ans = ans + " " - if (i+2)%16 == 0: - if i+2 < len(self): - ans += " " + ascii + "\n" + (TransferBuffer.phex(i+2)+": ").rjust(10, "0") - ascii = "" - last_line = ans[ans.rfind("\n")+1:] - padding = 50 - len(last_line) - ans += "".ljust(padding) + " " + ascii - return ans.strip() + """ + ans, ascii = ": ".rjust(10,"0"), "" + for i in range(0, len(self), 2): + for b in range(2): + try: + ans += TransferBuffer.phex(self[i+b]) + ascii += chr(self[i+b]) if self[i+b] > 31 and self[i+b] < 127 else "." + except IndexError: break + ans = ans + " " + if (i+2)%16 == 0: + if i+2 < len(self): + ans += " " + ascii + "\n" + (TransferBuffer.phex(i+2)+": ").rjust(10, "0") + ascii = "" + last_line = ans[ans.rfind("\n")+1:] + padding = 50 - len(last_line) + ans += "".ljust(padding) + " " + ascii + return ans.strip() - def unpack(self, fmt=DWORD, start=0): - """ - Return decoded data from buffer. - - @param fmt: See U{struct} - @param start: Position in buffer from which to decode - """ - end = start + struct.calcsize(fmt) - return struct.unpack(fmt, "".join([ chr(i) for i in list.__getslice__(self, start, end) ])) - - def pack(self, val, fmt=DWORD, start=0): - """ - Encode C{val} and write it to buffer. - - @param fmt: See U{struct} - @param start: Position in buffer at which to write encoded data - """ - self[start:start+struct.calcsize(fmt)] = [ ord(i) for i in struct.pack(fmt, val) ] - - def _normalize(self): - """ Replace negative bytes in C{self} by 256 + byte """ - for i in range(len(self)): - if self[i] < 0: - self[i] = 256 + self[i] + def unpack(self, fmt=DWORD, start=0): + """ + Return decoded data from buffer. - @classmethod - def phex(cls, num): - """ - Return the hex representation of num without the 0x prefix. - - If the hex representation is only 1 digit it is padded to the left with a zero. Used in L{TransferBuffer.__str__}. - """ - index, sign = 2, "" - if num < 0: - index, sign = 3, "-" - h=hex(num)[index:] - if len(h) < 2: - h = "0"+h - return sign + h + @param fmt: See U{struct} + @param start: Position in buffer from which to decode + """ + end = start + struct.calcsize(fmt) + return struct.unpack(fmt, "".join([ chr(i) for i in list.__getslice__(self, start, end) ])) + + def pack(self, val, fmt=DWORD, start=0): + """ + Encode C{val} and write it to buffer. + + @param fmt: See U{struct} + @param start: Position in buffer at which to write encoded data + """ + self[start:start+struct.calcsize(fmt)] = [ ord(i) for i in struct.pack(fmt, val) ] + + def _normalize(self): + """ Replace negative bytes in C{self} by 256 + byte """ + for i in range(len(self)): + if self[i] < 0: + self[i] = 256 + self[i] + + @classmethod + def phex(cls, num): + """ + Return the hex representation of num without the 0x prefix. + + If the hex representation is only 1 digit it is padded to the left with a zero. Used in L{TransferBuffer.__str__}. + """ + index, sign = 2, "" + if num < 0: + index, sign = 3, "-" + h = hex(num)[index:] + if len(h) < 2: + h = "0"+h + return sign + h + - class field(object): - """ A U{Descriptor}, that implements access - to protocol packets in a human readable way. - """ - def __init__(self, start=16, fmt=DWORD): + """ A U{Descriptor}, that implements access + to protocol packets in a human readable way. """ - @param start: The byte at which this field is stored in the buffer - @param fmt: The packing format for this field. See U{struct}. - """ - self._fmt, self._start = fmt, start + 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}. + """ + self._fmt, self._start = fmt, start - def __get__(self, obj, typ=None): - return obj.unpack(start=self._start, fmt=self._fmt)[0] + def __get__(self, obj, typ=None): + return obj.unpack(start=self._start, fmt=self._fmt)[0] - def __set__(self, obj, val): - obj.pack(val, start=self._start, fmt=self._fmt) + def __set__(self, obj, val): + obj.pack(val, start=self._start, fmt=self._fmt) - 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) + 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) class stringfield(object): - """ A field storing a variable length string. """ - def __init__(self, length_field, start=16): - """ - @param length_field: A U{Descriptor} - that returns the length of the string. - @param start: The byte at which this field is stored in the buffer - """ - self._length_field = length_field - self._start = start + """ A field storing a variable length string. """ + def __init__(self, length_field, start=16): + """ + @param length_field: A U{Descriptor} + that returns the length of the string. + @param start: The byte at which this field is stored in the buffer + """ + self._length_field = length_field + self._start = start - def __get__(self, obj, typ=None): - length = str(self._length_field.__get__(obj)) - return obj.unpack(start=self._start, fmt="<"+length+"s")[0] + def __get__(self, obj, typ=None): + length = str(self._length_field.__get__(obj)) + return obj.unpack(start=self._start, fmt="<"+length+"s")[0] - def __set__(self, obj, val): - if val.__class__.__name__ != 'str': val = str(val) - obj.pack(val, start=self._start, fmt="<"+str(len(val))+"s") + def __set__(self, obj, val): + val = str(val) + obj.pack(val, start=self._start, fmt="<"+str(len(val))+"s") - def __repr__(self): - return "A string starting at byte " + str(self._start) + def __repr__(self): + return "A string starting at byte " + str(self._start) class Command(TransferBuffer): - - """ Defines the structure of command packets sent to the device. """ - 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 + """ 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) + # 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. + @apply + def data(): + doc = \ + """ + 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. + """ + def fget(self): + return self[16:] + + def fset(self, buff): + self[16:] = buff + self.length = len(buff) + + return property(doc=doc, fget=fget, fset=fset) - 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) - - return property(**locals()) - - 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") - TransferBuffer.__init__(self, packet) - + 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") + TransferBuffer.__init__(self, packet) + 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.) - 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 - hour = field(start=0x20, fmt=DWORD) #: hour 0-23 - minute = field(start=0x24, fmt=DWORD) #: minute 0-59 - second = field(start=0x28, fmt=DWORD) #: second 0-59 - - def __init__(self, t=None): - """ @param t: time as an epoch """ - self.number = SetTime.NUMBER - self.type = 0x01 - self.length = 0x1c - tz = int(-time.timezone/60.) - self.timezone = tz if tz > 0 else 0xffffffff +1 + tz - if not t: t = time.time() - t = time.gmtime(t) - self.year = t[0] - self.month = t[1] - 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 + """ + Set time on device. All fields refer to time in the GMT time zone. + """ + NUMBER = 0x104 + # -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 + hour = field(start=0x20, fmt=DWORD) #: hour 0-23 + minute = field(start=0x24, fmt=DWORD) #: minute 0-59 + second = field(start=0x28, fmt=DWORD) #: second 0-59 - + def __init__(self, t=None): + """ @param t: time as an epoch """ + self.number = SetTime.NUMBER + self.type = 0x01 + self.length = 0x1c + tz = int(-time.timezone/60.) + self.timezone = tz if tz > 0 else 0xffffffff +1 + tz + if not t: t = time.time() + t = time.gmtime(t) + self.year = t[0] + self.month = t[1] + self.day = t[2] + self.hour = t[3] + self.minute = t[4] + # 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): - - """ 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 - - def __init__(self, number=0x00, type=0x00, command=0x00): - """ - @param number: L{Command.number} - @param type: L{Command.type} - @param command: L{ShortCommand.command} - """ - Command.__init__(self, ShortCommand.SIZE) - self.number = number - self.type = type - self.length = 4 - self.command = command + """ A L{Command} whoose data section is 4 bytes long """ + + SIZE = 20 #: Packet size in bytes + # Usually carries additional information + command = field(start=16, fmt=DWORD) + + def __init__(self, number=0x00, type=0x00, command=0x00): + """ + @param number: L{Command.number} + @param type: L{Command.type} + @param command: L{ShortCommand.command} + """ + Command.__init__(self, ShortCommand.SIZE) + self.number = number + self.type = type + self.length = 4 + self.command = 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): - """ @param id: The identifier returned as a result of a L{DirOpen} command """ - ShortCommand.__init__(self, number=DirRead.NUMBER, type=0x01, command=id) - + """ The command that asks the device to send the next item in the list """ + NUMBER = 0x35 #: Command number + 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) + class DirClose(ShortCommand): - """ Close a previously opened directory """ - NUMBER = 0x34 #: Command number - 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) + """ Close a previously opened directory """ + NUMBER = 0x34 #: Command number + 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) 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 - def __init__(self): - ShortCommand.__init__(self, number=EndSession.NUMBER, type=0x01, command=0x00) - + """ + 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) + 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) + """ Get USB Protocol version used by device """ + NUMBER = 0x0 #: Command number + def __init__(self): + ShortCommand.__init__(self, \ + number=GetUSBProtocolVersion.NUMBER, \ + type=0x01, command=0x00) class SetBulkSize(ShortCommand): - NUMBER = 0x107 #: Command number - def __init__(self, size=0x028000): - ShortCommand.__init__(self, number=SetBulkSize.NUMBER, type=0x01, command=size) + """ 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) class UnlockDevice(ShortCommand): - NUMBER = 0x106 #: Command number - def __init__(self, key=0x312d): - ShortCommand.__init__(self, number=UnlockDevice.NUMBER, type=0x01, command=key) + """ Unlock the device """ + NUMBER = 0x106 #: Command number + def __init__(self, key=0x312d): + ShortCommand.__init__(self, \ + number=UnlockDevice.NUMBER, type=0x01, command=key) class LongCommand(Command): - - """ A L{Command} whoose data section is 16 bytes long """ - - SIZE = 32 #: Size in bytes of C{LongCommand} packets - - def __init__(self, number=0x00, type=0x00, command=0x00): - """ - @param number: L{Command.number} - @param type: L{Command.type} - @param command: L{LongCommand.command} - """ - Command.__init__(self, LongCommand.SIZE) - self.number = number - self.type = type - self.length = 16 - self.command = command - - @apply - def 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. - """ - def fget(self): - return self.unpack(start=16, fmt="<"+str(self.length/4)+"I") - - def fset(self, val): - if "__len__" not in dir(val): val = (val,) - start = 16 - for command in val: - self.pack(command, start=start, fmt=DWORD) - start += struct.calcsize(DWORD) - - return property(**locals()) + + """ A L{Command} whoose data section is 16 bytes long """ + + SIZE = 32 #: Size in bytes of C{LongCommand} packets + + def __init__(self, number=0x00, type=0x00, command=0x00): + """ + @param number: L{Command.number} + @param type: L{Command.type} + @param command: L{LongCommand.command} + """ + Command.__init__(self, LongCommand.SIZE) + self.number = number + self.type = type + self.length = 16 + self.command = command + + @apply + def 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. + """ + def fget(self): + return self.unpack(start=16, fmt="<"+str(self.length/4)+"I") + + def fset(self, val): + if "__len__" not in dir(val): val = (val,) + start = 16 + for command in val: + self.pack(command, start=start, fmt=DWORD) + start += struct.calcsize(DWORD) + + return property(doc=doc, fget=fget, fset=fset) class PathCommand(Command): - """ Abstract class that defines structure common to all path related commands. """ - - path_length = field(start=16, fmt=DWORD) #: Length of the path to follow - path = stringfield(path_length, start=20) #: The path this query is about - def __init__(self, path, number, path_len_at_byte=16): - Command.__init__(self, path_len_at_byte+4+len(path)) - self.path_length = len(path) - self.path = path - self.type = 0x01 - self.length = len(self)-16 - self.number = number + """ Abstract class that defines structure common to all path related commands. """ + path_length = field(start=16, fmt=DWORD) #: Length of the path to follow + path = stringfield(path_length, start=20) #: The path this query is about + def __init__(self, path, number, path_len_at_byte=16): + Command.__init__(self, path_len_at_byte+4+len(path)) + self.path_length = len(path) + self.path = path + self.type = 0x01 + self.length = len(self) - 16 + self.number = number + class TotalSpaceQuery(PathCommand): - """ Query the total space available on the volume represented by path """ - NUMBER = 0x53 #: Command number - def __init__(self, path): - """ @param path: valid values are 'a:', 'b:', '/Data/' """ - PathCommand.__init__(self, path, TotalSpaceQuery.NUMBER) + """ Query the total space available on the volume represented by path """ + NUMBER = 0x53 #: Command number + def __init__(self, path): + """ @param path: valid values are 'a:', 'b:', '/Data/' """ + PathCommand.__init__(self, path, TotalSpaceQuery.NUMBER) class FreeSpaceQuery(ShortCommand): - """ Query the free space available """ - NUMBER = 0x103 #: Command number - def __init__(self, where): - """ @param where: valid values are: 'a:', 'b:', '/' """ - c = 0 - if where.startswith('a:'): c = 1 - elif where.startswith('b:'): c = 2 - ShortCommand.__init__(self, number=FreeSpaceQuery.NUMBER, type=0x01, command=c) + """ Query the free space available """ + NUMBER = 0x103 #: Command number + def __init__(self, where): + """ @param where: valid values are: 'a:', 'b:', '/' """ + c = 0 + if where.startswith('a:'): c = 1 + elif where.startswith('b:'): c = 2 + ShortCommand.__init__(self, \ + number=FreeSpaceQuery.NUMBER, type=0x01, command=c) class DirCreate(PathCommand): - """ Create a directory """ - NUMBER = 0x30 - def __init__(self, path): - PathCommand.__init__(self, path, DirCreate.NUMBER) + """ Create a directory """ + NUMBER = 0x30 + def __init__(self, path): + PathCommand.__init__(self, path, DirCreate.NUMBER) class DirOpen(PathCommand): - """ Open a directory for reading its contents """ - NUMBER = 0x33 #: Command number - def __init__(self, path): - PathCommand.__init__(self, path, DirOpen.NUMBER) + """ Open a directory for reading its contents """ + NUMBER = 0x33 #: Command number + def __init__(self, path): + PathCommand.__init__(self, path, DirOpen.NUMBER) 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) + """ 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) class DeviceInfoQuery(Command): - """ The command used to ask for device information """ - NUMBER=0x0101 #: Command number - def __init__(self): - Command.__init__(self, 16) - self.number=DeviceInfoQuery.NUMBER - self.type=0x01 + """ The command used to ask for device information """ + NUMBER = 0x101 #: Command number + def __init__(self): + Command.__init__(self, 16) + self.number = DeviceInfoQuery.NUMBER + self.type = 0x01 class FileClose(ShortCommand): - """ File close command """ - NUMBER = 0x11 #: Command number - def __init__(self, id): - ShortCommand.__init__(self, number=FileClose.NUMBER, type=0x01, command=id) + """ File close command """ + NUMBER = 0x11 #: Command number + def __init__(self, _id): + ShortCommand.__init__(self, number=FileClose.NUMBER, \ + type=0x01, command=_id) class FileCreate(PathCommand): - """ Create a file """ - NUMBER=0x1a #: Command number - def __init__(self, path): - PathCommand.__init__(self, path, FileCreate.NUMBER) + """ Create a file """ + NUMBER = 0x1a #: Command number + def __init__(self, path): + PathCommand.__init__(self, path, FileCreate.NUMBER) class FileDelete(PathCommand): - """ Delete a file """ - NUMBER=0x1B - def __init__(self, path): - PathCommand.__init__(self, path, FileDelete.NUMBER) + """ Delete a file """ + NUMBER = 0x1B + def __init__(self, path): + PathCommand.__init__(self, path, FileDelete.NUMBER) class DirDelete(PathCommand): - """ Delete a directory """ - NUMBER=0x31 - def __init__(self, path): - PathCommand.__init__(self, path, DirDelete.NUMBER) + """ Delete a directory """ + NUMBER = 0x31 + def __init__(self, path): + PathCommand.__init__(self, path, DirDelete.NUMBER) class FileOpen(PathCommand): - """ File open command """ - NUMBER = 0x10 #: Command number - READ = 0x00 #: Open file in read mode - WRITE = 0x01 #: Open file in write mode - path_length = field(start=20, fmt=DWORD) - path = stringfield(path_length, start=24) - - def __init__(self, path, mode=0x00): - PathCommand.__init__(self, path, FileOpen.NUMBER, path_len_at_byte=20) - self.mode = mode + """ File open command """ + NUMBER = 0x10 #: Command number + READ = 0x00 #: Open file in read mode + WRITE = 0x01 #: Open file in write mode + path_length = field(start=20, fmt=DWORD) + path = stringfield(path_length, start=24) - @apply - def mode(): - doc =\ - """ 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()) - - + def __init__(self, path, mode=0x00): + PathCommand.__init__(self, path, FileOpen.NUMBER, path_len_at_byte=20) + self.mode = mode + + @apply + def mode(): + doc = \ + """ + 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(doc=doc, fget=fget, fset=fset) + + class FileIO(Command): - """ Command to read/write from an open file """ - RNUMBER = 0x16 #: Command number to read from a file - WNUMBER = 0x17 #: Command number to write to a file - id = field(start=16, fmt=DWORD) #: The file ID returned by a FileOpen command - 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): - """ - @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} - @param size: number of bytes to read - @type size: C{unsigned int} - @param mode: Either L{FileIO.RNUMBER} or L{File.WNUMBER} - """ - Command.__init__(self, 32) - self.number=mode - self.type = 0x01 - self.length = 16 - self.id = id - self.offset = offset - self.size = size + """ Command to read/write from an open file """ + RNUMBER = 0x16 #: Command number to read from a file + WNUMBER = 0x17 #: Command number to write to a file + id = field(start=16, fmt=DWORD) #: The file ID returned by a FileOpen command + 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): + """ + @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} + @param size: number of bytes to read + @type size: C{unsigned int} + @param mode: Either L{FileIO.RNUMBER} or L{File.WNUMBER} + """ + Command.__init__(self, 32) + self.number = mode + self.type = 0x01 + self.length = 16 + self.id = _id + self.offset = offset + self.size = size class PathQuery(PathCommand): - """ Defines structure of command that requests information about a path """ - NUMBER = 0x18 #: Command number - def __init__(self, path): - PathCommand.__init__(self, path, PathQuery.NUMBER) - + """ Defines structure of command that requests information about a path """ + NUMBER = 0x18 #: Command number + def __init__(self, path): + PathCommand.__init__(self, path, PathQuery.NUMBER) + class SetFileInfo(PathCommand): - """ Set File information """ - NUMBER = 0x19 #: Command number - def __init__(self, path): - PathCommand.__init__(self, path, SetFileInfo.NUMBER) - + """ Set File information """ + NUMBER = 0x19 #: Command number + def __init__(self, path): + PathCommand.__init__(self, path, SetFileInfo.NUMBER) + 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. - """ - - 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 - - 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))) - Command.__init__(self, packet) - if self.number != 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. """ - def fget(self): - return self.unpack(start=20, fmt="=} 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") - elif packet < 16: - raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes") - TransferBuffer.__init__(self, packet) + """ + 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 + + def __init__(self, packet): + """ @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") + elif packet < 16: + 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. """ - - 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 - - @apply - def is_dir(): - doc = """True if path points to a directory, False if it points to a file.""" - def fget(self): - return (self.file_type == 2) - - def fset(self, val): - if val: val = 2 - else: val = 1 - self.file_type = val - - return property(**locals()) + """ + 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 + # 0 = default permissions, 4 = read only + permissions = field(start=36, fmt=DWORD) + + @apply + def is_dir(): + doc = """True if path points to a directory, False if it points to a file.""" + + def fget(self): + return (self.file_type == 2) + + def fset(self, val): + if val: + val = 2 + else: + val = 1 + self.file_type = val + + return property(doc=doc, fget=fget, fset=fset) - @apply - def is_readonly(): - doc = """ Whether this file is readonly.""" - - def fget(self): - return self.unpack(start=36, fmt=DWORD)[0] != 0 - - def fset(self, val): - if val: val = 4 - else: val = 0 - self.pack(val, start=36, fmt=DWORD) - - return property(**locals()) - + @apply + def is_readonly(): + doc = """ Whether this file is readonly.""" + + def fget(self): + return self.unpack(start=36, fmt=DWORD)[0] != 0 + + def fset(self, val): + if val: + val = 4 + else: + val = 0 + self.pack(val, start=36, fmt=DWORD) + + return property(doc=doc, fget=fget, fset=fset) + class USBProtocolVersion(Answer): - version = field(start=16, fmt=DDWORD) + """ Get USB Protocol version """ + version = field(start=16, fmt=DDWORD) class IdAnswer(Answer): - - """ Defines the structure of packets that contain identifiers for queries. """ - - @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. """ - 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()) + """ Defines the structure of packets that contain identifiers for queries. """ + @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. + """ + + def fget(self): + return self.unpack(start=16, fmt=DWORD)[0] + + def fset(self, val): + self.pack(val, start=16, fmt=DWORD) + + return property(doc=doc, fget=fget, fset=fset) + class DeviceInfo(Answer): - """ Defines the structure of the packet containing information about the device """ - device_name = field(start=16, fmt="<32s") - device_version = field(start=48, fmt="<32s") - software_version = field(start=80, fmt="<24s") - mime_type = field(start=104, fmt="<32s") - + """ Defines the structure of the packet containing information about the device """ + device_name = field(start=16, fmt="<32s") + device_version = field(start=48, fmt="<32s") + software_version = field(start=80, fmt="<24s") + mime_type = field(start=104, fmt="<32s") + 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 - + total = field(start=24, fmt=DDWORD) #: Total space available + # Supposedly free space available, but it does not work for main memory + free_space = field(start=32, fmt=DDWORD) + class FreeSpaceAnswer(Answer): - SIZE = 24 - free = field(start=16, fmt=DDWORD) + SIZE = 24 + free = field(start=16, fmt=DDWORD) class ListAnswer(Answer): - """ Defines the structure of packets that contain items in a list. """ - name_length = field(start=20, fmt=DWORD) - name = stringfield(name_length, start=24) - - @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. """ + """ Defines the structure of packets that contain items in a list. """ + name_length = field(start=20, fmt=DWORD) + name = stringfield(name_length, start=24) - def fget(self): - return (self.unpack(start=16, fmt=DWORD)[0] == 2) - - def fset(self, val): - if val: val = 2 - else: val = 1 - self.pack(val, start=16, fmt=DWORD) - - return property(**locals()) - - + @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. + """ + + def fget(self): + return (self.unpack(start=16, fmt=DWORD)[0] == 2) + + def fset(self, val): + if val: val = 2 + else: val = 1 + self.pack(val, start=16, fmt=DWORD) + + return property(doc=doc, fget=fget, fset=fset) + + diff --git a/prs-500.e4p b/prs-500.e4p deleted file mode 100644 index a8a809c216..0000000000 --- a/prs-500.e4p +++ /dev/null @@ -1,239 +0,0 @@ - - - - - - - Python - Qt4 - Library to communicate with the Sony Reader PRS-500 via USB - - Kovid Goyal - kovid@kovidgoyal.net - - - libprs500 - communicate.py - - - libprs500 - prstypes.py - - - libprs500 - errors.py - - - libprs500 - __init__.py - - - setup.py - - - libprs500 - cli - terminfo.py - - - libprs500 - cli - main.py - - - libprs500 - cli - __init__.py - - - libprs500 - gui - main.py - - - libprs500 - gui - __init__.py - - - libprs500 - lrf - meta.py - - - libprs500 - lrf - __init__.py - - - libprs500 - gui - database.py - - - libprs500 - gui - editbook.py - - - libprs500 - books.py - - - libprs500 - gui - widgets.py - - - -
- libprs500 - gui - main.ui -
-
- libprs500 - gui - editbook.ui -
-
- - - - - - - - - epydoc.conf - - - epydoc-pdf.conf - - - - libprs500 - gui - main.py - - - Subversion - - - - status - - - - - - - - log - - - - - - - - global - - - - - - - - update - - - - - - - - remove - - - - - - - - add - - - - - - - - tag - - - - - - - - export - - - - - - - - commit - - - - - - - - diff - - - - - - - - checkout - - - - - - - - history - - - - - - - - - - - - standardLayout - - - True - - - - - - - - - - - -