mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Raising pylint scores for libprs500/*.py
This commit is contained in:
parent
3d7d6a862a
commit
6010e9b2ca
@ -12,16 +12,25 @@
|
||||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
""" This module contains the logic for dealing with XML book lists found in the reader cache """
|
||||
"""
|
||||
This module contains the logic for dealing with XML book lists found
|
||||
in the reader cache.
|
||||
"""
|
||||
from xml.dom.ext import PrettyPrint
|
||||
import xml.dom.minidom as dom
|
||||
from base64 import b64decode as decode
|
||||
from base64 import b64encode as encode
|
||||
import time
|
||||
|
||||
MIME_MAP = { "lrf":"application/x-sony-bbeb", "rtf":"application/rtf", "pdf":"application/pdf", "txt":"text/plain" }
|
||||
MIME_MAP = { \
|
||||
"lrf":"application/x-sony-bbeb", \
|
||||
"rtf":"application/rtf", \
|
||||
"pdf":"application/pdf", \
|
||||
"txt":"text/plain" \
|
||||
}
|
||||
|
||||
class book_metadata_field(object):
|
||||
""" Represents metadata stored as an attribute """
|
||||
def __init__(self, attr, formatter=None, setter=None):
|
||||
self.attr = attr
|
||||
self.formatter = formatter
|
||||
@ -29,25 +38,36 @@ class book_metadata_field(object):
|
||||
|
||||
def __get__(self, obj, typ=None):
|
||||
""" Return a string. String may be empty if self.attr is absent """
|
||||
return self.formatter(obj.elem.getAttribute(self.attr)) if self.formatter else obj.elem.getAttribute(self.attr).strip()
|
||||
return self.formatter(obj.elem.getAttribute(self.attr)) if \
|
||||
self.formatter else obj.elem.getAttribute(self.attr).strip()
|
||||
|
||||
def __set__(self, obj, val):
|
||||
""" Set the attribute """
|
||||
val = self.setter(val) if self.setter else val
|
||||
obj.elem.setAttribute(self.attr, str(val))
|
||||
|
||||
class Book(object):
|
||||
""" Provides a view onto the XML element that represents a book """
|
||||
title = book_metadata_field("title")
|
||||
author = book_metadata_field("author", formatter=lambda x: x if x.strip() else "Unknown")
|
||||
author = book_metadata_field("author", \
|
||||
formatter=lambda x: x if x.strip() else "Unknown")
|
||||
mime = book_metadata_field("mime")
|
||||
rpath = book_metadata_field("path")
|
||||
id = book_metadata_field("id", formatter=int)
|
||||
sourceid = book_metadata_field("sourceid", formatter=int)
|
||||
size = book_metadata_field("size", formatter=int)
|
||||
# When setting this attribute you must use an epoch
|
||||
datetime = book_metadata_field("date", formatter=lambda x: time.strptime(x, "%a, %d %b %Y %H:%M:%S %Z"), setter=lambda x: time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(x)))
|
||||
datetime = book_metadata_field("date", \
|
||||
formatter=lambda x: time.strptime(x, "%a, %d %b %Y %H:%M:%S %Z"),
|
||||
setter=lambda x: time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(x)))
|
||||
|
||||
@apply
|
||||
def thumbnail():
|
||||
doc = \
|
||||
"""
|
||||
The thumbnail. Should be a height 68 image.
|
||||
Setting is not supported.
|
||||
"""
|
||||
def fget(self):
|
||||
th = self.elem.getElementsByTagName(self.prefix + "thumbnail")
|
||||
if len(th):
|
||||
@ -57,14 +77,17 @@ class Book(object):
|
||||
break
|
||||
rc = ""
|
||||
for node in th.childNodes:
|
||||
if node.nodeType == node.TEXT_NODE: rc += node.data
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
rc += node.data
|
||||
return decode(rc)
|
||||
return property(**locals())
|
||||
return property(fget=fget, doc=doc)
|
||||
|
||||
@apply
|
||||
def path():
|
||||
def fget(self): return self.root + self.rpath
|
||||
return property(**locals())
|
||||
doc = """ Absolute path to book on device. Setting not supported. """
|
||||
def fget(self):
|
||||
return self.root + self.rpath
|
||||
return property(fget=fget, doc=doc)
|
||||
|
||||
def __init__(self, node, prefix="xs1:", root="/Data/media/"):
|
||||
self.elem = node
|
||||
@ -79,57 +102,77 @@ class Book(object):
|
||||
|
||||
|
||||
def fix_ids(media, cache):
|
||||
id = 0
|
||||
"""
|
||||
Update ids in media, cache to be consistent with their
|
||||
current structure
|
||||
"""
|
||||
cid = 0
|
||||
for child in media.root.childNodes:
|
||||
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
|
||||
child.setAttribute("id", str(id))
|
||||
id += 1
|
||||
mmaxid = id - 1
|
||||
id = mmaxid + 2
|
||||
if child.nodeType == child.ELEMENT_NODE and \
|
||||
child.hasAttribute("id"):
|
||||
child.setAttribute("id", str(cid))
|
||||
cid += 1
|
||||
mmaxid = cid - 1
|
||||
cid = mmaxid + 2
|
||||
if len(cache):
|
||||
for child in cache.root.childNodes:
|
||||
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("sourceid"):
|
||||
if child.nodeType == child.ELEMENT_NODE and \
|
||||
child.hasAttribute("sourceid"):
|
||||
child.setAttribute("sourceid", str(mmaxid+1))
|
||||
child.setAttribute("id", str(id))
|
||||
id += 1
|
||||
media.document.documentElement.setAttribute("nextID", str(id))
|
||||
child.setAttribute("id", str(cid))
|
||||
cid += 1
|
||||
media.document.documentElement.setAttribute("nextID", str(cid))
|
||||
|
||||
class BookList(list):
|
||||
"""
|
||||
A list of L{Book}s. Created from an XML file. Can write list
|
||||
to an XML file.
|
||||
"""
|
||||
__getslice__ = None
|
||||
__setslice__ = None
|
||||
|
||||
def __init__(self, prefix="xs1:", root="/Data/media/", file=None):
|
||||
def __init__(self, prefix="xs1:", root="/Data/media/", sfile=None):
|
||||
list.__init__(self)
|
||||
if file:
|
||||
if sfile:
|
||||
self.prefix = prefix
|
||||
self.proot = root
|
||||
file.seek(0)
|
||||
self.document = dom.parse(file)
|
||||
self.root = self.document.documentElement #: The root element containing all records
|
||||
if prefix == "xs1:": self.root = self.root.getElementsByTagName("records")[0]
|
||||
for book in self.document.getElementsByTagName(self.prefix + "text"): self.append(Book(book, root=root, prefix=prefix))
|
||||
sfile.seek(0)
|
||||
self.document = dom.parse(sfile)
|
||||
# The root element containing all records
|
||||
self.root = self.document.documentElement
|
||||
if prefix == "xs1:":
|
||||
self.root = self.root.getElementsByTagName("records")[0]
|
||||
for book in self.document.getElementsByTagName(self.prefix + "text"):
|
||||
self.append(Book(book, root=root, prefix=prefix))
|
||||
|
||||
def max_id(self):
|
||||
id = -1
|
||||
""" Highest id in underlying XML file """
|
||||
cid = -1
|
||||
for child in self.root.childNodes:
|
||||
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
|
||||
if child.nodeType == child.ELEMENT_NODE and \
|
||||
child.hasAttribute("id"):
|
||||
c = int(child.getAttribute("id"))
|
||||
if c > id: id = c
|
||||
return id
|
||||
if c > cid:
|
||||
cid = c
|
||||
return cid
|
||||
|
||||
def has_id(self, id):
|
||||
""" Check if a book with id C{ == id} exists already. This *does not* check if id exists in the underlying XML file """
|
||||
def has_id(self, cid):
|
||||
"""
|
||||
Check if a book with id C{ == cid} exists already.
|
||||
This *does not* check if id exists in the underlying XML file
|
||||
"""
|
||||
ans = False
|
||||
for book in self:
|
||||
if book.id == id:
|
||||
if book.id == cid:
|
||||
ans = True
|
||||
break
|
||||
return ans
|
||||
|
||||
def delete_book(self, id):
|
||||
def delete_book(self, cid):
|
||||
""" Remove DOM node corresponding to book with C{id == cid}."""
|
||||
node = None
|
||||
for book in self:
|
||||
if book.id == id:
|
||||
if book.id == cid:
|
||||
node = book
|
||||
self.remove(book)
|
||||
break
|
||||
@ -137,11 +180,15 @@ class BookList(list):
|
||||
node.elem.unlink()
|
||||
|
||||
def add_book(self, info, name, size, ctime):
|
||||
""" Add a node into DOM tree representing a book """
|
||||
node = self.document.createElement(self.prefix + "text")
|
||||
mime = MIME_MAP[name[name.rfind(".")+1:]]
|
||||
id = self.max_id()+1
|
||||
cid = self.max_id()+1
|
||||
sourceid = str(self[0].sourceid) if len(self) else "1"
|
||||
attrs = { "title":info["title"], "author":info["authors"], "page":"0", "part":"0", "scale":"0", "sourceid":sourceid, "id":str(id), "date":"", "mime":mime, "path":name, "size":str(size)}
|
||||
attrs = { "title":info["title"], "author":info["authors"], \
|
||||
"page":"0", "part":"0", "scale":"0", \
|
||||
"sourceid":sourceid, "id":str(cid), "date":"", \
|
||||
"mime":mime, "path":name, "size":str(size)}
|
||||
for attr in attrs.keys():
|
||||
node.setAttributeNode(self.document.createAttribute(attr))
|
||||
node.setAttribute(attr, attrs[attr])
|
||||
@ -160,4 +207,5 @@ class BookList(list):
|
||||
self.append(book)
|
||||
|
||||
def write(self, stream):
|
||||
""" Write XML representation of DOM tree to C{stream} """
|
||||
PrettyPrint(self.document, stream)
|
||||
|
@ -36,47 +36,74 @@
|
||||
### bInterval 0
|
||||
###
|
||||
###
|
||||
### Endpoint 0x81 is device->host and endpoint 0x02 is host->device. You can establish Stream pipes to/from these endpoints for Bulk transfers.
|
||||
### Has two configurations 1 is the USB charging config 2 is the self-powered config.
|
||||
### I think config management is automatic. Endpoints are the same
|
||||
### Endpoint 0x81 is device->host and endpoint 0x02 is host->device.
|
||||
### You can establish Stream pipes to/from these endpoints for Bulk transfers.
|
||||
### Has two configurations 1 is the USB charging config 2 is the self-powered
|
||||
### config. I think config management is automatic. Endpoints are the same
|
||||
"""
|
||||
Contains the logic for communication with the device (a SONY PRS-500).
|
||||
|
||||
The public interface of class L{PRS500Device} defines the methods for performing various tasks.
|
||||
The public interface of class L{PRS500Device} defines the
|
||||
methods for performing various tasks.
|
||||
"""
|
||||
import usb, sys, os, time
|
||||
import usb
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from tempfile import TemporaryFile
|
||||
from array import array
|
||||
|
||||
from libprs500.prstypes import *
|
||||
from libprs500.errors import *
|
||||
from libprs500.books import *
|
||||
from libprs500.books import BookList, fix_ids
|
||||
from libprs500 import __author__ as AUTHOR
|
||||
|
||||
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
|
||||
_packet_number = 0 #: Keep track of the packet number of packet tracing
|
||||
KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L] #: Protocol versions libprs500 has been tested with
|
||||
|
||||
def _log_packet(packet, header, stream=sys.stderr):
|
||||
""" Log C{packet} to stream C{stream}. Header should be a small word describing the type of packet. """
|
||||
global _packet_number
|
||||
_packet_number += 1
|
||||
print >>stream, str(_packet_number), header, "Type:", packet.__class__.__name__
|
||||
# Protocol versions libprs500 has been tested with
|
||||
KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L]
|
||||
|
||||
class Device(object):
|
||||
""" Contains specific device independent methods """
|
||||
_packet_number = 0 #: Keep track of the packet number for packet tracing
|
||||
|
||||
def log_packet(self, packet, header, stream=sys.stderr):
|
||||
"""
|
||||
Log C{packet} to stream C{stream}.
|
||||
Header should be a small word describing the type of packet.
|
||||
"""
|
||||
self._packet_number += 1
|
||||
print >> stream, str(self._packet_number), header, "Type:", \
|
||||
packet.__class__.__name__
|
||||
print >> stream, packet
|
||||
print >> stream, "--"
|
||||
|
||||
@classmethod
|
||||
def validate_response(cls, res, _type=0x00, number=0x00):
|
||||
"""
|
||||
Raise a ProtocolError if the type and number of C{res}
|
||||
is not the same as C{type} and C{number}.
|
||||
"""
|
||||
if _type != res.type or number != res.rnumber:
|
||||
raise ProtocolError("Inavlid response.\ntype: expected=" + \
|
||||
hex(_type)+" actual=" + hex(res.type) + \
|
||||
"\nrnumber: expected=" + hex(number) + \
|
||||
" actual="+hex(res.rnumber))
|
||||
|
||||
|
||||
class File(object):
|
||||
""" Wrapper that allows easy access to all information about files/directories """
|
||||
def __init__(self, file):
|
||||
self.is_dir = file[1].is_dir #: True if self is a directory
|
||||
self.is_readonly = file[1].is_readonly #: True if self is readonly
|
||||
self.size = file[1].file_size #: Size in bytes of self
|
||||
self.ctime = file[1].ctime #: Creation time of self as a epoch
|
||||
self.wtime = file[1].wtime #: Creation time of self as an epoch
|
||||
path = file[0]
|
||||
if path.endswith("/"): path = path[:-1]
|
||||
"""
|
||||
Wrapper that allows easy access to all information about files/directories
|
||||
"""
|
||||
def __init__(self, _file):
|
||||
self.is_dir = _file[1].is_dir #: True if self is a directory
|
||||
self.is_readonly = _file[1].is_readonly #: True if self is readonly
|
||||
self.size = _file[1].file_size #: Size in bytes of self
|
||||
self.ctime = _file[1].ctime #: Creation time of self as a epoch
|
||||
self.wtime = _file[1].wtime #: Creation time of self as an epoch
|
||||
path = _file[0]
|
||||
if path.endswith("/"):
|
||||
path = path[:-1]
|
||||
self.path = path #: Path to self
|
||||
self.name = path[path.rfind("/")+1:].rstrip() #: Name of self
|
||||
|
||||
@ -101,7 +128,7 @@ class DeviceDescriptor:
|
||||
self.product_id = product_id
|
||||
self.interface_id = interface_id
|
||||
|
||||
def getDevice(self) :
|
||||
def get_device(self) :
|
||||
"""
|
||||
Return the device corresponding to the device descriptor if it is
|
||||
available on a USB bus. Otherwise, return None. Note that the
|
||||
@ -116,7 +143,7 @@ class DeviceDescriptor:
|
||||
return None
|
||||
|
||||
|
||||
class PRS500Device(object):
|
||||
class PRS500Device(Device):
|
||||
|
||||
"""
|
||||
Contains the logic for performing various tasks on the reader.
|
||||
@ -132,12 +159,17 @@ class PRS500Device(object):
|
||||
PRS500_INTERFACE_ID = 0 #: The interface we use to talk to the device
|
||||
PRS500_BULK_IN_EP = 0x81 #: Endpoint for Bulk reads
|
||||
PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes
|
||||
MEDIA_XML = "/Data/database/cache/media.xml" #: Location of media.xml file on device
|
||||
CACHE_XML = "/Sony Reader/database/cache.xml" #: Location of cache.xml on storage card in device
|
||||
FORMATS = ["lrf", "rtf", "pdf", "txt"] #: Ordered list of supported formats
|
||||
THUMBNAIL_HEIGHT = 68 #: Height for thumbnails of books/images on the device
|
||||
# Location of media.xml file on device
|
||||
MEDIA_XML = "/Data/database/cache/media.xml"
|
||||
# Location of cache.xml on storage card in device
|
||||
CACHE_XML = "/Sony Reader/database/cache.xml"
|
||||
# Ordered list of supported formats
|
||||
FORMATS = ["lrf", "rtf", "pdf", "txt"]
|
||||
# Height for thumbnails of books/images on the device
|
||||
THUMBNAIL_HEIGHT = 68
|
||||
|
||||
device_descriptor = DeviceDescriptor(SONY_VENDOR_ID, PRS500_PRODUCT_ID, PRS500_INTERFACE_ID)
|
||||
device_descriptor = DeviceDescriptor(SONY_VENDOR_ID, \
|
||||
PRS500_PRODUCT_ID, PRS500_INTERFACE_ID)
|
||||
|
||||
@classmethod
|
||||
def signature(cls):
|
||||
@ -146,14 +178,18 @@ class PRS500Device(object):
|
||||
|
||||
def safe(func):
|
||||
"""
|
||||
Decorator that wraps a call to C{func} to ensure that exceptions are handled correctly.
|
||||
It also calls L{open} to claim the interface and initialize the Reader if needed.
|
||||
Decorator that wraps a call to C{func} to ensure that
|
||||
exceptions are handled correctly. It also calls L{open} to claim
|
||||
the interface and initialize the Reader if needed.
|
||||
|
||||
As a convenience, C{safe} automatically sends the a L{EndSession} after calling func, unless func has
|
||||
As a convenience, C{safe} automatically sends the a
|
||||
L{EndSession} after calling func, unless func has
|
||||
a keyword argument named C{end_session} set to C{False}.
|
||||
|
||||
An L{ArgumentError} will cause the L{EndSession} command to be sent to the device, unless end_session is set to C{False}.
|
||||
An L{usb.USBError} will cause the library to release control of the USB interface via a call to L{close}.
|
||||
An L{ArgumentError} will cause the L{EndSession} command to
|
||||
be sent to the device, unless end_session is set to C{False}.
|
||||
An L{usb.USBError} will cause the library to release control of the
|
||||
USB interface via a call to L{close}.
|
||||
|
||||
@todo: Fix handling of timeout errors
|
||||
"""
|
||||
@ -161,25 +197,27 @@ class PRS500Device(object):
|
||||
dev = args[0]
|
||||
res = None
|
||||
try:
|
||||
if not dev.handle: dev.open()
|
||||
if not dev.handle:
|
||||
dev.open()
|
||||
res = func(*args, **kwargs)
|
||||
except ArgumentError:
|
||||
if not kwargs.has_key("end_session") or kwargs["end_session"]:
|
||||
dev._send_validated_command(EndSession())
|
||||
dev.send_validated_command(EndSession())
|
||||
raise
|
||||
except usb.USBError, e:
|
||||
if "No such device" in str(e):
|
||||
except usb.USBError, err:
|
||||
if "No such device" in str(err):
|
||||
raise DeviceError()
|
||||
elif "Connection timed out" in str(e):
|
||||
elif "Connection timed out" in str(err):
|
||||
dev.close()
|
||||
raise TimeoutError(func.__name__)
|
||||
elif "Protocol error" in str(e):
|
||||
elif "Protocol error" in str(err):
|
||||
dev.close()
|
||||
raise ProtocolError("There was an unknown error in the protocol. Contact " + AUTHOR)
|
||||
raise ProtocolError("There was an unknown error in the"+\
|
||||
" protocol. Contact " + AUTHOR)
|
||||
dev.close()
|
||||
raise
|
||||
if not kwargs.has_key("end_session") or kwargs["end_session"]:
|
||||
dev._send_validated_command(EndSession())
|
||||
dev.send_validated_command(EndSession())
|
||||
return res
|
||||
|
||||
return run_session
|
||||
@ -187,62 +225,69 @@ class PRS500Device(object):
|
||||
def __init__(self, log_packets=False, report_progress=None) :
|
||||
"""
|
||||
@param log_packets: If true the packet stream to/from the device is logged
|
||||
@param report_progress: Function that is called with a % progress (number between 0 and 100) for various tasks
|
||||
If it is called with -1 that means that the task does not have any progress information
|
||||
@param report_progress: Function that is called with a % progress
|
||||
(number between 0 and 100) for various tasks
|
||||
If it is called with -1 that means that the
|
||||
task does not have any progress information
|
||||
"""
|
||||
self.device = self.device_descriptor.getDevice() #: The actual device (PyUSB object)
|
||||
self.handle = None #: Handle that is used to communicate with device. Setup in L{open}
|
||||
self._log_packets = log_packets
|
||||
Device.__init__(self)
|
||||
# The actual device (PyUSB object)
|
||||
self.device = self.device_descriptor.get_device()
|
||||
# Handle that is used to communicate with device. Setup in L{open}
|
||||
self.handle = None
|
||||
self.log_packets = log_packets
|
||||
self.report_progress = report_progress
|
||||
|
||||
def reconnect(self):
|
||||
self.device = self.device_descriptor.getDevice()
|
||||
""" Only recreates the device node and deleted the connection handle """
|
||||
self.device = self.device_descriptor.get_device()
|
||||
self.handle = None
|
||||
|
||||
@classmethod
|
||||
def is_connected(cls):
|
||||
"""
|
||||
This method checks to see whether the device is physically connected.
|
||||
It does not return any information about the validity of the software connection. You may need to call L{reconnect} if you keep
|
||||
It does not return any information about the validity of the
|
||||
software connection. You may need to call L{reconnect} if you keep
|
||||
getting L{DeviceError}.
|
||||
"""
|
||||
return cls.device_descriptor.getDevice() != None
|
||||
return cls.device_descriptor.get_device() != None
|
||||
|
||||
@classmethod
|
||||
def _validate_response(cls, res, type=0x00, number=0x00):
|
||||
""" Raise a ProtocolError if the type and number of C{res} is not the same as C{type} and C{number}. """
|
||||
if type != res.type or number != res.rnumber:
|
||||
raise ProtocolError("Inavlid response.\ntype: expected="+hex(type)+" actual="+hex(res.type)+
|
||||
"\nrnumber: expected="+hex(number)+" actual="+hex(res.rnumber))
|
||||
|
||||
def open(self) :
|
||||
"""
|
||||
Claim an interface on the device for communication. Requires write privileges to the device file.
|
||||
Also initialize the device. See the source code for the sequenceof initialization commands.
|
||||
Claim an interface on the device for communication.
|
||||
Requires write privileges to the device file.
|
||||
Also initialize the device.
|
||||
See the source code for the sequenceof initialization commands.
|
||||
|
||||
@todo: Implement unlocking of the device
|
||||
@todo: Check this on Mac OSX
|
||||
"""
|
||||
self.device = self.device_descriptor.getDevice()
|
||||
self.device = self.device_descriptor.get_device()
|
||||
if not self.device:
|
||||
raise DeviceError()
|
||||
try:
|
||||
self.handle = self.device.open()
|
||||
self.handle.claimInterface(self.device_descriptor.interface_id)
|
||||
except usb.USBError, e:
|
||||
print >>sys.stderr, e
|
||||
except usb.USBError, err:
|
||||
print >> sys.stderr, err
|
||||
raise DeviceBusy()
|
||||
res = self._send_validated_command(GetUSBProtocolVersion(), timeout=20000) # Large timeout as device may still be initializing
|
||||
if res.code != 0: raise ProtocolError("Unable to get USB Protocol version.")
|
||||
# Large timeout as device may still be initializing
|
||||
res = self.send_validated_command(GetUSBProtocolVersion(), timeout=20000)
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Unable to get USB Protocol version.")
|
||||
version = self._bulk_read(24, data_type=USBProtocolVersion)[0].version
|
||||
if version not in KNOWN_USB_PROTOCOL_VERSIONS:
|
||||
print >>sys.stderr, "WARNING: Usb protocol version " + hex(version) + " is unknown"
|
||||
res = self._send_validated_command(SetBulkSize(size=0x028000))
|
||||
if res.code != 0: raise ProtocolError("Unable to set bulk size.")
|
||||
self._send_validated_command(UnlockDevice(key=0x312d))
|
||||
print >> sys.stderr, "WARNING: Usb protocol version " + \
|
||||
hex(version) + " is unknown"
|
||||
res = self.send_validated_command(SetBulkSize(size=0x028000))
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Unable to set bulk size.")
|
||||
self.send_validated_command(UnlockDevice(key=0x312d))
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Unlocking of device not implemented. Remove locking and retry.")
|
||||
res = self._send_validated_command(SetTime())
|
||||
res = self.send_validated_command(SetTime())
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Could not set time on device")
|
||||
|
||||
@ -251,7 +296,8 @@ class PRS500Device(object):
|
||||
try:
|
||||
self.handle.reset()
|
||||
self.handle.releaseInterface()
|
||||
except: pass
|
||||
except Exception, err:
|
||||
print >> sys.stderr, err
|
||||
self.handle, self.device = None, None
|
||||
|
||||
def _send_command(self, command, response_type=Response, timeout=1000):
|
||||
@ -259,36 +305,49 @@ class PRS500Device(object):
|
||||
Send L{command<Command>} to device and return its L{response<Response>}.
|
||||
|
||||
@param command: an object of type Command or one of its derived classes
|
||||
@param response_type: an object of type 'type'. The return packet from the device is returned as an object of type response_type.
|
||||
@param timeout: the time to wait for a response from the device, in milliseconds. If there is no response, a L{usb.USBError} is raised.
|
||||
@param response_type: an object of type 'type'. The return packet
|
||||
from the device is returned as an object of type response_type.
|
||||
@param timeout: The time to wait for a response from the
|
||||
device, in milliseconds. If there is no response, a L{usb.USBError} is raised.
|
||||
"""
|
||||
if self._log_packets: _log_packet(command, "Command")
|
||||
if self.log_packets:
|
||||
self.log_packet(command, "Command")
|
||||
bytes_sent = self.handle.controlMsg(0x40, 0x80, command)
|
||||
if bytes_sent != len(command):
|
||||
raise ControlError(desc="Could not send control request to device\n" + str(query.query))
|
||||
response = response_type(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=timeout))
|
||||
if self._log_packets: _log_packet(response, "Response")
|
||||
raise ControlError(desc="Could not send control request to device\n"\
|
||||
+ str(command))
|
||||
response = response_type(self.handle.controlMsg(0xc0, 0x81, \
|
||||
Response.SIZE, timeout=timeout))
|
||||
if self.log_packets:
|
||||
self.log_packet(response, "Response")
|
||||
return response
|
||||
|
||||
def _send_validated_command(self, command, cnumber=None, response_type=Response, timeout=1000):
|
||||
def send_validated_command(self, command, cnumber=None, \
|
||||
response_type=Response, timeout=1000):
|
||||
"""
|
||||
Wrapper around L{_send_command} that checks if the C{Response.rnumber == cnumber or command.number if cnumber==None}. Also check that
|
||||
Wrapper around L{_send_command} that checks if the
|
||||
C{Response.rnumber == cnumber or
|
||||
command.number if cnumber==None}. Also check that
|
||||
C{Response.type == Command.type}.
|
||||
"""
|
||||
if cnumber == None: cnumber = command.number
|
||||
res = self._send_command(command, response_type=response_type, timeout=timeout)
|
||||
PRS500Device._validate_response(res, type=command.type, number=cnumber)
|
||||
if cnumber == None:
|
||||
cnumber = command.number
|
||||
res = self._send_command(command, response_type=response_type, \
|
||||
timeout=timeout)
|
||||
self.validate_response(res, _type=command.type, number=cnumber)
|
||||
return res
|
||||
|
||||
def _bulk_write(self, data, packet_size=0x1000):
|
||||
"""
|
||||
Send data to device via a bulk transfer.
|
||||
@type data: Any listable type supporting __getslice__
|
||||
@param packet_size: Size of packets to be sent to device. C{data} is broken up into packets to be sent to device.
|
||||
@param packet_size: Size of packets to be sent to device.
|
||||
C{data} is broken up into packets to be sent to device.
|
||||
"""
|
||||
def bulk_write_packet(packet):
|
||||
self.handle.bulkWrite(PRS500Device.PRS500_BULK_OUT_EP, packet)
|
||||
if self._log_packets: _log_packet(Answer(packet), "Answer h->d")
|
||||
if self.log_packets:
|
||||
self.log_packet(Answer(packet), "Answer h->d")
|
||||
|
||||
bytes_left = len(data)
|
||||
if bytes_left + 16 <= packet_size:
|
||||
@ -305,38 +364,52 @@ class PRS500Device(object):
|
||||
pos = first_packet.length
|
||||
bytes_left -= first_packet.length
|
||||
while bytes_left > 0:
|
||||
endpos = pos + packet_size if pos + packet_size <= len(data) else len(data)
|
||||
endpos = pos + packet_size if pos + packet_size <= len(data) \
|
||||
else len(data)
|
||||
bulk_write_packet(data[pos:endpos])
|
||||
bytes_left -= endpos - pos
|
||||
pos = endpos
|
||||
res = Response(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=5000))
|
||||
if self._log_packets: _log_packet(res, "Response")
|
||||
res = Response(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, \
|
||||
timeout=5000))
|
||||
if self.log_packets:
|
||||
self.log_packet(res, "Response")
|
||||
if res.rnumber != 0x10005 or res.code != 0:
|
||||
raise ProtocolError("Sending via Bulk Transfer failed with response:\n"+str(res))
|
||||
raise ProtocolError("Sending via Bulk Transfer failed with response:\n"\
|
||||
+str(res))
|
||||
if res.data_size != len(data):
|
||||
raise ProtocolError("Unable to transfer all data to device. Response packet:\n"+str(res))
|
||||
raise ProtocolError("Unable to transfer all data to device. Response packet:\n"\
|
||||
+str(res))
|
||||
|
||||
|
||||
def _bulk_read(self, bytes, command_number=0x00, packet_size=4096, data_type=Answer):
|
||||
def _bulk_read(self, bytes, command_number=0x00, packet_size=4096, \
|
||||
data_type=Answer):
|
||||
"""
|
||||
Read in C{bytes} bytes via a bulk transfer in packets of size S{<=} C{packet_size}
|
||||
@param data_type: an object of type type. The data packet is returned as an object of type C{data_type}.
|
||||
@return: A list of packets read from the device. Each packet is of type data_type
|
||||
Read in C{bytes} bytes via a bulk transfer in
|
||||
packets of size S{<=} C{packet_size}
|
||||
@param data_type: an object of type type.
|
||||
The data packet is returned as an object of type C{data_type}.
|
||||
@return: A list of packets read from the device.
|
||||
Each packet is of type data_type
|
||||
@todo: Figure out how to make bulk reads work in OSX
|
||||
"""
|
||||
def bulk_read_packet(data_type=Answer, size=0x1000):
|
||||
data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size))
|
||||
if self._log_packets: _log_packet(data, "Answer d->h")
|
||||
data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, \
|
||||
size))
|
||||
if self.log_packets:
|
||||
self.log_packet(data, "Answer d->h")
|
||||
return data
|
||||
|
||||
bytes_left = bytes
|
||||
packets = []
|
||||
while bytes_left > 0:
|
||||
if packet_size > bytes_left: packet_size = bytes_left
|
||||
if packet_size > bytes_left:
|
||||
packet_size = bytes_left
|
||||
packet = bulk_read_packet(data_type=data_type, size=packet_size)
|
||||
bytes_left -= len(packet)
|
||||
packets.append(packet)
|
||||
self._send_validated_command(AcknowledgeBulkRead(packets[0].number), cnumber=command_number)
|
||||
self.send_validated_command(\
|
||||
AcknowledgeBulkRead(packets[0].number), \
|
||||
cnumber=command_number)
|
||||
return packets
|
||||
|
||||
@safe
|
||||
@ -345,63 +418,85 @@ class PRS500Device(object):
|
||||
Ask device for device information. See L{DeviceInfoQuery}.
|
||||
@return: (device name, device version, software version on device, mime type)
|
||||
"""
|
||||
size = self._send_validated_command(DeviceInfoQuery()).data[2] + 16
|
||||
data = self._bulk_read(size, command_number=DeviceInfoQuery.NUMBER, data_type=DeviceInfo)[0]
|
||||
return (data.device_name, data.device_version, data.software_version, data.mime_type)
|
||||
size = self.send_validated_command(DeviceInfoQuery()).data[2] + 16
|
||||
data = self._bulk_read(size, command_number=\
|
||||
DeviceInfoQuery.NUMBER, data_type=DeviceInfo)[0]
|
||||
return (data.device_name, data.device_version, \
|
||||
data.software_version, data.mime_type)
|
||||
|
||||
@safe
|
||||
def path_properties(self, path, end_session=True):
|
||||
""" Send command asking device for properties of C{path}. Return L{FileProperties}. """
|
||||
res = self._send_validated_command(PathQuery(path), response_type=ListResponse)
|
||||
data = self._bulk_read(0x28, data_type=FileProperties, command_number=PathQuery.NUMBER)[0]
|
||||
if path.endswith("/"): path = path[:-1]
|
||||
if res.path_not_found : raise PathError(path + " does not exist on device")
|
||||
if res.is_invalid : raise PathError(path + " is not a valid path")
|
||||
if res.is_unmounted : raise PathError(path + " is not mounted")
|
||||
"""
|
||||
Send command asking device for properties of C{path}.
|
||||
Return L{FileProperties}.
|
||||
"""
|
||||
res = self.send_validated_command(PathQuery(path), \
|
||||
response_type=ListResponse)
|
||||
data = self._bulk_read(0x28, data_type=FileProperties, \
|
||||
command_number=PathQuery.NUMBER)[0]
|
||||
if path.endswith("/"):
|
||||
path = path[:-1]
|
||||
if res.path_not_found :
|
||||
raise PathError(path + " does not exist on device")
|
||||
if res.is_invalid:
|
||||
raise PathError(path + " is not a valid path")
|
||||
if res.is_unmounted:
|
||||
raise PathError(path + " is not mounted")
|
||||
if res.code not in (0, PathResponseCodes.IS_FILE):
|
||||
raise PathError(path + " has an unknown error. Code: " + hex(res.code))
|
||||
raise PathError(path + " has an unknown error. Code: " + \
|
||||
hex(res.code))
|
||||
return data
|
||||
|
||||
@safe
|
||||
def get_file(self, path, outfile, end_session=True):
|
||||
"""
|
||||
Read the file at path on the device and write it to outfile. For the logic see L{_get_file}.
|
||||
Read the file at path on the device and write it to outfile.
|
||||
|
||||
The data is fetched in chunks of size S{<=} 32K. Each chunk is make of packets of size S{<=} 4K. See L{FileOpen},
|
||||
The data is fetched in chunks of size S{<=} 32K. Each chunk is
|
||||
made of packets of size S{<=} 4K. See L{FileOpen},
|
||||
L{FileRead} and L{FileClose} for details on the command packets used.
|
||||
|
||||
@param outfile: file object like C{sys.stdout} or the result of an C{open} call
|
||||
"""
|
||||
if path.endswith("/"): path = path[:-1] # We only copy files
|
||||
file = self.path_properties(path, end_session=False)
|
||||
if file.is_dir: raise PathError("Cannot read as " + path + " is a directory")
|
||||
bytes = file.file_size
|
||||
res = self._send_validated_command(FileOpen(path))
|
||||
if path.endswith("/"):
|
||||
path = path[:-1] # We only copy files
|
||||
_file = self.path_properties(path, end_session=False)
|
||||
if _file.is_dir:
|
||||
raise PathError("Cannot read as " + path + " is a directory")
|
||||
bytes = _file.file_size
|
||||
res = self.send_validated_command(FileOpen(path))
|
||||
if res.code != 0:
|
||||
raise PathError("Unable to open " + path + " for reading. Response code: " + hex(res.code))
|
||||
id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id
|
||||
raise PathError("Unable to open " + path + \
|
||||
" for reading. Response code: " + hex(res.code))
|
||||
_id = self._bulk_read(20, data_type=IdAnswer, \
|
||||
command_number=FileOpen.NUMBER)[0].id
|
||||
bytes_left, chunk_size, pos = bytes, 0x8000, 0
|
||||
while bytes_left > 0:
|
||||
if chunk_size > bytes_left: chunk_size = bytes_left
|
||||
res = self._send_validated_command(FileIO(id, pos, chunk_size))
|
||||
if chunk_size > bytes_left:
|
||||
chunk_size = bytes_left
|
||||
res = self.send_validated_command(FileIO(_id, pos, chunk_size))
|
||||
if res.code != 0:
|
||||
self._send_validated_command(FileClose(id))
|
||||
raise ProtocolError("Error while reading from " + path + ". Response code: " + hex(res.code))
|
||||
packets = self._bulk_read(chunk_size+16, command_number=FileIO.RNUMBER, packet_size=4096)
|
||||
self.send_validated_command(FileClose(id))
|
||||
raise ProtocolError("Error while reading from " + path + \
|
||||
". Response code: " + hex(res.code))
|
||||
packets = self._bulk_read(chunk_size+16, \
|
||||
command_number=FileIO.RNUMBER, packet_size=4096)
|
||||
try:
|
||||
array('B', packets[0][16:]).tofile(outfile) # The first 16 bytes are meta information on the packet stream
|
||||
# The first 16 bytes are meta information on the packet stream
|
||||
array('B', packets[0][16:]).tofile(outfile)
|
||||
for i in range(1, len(packets)):
|
||||
array('B', packets[i]).tofile(outfile)
|
||||
except IOError, e:
|
||||
self._send_validated_command(FileClose(id))
|
||||
raise ArgumentError("File get operation failed. Could not write to local location: " + str(e))
|
||||
except IOError, err:
|
||||
self.send_validated_command(FileClose(_id))
|
||||
raise ArgumentError("File get operation failed. " + \
|
||||
"Could not write to local location: " + str(err))
|
||||
bytes_left -= chunk_size
|
||||
pos += chunk_size
|
||||
if self.report_progress: self.report_progress(int(100*((1.*pos)/bytes)))
|
||||
self._send_validated_command(FileClose(id))
|
||||
# Not going to check response code to see if close was successful as there's not much we can do if it wasnt
|
||||
|
||||
|
||||
if self.report_progress:
|
||||
self.report_progress(int(100*((1.*pos)/bytes)))
|
||||
self.send_validated_command(FileClose(_id))
|
||||
# Not going to check response code to see if close was successful
|
||||
# as there's not much we can do if it wasnt
|
||||
|
||||
@safe
|
||||
def list(self, path, recurse=False, end_session=True):
|
||||
@ -413,13 +508,18 @@ class PRS500Device(object):
|
||||
@param path: The path to list
|
||||
@type recurse: boolean
|
||||
@param recurse: If true do a recursive listing
|
||||
@return: A list of tuples. The first element of each tuple is a path. The second element is a list of L{Files<File>}.
|
||||
The path is the path we are listing, the C{Files} are the files/directories in that path. If it is a recursive
|
||||
list, then the first element will be (C{path}, children), the next will be (child, its children) and so on. If it
|
||||
is not recursive the length of the outermost list will be 1.
|
||||
@return: A list of tuples. The first element of each tuple is a path.
|
||||
The second element is a list of L{Files<File>}.
|
||||
The path is the path we are listing, the C{Files} are the
|
||||
files/directories in that path. If it is a recursive list, then the first
|
||||
element will be (C{path}, children), the next will be
|
||||
(child, its children) and so on. If it is not recursive the length of the
|
||||
outermost list will be 1.
|
||||
"""
|
||||
def _list(path): # Do a non recursive listsing of path
|
||||
if not path.endswith("/"): path += "/" # Initially assume path is a directory
|
||||
def _list(path):
|
||||
""" Do a non recursive listsing of path """
|
||||
if not path.endswith("/"):
|
||||
path += "/" # Initially assume path is a directory
|
||||
files = []
|
||||
candidate = self.path_properties(path, end_session=False)
|
||||
if not candidate.is_dir:
|
||||
@ -428,23 +528,31 @@ class PRS500Device(object):
|
||||
files = [ File((path, data)) ]
|
||||
else:
|
||||
# Get query ID used to ask for next element in list
|
||||
res = self._send_validated_command(DirOpen(path))
|
||||
res = self.send_validated_command(DirOpen(path))
|
||||
if res.code != 0:
|
||||
raise PathError("Unable to open directory " + path + " for reading. Response code: " + hex(res.code))
|
||||
id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id
|
||||
raise PathError("Unable to open directory " + path + \
|
||||
" for reading. Response code: " + hex(res.code))
|
||||
_id = self._bulk_read(0x14, data_type=IdAnswer, \
|
||||
command_number=DirOpen.NUMBER)[0].id
|
||||
# Create command asking for next element in list
|
||||
next = DirRead(id)
|
||||
next = DirRead(_id)
|
||||
items = []
|
||||
while True:
|
||||
res = self._send_validated_command(next, response_type=ListResponse)
|
||||
res = self.send_validated_command(next, response_type=ListResponse)
|
||||
size = res.data_size + 16
|
||||
data = self._bulk_read(size, data_type=ListAnswer, command_number=DirRead.NUMBER)[0]
|
||||
# path_not_found seems to happen if the usb server doesn't have the permissions to access the directory
|
||||
if res.is_eol or res.path_not_found: break
|
||||
data = self._bulk_read(size, data_type=ListAnswer, \
|
||||
command_number=DirRead.NUMBER)[0]
|
||||
# path_not_found seems to happen if the usb server
|
||||
# doesn't have the permissions to access the directory
|
||||
if res.is_eol or res.path_not_found:
|
||||
break
|
||||
elif res.code != 0:
|
||||
raise ProtocolError("Unknown error occured while reading contents of directory " + path + ". Response code: " + haex(res.code))
|
||||
raise ProtocolError("Unknown error occured while "+\
|
||||
"reading contents of directory " + path + \
|
||||
". Response code: " + hex(res.code))
|
||||
items.append(data.name)
|
||||
self._send_validated_command(DirClose(id)) # Ignore res.code as we cant do anything if close fails
|
||||
self.send_validated_command(DirClose(_id))
|
||||
# Ignore res.code as we cant do anything if close fails
|
||||
for item in items:
|
||||
ipath = path + item
|
||||
data = self.path_properties(ipath, end_session=False)
|
||||
@ -455,9 +563,9 @@ class PRS500Device(object):
|
||||
files = _list(path)
|
||||
dirs = [(path, files)]
|
||||
|
||||
for file in files:
|
||||
if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")):
|
||||
dirs[len(dirs):] = self.list(file.path, recurse=True, end_session=False)
|
||||
for _file in files:
|
||||
if recurse and _file.is_dir and not _file.path.startswith(("/dev","/proc")):
|
||||
dirs[len(dirs):] = self.list(_file.path, recurse=True, end_session=False)
|
||||
return dirs
|
||||
|
||||
@safe
|
||||
@ -472,9 +580,12 @@ class PRS500Device(object):
|
||||
"""
|
||||
data = []
|
||||
for path in ("/Data/", "a:/", "b:/"):
|
||||
res = self._send_validated_command(TotalSpaceQuery(path),timeout=5000) # Timeout needs to be increased as it takes time to read card
|
||||
# Timeout needs to be increased as it takes time to read card
|
||||
res = self.send_validated_command(TotalSpaceQuery(path), \
|
||||
timeout=5000)
|
||||
buffer_size = 16 + res.data[2]
|
||||
pkt = self._bulk_read(buffer_size, data_type=TotalSpaceAnswer, command_number=TotalSpaceQuery.NUMBER)[0]
|
||||
pkt = self._bulk_read(buffer_size, data_type=TotalSpaceAnswer, \
|
||||
command_number=TotalSpaceQuery.NUMBER)[0]
|
||||
data.append( pkt.total )
|
||||
return data
|
||||
|
||||
@ -490,8 +601,12 @@ class PRS500Device(object):
|
||||
"""
|
||||
data = []
|
||||
for path in ("/", "a:/", "b:/"):
|
||||
res = self._send_validated_command(FreeSpaceQuery(path),timeout=5000) # Timeout needs to be increased as it takes time to read card
|
||||
pkt = self._bulk_read(FreeSpaceAnswer.SIZE, data_type=FreeSpaceAnswer, command_number=FreeSpaceQuery.NUMBER)[0]
|
||||
# Timeout needs to be increased as it takes time to read card
|
||||
self.send_validated_command(FreeSpaceQuery(path), \
|
||||
timeout=5000)
|
||||
pkt = self._bulk_read(FreeSpaceAnswer.SIZE, \
|
||||
data_type=FreeSpaceAnswer, \
|
||||
command_number=FreeSpaceQuery.NUMBER)[0]
|
||||
data.append( pkt.free )
|
||||
return data
|
||||
|
||||
@ -500,8 +615,9 @@ class PRS500Device(object):
|
||||
dest = None
|
||||
try:
|
||||
dest = self.path_properties(path, end_session=False)
|
||||
except PathError, e:
|
||||
if "does not exist" in str(e) or "not mounted" in str(e): return (False, None)
|
||||
except PathError, err:
|
||||
if "does not exist" in str(err) or "not mounted" in str(err):
|
||||
return (False, None)
|
||||
else: raise
|
||||
return (True, dest)
|
||||
|
||||
@ -509,23 +625,28 @@ class PRS500Device(object):
|
||||
def touch(self, path, end_session=True):
|
||||
"""
|
||||
Create a file at path
|
||||
@todo: Update file modification time if it exists. Opening the file in write mode and then closing it doesn't work.
|
||||
@todo: Update file modification time if it exists.
|
||||
Opening the file in write mode and then closing it doesn't work.
|
||||
"""
|
||||
if path.endswith("/") and len(path) > 1: path = path[:-1]
|
||||
exists, file = self._exists(path)
|
||||
if exists and file.is_dir:
|
||||
if path.endswith("/") and len(path) > 1:
|
||||
path = path[:-1]
|
||||
exists, _file = self._exists(path)
|
||||
if exists and _file.is_dir:
|
||||
raise PathError("Cannot touch directories")
|
||||
if not exists:
|
||||
res = self._send_validated_command(FileCreate(path))
|
||||
res = self.send_validated_command(FileCreate(path))
|
||||
if res.code != 0:
|
||||
raise PathError("Could not create file " + path + ". Response code: " + str(hex(res.code)))
|
||||
raise PathError("Could not create file " + path + \
|
||||
". Response code: " + str(hex(res.code)))
|
||||
|
||||
@safe
|
||||
def put_file(self, infile, path, replace_file=False, end_session=True):
|
||||
"""
|
||||
Put infile onto the devoce at path
|
||||
@param infile: An open file object. infile must have a name attribute. If you are using a StringIO object set its name attribute manually.
|
||||
@param path: The path on the device at which to put infile. It should point to an existing directory.
|
||||
@param infile: An open file object. infile must have a name attribute.
|
||||
If you are using a StringIO object set its name attribute manually.
|
||||
@param path: The path on the device at which to put infile.
|
||||
It should point to an existing directory.
|
||||
@param replace_file: If True and path points to a file that already exists, it is replaced
|
||||
"""
|
||||
pos = infile.tell()
|
||||
@ -536,22 +657,27 @@ class PRS500Device(object):
|
||||
exists, dest = self._exists(path)
|
||||
if exists:
|
||||
if dest.is_dir:
|
||||
if not path.endswith("/"): path += "/"
|
||||
if not path.endswith("/"):
|
||||
path += "/"
|
||||
path += os.path.basename(infile.name)
|
||||
return self.put_file(infile, path, replace_file=replace_file, end_session=False)
|
||||
else:
|
||||
if not replace_file: raise PathError("Cannot write to " + path + " as it already exists")
|
||||
file = self.path_properties(path, end_session=False)
|
||||
if file.file_size > bytes:
|
||||
if not replace_file:
|
||||
raise PathError("Cannot write to " + \
|
||||
path + " as it already exists")
|
||||
_file = self.path_properties(path, end_session=False)
|
||||
if _file.file_size > bytes:
|
||||
self.del_file(path, end_session=False)
|
||||
self.touch(path, end_session=False)
|
||||
else: self.touch(path, end_session=False)
|
||||
chunk_size = 0x8000
|
||||
data_left = True
|
||||
res = self._send_validated_command(FileOpen(path, mode=FileOpen.WRITE))
|
||||
res = self.send_validated_command(FileOpen(path, mode=FileOpen.WRITE))
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Unable to open " + path + " for writing. Response code: " + hex(res.code))
|
||||
id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id
|
||||
raise ProtocolError("Unable to open " + path + \
|
||||
" for writing. Response code: " + hex(res.code))
|
||||
_id = self._bulk_read(20, data_type=IdAnswer, \
|
||||
command_number=FileOpen.NUMBER)[0].id
|
||||
|
||||
while data_left:
|
||||
data = array('B')
|
||||
@ -559,35 +685,47 @@ class PRS500Device(object):
|
||||
data.fromfile(infile, chunk_size)
|
||||
except EOFError:
|
||||
data_left = False
|
||||
res = self._send_validated_command(FileIO(id, pos, len(data), mode=FileIO.WNUMBER))
|
||||
res = self.send_validated_command(FileIO(_id, pos, len(data), \
|
||||
mode=FileIO.WNUMBER))
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Unable to write to " + path + ". Response code: " + hex(res.code))
|
||||
raise ProtocolError("Unable to write to " + \
|
||||
path + ". Response code: " + hex(res.code))
|
||||
self._bulk_write(data)
|
||||
pos += len(data)
|
||||
if self.report_progress:
|
||||
self.report_progress( int(100*(pos-start_pos)/(1.*bytes)) )
|
||||
self._send_validated_command(FileClose(id)) # Ignore res.code as cant do anything if close fails
|
||||
file = self.path_properties(path, end_session=False)
|
||||
if file.file_size != pos:
|
||||
raise ProtocolError("Copying to device failed. The file on the device is larger by " + str(file.file_size - pos) + " bytes")
|
||||
self.send_validated_command(FileClose(_id))
|
||||
# Ignore res.code as cant do anything if close fails
|
||||
_file = self.path_properties(path, end_session=False)
|
||||
if _file.file_size != pos:
|
||||
raise ProtocolError("Copying to device failed. The file " +\
|
||||
"on the device is larger by " + \
|
||||
str(_file.file_size - pos) + " bytes")
|
||||
|
||||
@safe
|
||||
def del_file(self, path, end_session=True):
|
||||
""" Delete C{path} from device iff path is a file """
|
||||
data = self.path_properties(path, end_session=False)
|
||||
if data.is_dir: raise PathError("Cannot delete directories")
|
||||
res = self._send_validated_command(FileDelete(path), response_type=ListResponse)
|
||||
if data.is_dir:
|
||||
raise PathError("Cannot delete directories")
|
||||
res = self.send_validated_command(FileDelete(path), \
|
||||
response_type=ListResponse)
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Unable to delete " + path + " with response:\n" + str(res))
|
||||
raise ProtocolError("Unable to delete " + path + \
|
||||
" with response:\n" + str(res))
|
||||
|
||||
@safe
|
||||
def mkdir(self, path, end_session=True):
|
||||
if not path.endswith("/"): path += "/"
|
||||
""" Make directory """
|
||||
if not path.endswith("/"):
|
||||
path += "/"
|
||||
error_prefix = "Cannot create directory " + path
|
||||
res = self._send_validated_command(DirCreate(path)).data[0]
|
||||
res = self.send_validated_command(DirCreate(path)).data[0]
|
||||
if res == 0xffffffcc:
|
||||
raise PathError(error_prefix + " as it already exists")
|
||||
elif res == PathResponseCodes.NOT_FOUND:
|
||||
raise PathError(error_prefix + " as " + path[0:path[:-1].rfind("/")] + " does not exist ")
|
||||
raise PathError(error_prefix + " as " + \
|
||||
path[0:path[:-1].rfind("/")] + " does not exist ")
|
||||
elif res == PathResponseCodes.INVALID:
|
||||
raise PathError(error_prefix + " as " + path + " is invalid")
|
||||
elif res != 0:
|
||||
@ -600,56 +738,73 @@ class PRS500Device(object):
|
||||
if not dir.is_dir:
|
||||
self.del_file(path, end_session=False)
|
||||
else:
|
||||
if not path.endswith("/"): path += "/"
|
||||
res = self._send_validated_command(DirDelete(path))
|
||||
if not path.endswith("/"):
|
||||
path += "/"
|
||||
res = self.send_validated_command(DirDelete(path))
|
||||
if res.code == PathResponseCodes.HAS_CHILDREN:
|
||||
raise PathError("Cannot delete directory " + path + " as it is not empty")
|
||||
raise PathError("Cannot delete directory " + path + \
|
||||
" as it is not empty")
|
||||
if res.code != 0:
|
||||
raise ProtocolError("Failed to delete directory " + path + ". Response code: " + hex(res.code))
|
||||
raise ProtocolError("Failed to delete directory " + path + \
|
||||
". Response code: " + hex(res.code))
|
||||
|
||||
@safe
|
||||
def card(self, end_session=True):
|
||||
""" Return path prefix to installed card or None """
|
||||
card = None
|
||||
if self._exists("a:/")[0]: card = "a:"
|
||||
if self._exists("b:/")[0]: card = "b:"
|
||||
if self._exists("a:/")[0]:
|
||||
card = "a:"
|
||||
if self._exists("b:/")[0]:
|
||||
card = "b:"
|
||||
return card
|
||||
|
||||
@safe
|
||||
def books(self, oncard=False, end_session=True):
|
||||
"""
|
||||
Return a list of ebooks on the device.
|
||||
@param oncard: If True return a list of ebookson the storage card, otherwise return list of ebooks in main memory of device
|
||||
@param oncard: If True return a list of ebooks on the storage card,
|
||||
otherwise return list of ebooks in main memory of device
|
||||
|
||||
@return: L{BookList}
|
||||
"""
|
||||
root = "/Data/media/"
|
||||
prefix = "xs1:"
|
||||
file = TemporaryFile()
|
||||
tfile = TemporaryFile()
|
||||
if oncard:
|
||||
prefix = ""
|
||||
try:
|
||||
self.get_file("a:"+self.CACHE_XML, file, end_session=False)
|
||||
self.get_file("a:"+self.CACHE_XML, tfile, end_session=False)
|
||||
root = "a:/"
|
||||
except PathError:
|
||||
try:
|
||||
self.get_file("b:"+self.CACHE_XML, file, end_session=False)
|
||||
self.get_file("b:"+self.CACHE_XML, tfile, end_session=False)
|
||||
root = "b:/"
|
||||
except PathError: pass
|
||||
if file.tell() == 0: file = None
|
||||
else: self.get_file(self.MEDIA_XML, file, end_session=False)
|
||||
return BookList(prefix=prefix, root=root, file=file)
|
||||
if tfile.tell() == 0:
|
||||
tfile = None
|
||||
else:
|
||||
self.get_file(self.MEDIA_XML, tfile, end_session=False)
|
||||
return BookList(prefix=prefix, root=root, sfile=tfile)
|
||||
|
||||
@safe
|
||||
def add_book(self, infile, name, info, booklists, oncard=False, sync_booklists=False, end_session=True):
|
||||
def add_book(self, infile, name, info, booklists, oncard=False, \
|
||||
sync_booklists=False, end_session=True):
|
||||
"""
|
||||
Add a book to the device. If oncard is True then the book is copied to the card rather than main memory.
|
||||
Add a book to the device. If oncard is True then the book is copied
|
||||
to the card rather than main memory.
|
||||
|
||||
@param infile: The source file, should be opened in "rb" mode
|
||||
@param name: The name of the book file when uploaded to the device. The extension of name must be one of the supported formats for this device.
|
||||
@param name: The name of the book file when uploaded to the
|
||||
device. The extension of name must be one of
|
||||
the supported formats for this device.
|
||||
@param info: A dictionary that must have the keys "title", "authors", "cover".
|
||||
C{info["cover"]} should be a three element tuple (width, height, data) where data is the image data in JPEG format as a string
|
||||
@param booklists: A tuple containing the result of calls to (L{books}(oncard=False), L{books}(oncard=True)).
|
||||
@todo: Implement syncing the booklists to the device. This would mean juggling with the nextId attribute in media.xml and renumbering ids in cache.xml?
|
||||
C{info["cover"]} should be a three element tuple (width, height, data)
|
||||
where data is the image data in JPEG format as a string
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
(L{books}(oncard=False), L{books}(oncard=True)).
|
||||
@todo: Implement syncing the booklists to the device.
|
||||
This would mean juggling with the nextId attribute in
|
||||
media.xml and renumbering ids in cache.xml?
|
||||
"""
|
||||
infile.seek(0, 2)
|
||||
size = infile.tell()
|
||||
@ -658,16 +813,21 @@ class PRS500Device(object):
|
||||
space = self.free_space(end_session=False)
|
||||
mspace = space[0]
|
||||
cspace = space[1] if space[1] >= space[2] else space[2]
|
||||
if oncard and size > cspace - 1024*1024: raise FreeSpaceError("There is insufficient free space on the storage card")
|
||||
if not oncard and size > mspace - 1024*1024: raise FreeSpaceError("There is insufficient free space in main memory")
|
||||
if oncard and size > cspace - 1024*1024:
|
||||
raise FreeSpaceError("There is insufficient free space "+\
|
||||
"on the storage card")
|
||||
if not oncard and size > mspace - 1024*1024:
|
||||
raise FreeSpaceError("There is insufficient free space " +\
|
||||
"in main memory")
|
||||
prefix = "/Data/media/"
|
||||
if oncard: prefix = card + "/"
|
||||
if oncard:
|
||||
prefix = card + "/"
|
||||
else: name = "books/"+name
|
||||
path = prefix + name
|
||||
self.put_file(infile, path, end_session=False)
|
||||
ctime = self.path_properties(path, end_session=False).ctime
|
||||
bl = booklists[1] if oncard else booklists[0]
|
||||
bl.add_book(info, name, size, ctime)
|
||||
bkl = booklists[1] if oncard else booklists[0]
|
||||
bkl.add_book(info, name, size, ctime)
|
||||
fix_ids(booklists[0], booklists[1])
|
||||
if sync_booklists:
|
||||
self.upload_book_list(booklists[0], end_session=False)
|
||||
@ -676,11 +836,14 @@ class PRS500Device(object):
|
||||
|
||||
@safe
|
||||
def upload_book_list(self, booklist, end_session=True):
|
||||
if not len(booklist): raise ArgumentError("booklist is empty")
|
||||
if not len(booklist):
|
||||
raise ArgumentError("booklist is empty")
|
||||
path = self.MEDIA_XML
|
||||
if not booklist.prefix:
|
||||
card = self.card(end_session=True)
|
||||
if not card: raise ArgumentError("Cannot upload list to card as card is not present")
|
||||
if not card:
|
||||
raise ArgumentError("Cannot upload list to card as "+\
|
||||
"card is not present")
|
||||
path = card + self.CACHE_XML
|
||||
f = TemporaryFile()
|
||||
booklist.write(f)
|
||||
|
@ -17,7 +17,6 @@ Defines the errors that libprs500 generates.
|
||||
|
||||
G{classtree ProtocolError}
|
||||
"""
|
||||
from exceptions import Exception
|
||||
|
||||
class ProtocolError(Exception):
|
||||
""" The base class for all exceptions in this package """
|
||||
@ -27,12 +26,15 @@ class ProtocolError(Exception):
|
||||
class TimeoutError(ProtocolError):
|
||||
""" There was a timeout during communication """
|
||||
def __init__(self, func_name):
|
||||
ProtocolError.__init__(self, "There was a timeout while communicating with the device in function: "+func_name)
|
||||
ProtocolError.__init__(self, \
|
||||
"There was a timeout while communicating with the device in function: "\
|
||||
+func_name)
|
||||
|
||||
class DeviceError(ProtocolError):
|
||||
""" Raised when device is not found """
|
||||
def __init__(self):
|
||||
ProtocolError.__init__(self, "Unable to find SONY Reader. Is it connected?")
|
||||
ProtocolError.__init__(self, \
|
||||
"Unable to find SONY Reader. Is it connected?")
|
||||
|
||||
class DeviceBusy(ProtocolError):
|
||||
""" Raised when device is busy """
|
||||
@ -56,7 +58,7 @@ class ControlError(ProtocolError):
|
||||
def __init__(self, query=None, response=None, desc=None):
|
||||
self.query = query
|
||||
self.response = response
|
||||
Exception.__init__(self, desc)
|
||||
ProtocolError.__init__(self, desc)
|
||||
|
||||
def __str__(self):
|
||||
if self.query and self.response:
|
||||
|
@ -487,4 +487,5 @@ def main():
|
||||
lock = LockFile(lock)
|
||||
return app.exec_()
|
||||
|
||||
if __name__ == "__main__": sys.exit(main())
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
@ -12,19 +12,23 @@
|
||||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""
|
||||
Defines the structure of packets that are sent to/received from the device.
|
||||
|
||||
Packet structure is defined using classes and inheritance. Each class is a view that imposes
|
||||
structure on the underlying data buffer. The data buffer is encoded in little-endian format, but you don't
|
||||
have to worry about that if you are using the classes. The classes have instance variables with getter/setter functions defined
|
||||
to take care of the encoding/decoding. The classes are intended to mimic C structs.
|
||||
Packet structure is defined using classes and inheritance. Each class is a
|
||||
view that imposes structure on the underlying data buffer.
|
||||
The data buffer is encoded in little-endian format, but you don't
|
||||
have to worry about that if you are using the classes.
|
||||
The classes have instance variables with getter/setter functions defined
|
||||
to take care of the encoding/decoding.
|
||||
The classes are intended to mimic C structs.
|
||||
|
||||
There are three kinds of packets. L{Commands<Command>}, L{Responses<Response>}, and L{Answers<Answer>}.
|
||||
C{Commands} are sent to the device on the control bus, C{Responses} are received from the device,
|
||||
also on the control bus. C{Answers} and their sub-classes represent data packets sent to/received from
|
||||
the device via bulk transfers.
|
||||
There are three kinds of packets. L{Commands<Command>},
|
||||
L{Responses<Response>}, and L{Answers<Answer>}.
|
||||
C{Commands} are sent to the device on the control bus,
|
||||
C{Responses} are received from the device,
|
||||
also on the control bus. C{Answers} and their sub-classes represent
|
||||
data packets sent to/received from the device via bulk transfers.
|
||||
|
||||
Commands are organized as follows: G{classtree Command}
|
||||
|
||||
@ -37,8 +41,10 @@ Responses inherit Command as they share header structure.
|
||||
Answers are organized as follows: G{classtree Answer}
|
||||
"""
|
||||
|
||||
import struct, time
|
||||
from errors import PacketError
|
||||
import struct
|
||||
import time
|
||||
|
||||
from libprs500.errors import PacketError
|
||||
|
||||
DWORD = "<I" #: Unsigned integer little endian encoded in 4 bytes
|
||||
DDWORD = "<Q" #: Unsigned long long little endian encoded in 8 bytes
|
||||
@ -156,7 +162,8 @@ class field(object):
|
||||
def __init__(self, start=16, fmt=DWORD):
|
||||
"""
|
||||
@param start: The byte at which this field is stored in the buffer
|
||||
@param fmt: The packing format for this field. See U{struct<http://docs.python.org/lib/module-struct.html>}.
|
||||
@param fmt: The packing format for this field.
|
||||
See U{struct<http://docs.python.org/lib/module-struct.html>}.
|
||||
"""
|
||||
self._fmt, self._start = fmt, start
|
||||
|
||||
@ -168,9 +175,13 @@ class field(object):
|
||||
|
||||
def __repr__(self):
|
||||
typ = ""
|
||||
if self._fmt == DWORD: typ = "unsigned int"
|
||||
if self._fmt == DDWORD: typ = "unsigned long long"
|
||||
return "An " + typ + " stored in " + str(struct.calcsize(self._fmt)) + " bytes starting at byte " + str(self._start)
|
||||
if self._fmt == DWORD:
|
||||
typ = "unsigned int"
|
||||
if self._fmt == DDWORD:
|
||||
typ = "unsigned long long"
|
||||
return "An " + typ + " stored in " + \
|
||||
str(struct.calcsize(self._fmt)) + \
|
||||
" bytes starting at byte " + str(self._start)
|
||||
|
||||
class stringfield(object):
|
||||
""" A field storing a variable length string. """
|
||||
@ -188,7 +199,7 @@ class stringfield(object):
|
||||
return obj.unpack(start=self._start, fmt="<"+length+"s")[0]
|
||||
|
||||
def __set__(self, obj, val):
|
||||
if val.__class__.__name__ != 'str': val = str(val)
|
||||
val = str(val)
|
||||
obj.pack(val, start=self._start, fmt="<"+str(len(val))+"s")
|
||||
|
||||
def __repr__(self):
|
||||
@ -197,107 +208,109 @@ class stringfield(object):
|
||||
class Command(TransferBuffer):
|
||||
|
||||
""" Defines the structure of command packets sent to the device. """
|
||||
|
||||
# Command number. C{unsigned int} stored in 4 bytes at byte 0.
|
||||
#
|
||||
# Command numbers are:
|
||||
# 0 GetUsbProtocolVersion
|
||||
# 1 ReqEndSession
|
||||
# 10 FskFileOpen
|
||||
# 11 FskFileClose
|
||||
# 12 FskGetSize
|
||||
# 13 FskSetSize
|
||||
# 14 FskFileSetPosition
|
||||
# 15 FskGetPosition
|
||||
# 16 FskFileRead
|
||||
# 17 FskFileWrite
|
||||
# 18 FskFileGetFileInfo
|
||||
# 19 FskFileSetFileInfo
|
||||
# 1A FskFileCreate
|
||||
# 1B FskFileDelete
|
||||
# 1C FskFileRename
|
||||
# 30 FskFileCreateDirectory
|
||||
# 31 FskFileDeleteDirectory
|
||||
# 32 FskFileRenameDirectory
|
||||
# 33 FskDirectoryIteratorNew
|
||||
# 34 FskDirectoryIteratorDispose
|
||||
# 35 FskDirectoryIteratorGetNext
|
||||
# 52 FskVolumeGetInfo
|
||||
# 53 FskVolumeGetInfoFromPath
|
||||
# 80 FskFileTerminate
|
||||
# 100 ConnectDevice
|
||||
# 101 GetProperty
|
||||
# 102 GetMediaInfo
|
||||
# 103 GetFreeSpace
|
||||
# 104 SetTime
|
||||
# 105 DeviceBeginEnd
|
||||
# 106 UnlockDevice
|
||||
# 107 SetBulkSize
|
||||
# 110 GetHttpRequest
|
||||
# 111 SetHttpRespponse
|
||||
# 112 Needregistration
|
||||
# 114 GetMarlinState
|
||||
# 200 ReqDiwStart
|
||||
# 201 SetDiwPersonalkey
|
||||
# 202 GetDiwPersonalkey
|
||||
# 203 SetDiwDhkey
|
||||
# 204 GetDiwDhkey
|
||||
# 205 SetDiwChallengeserver
|
||||
# 206 GetDiwChallengeserver
|
||||
# 207 GetDiwChallengeclient
|
||||
# 208 SetDiwChallengeclient
|
||||
# 209 GetDiwVersion
|
||||
# 20A SetDiwWriteid
|
||||
# 20B GetDiwWriteid
|
||||
# 20C SetDiwSerial
|
||||
# 20D GetDiwModel
|
||||
# 20C SetDiwSerial
|
||||
# 20E GetDiwDeviceid
|
||||
# 20F GetDiwSerial
|
||||
# 210 ReqDiwCheckservicedata
|
||||
# 211 ReqDiwCheckiddata
|
||||
# 212 ReqDiwCheckserialdata
|
||||
# 213 ReqDiwFactoryinitialize
|
||||
# 214 GetDiwMacaddress
|
||||
# 215 ReqDiwTest
|
||||
# 216 ReqDiwDeletekey
|
||||
# 300 UpdateChangemode
|
||||
# 301 UpdateDeletePartition
|
||||
# 302 UpdateCreatePartition
|
||||
# 303 UpdateCreatePartitionWithImage
|
||||
# 304 UpdateGetPartitionSize
|
||||
number = field(start=0, fmt=DWORD)
|
||||
"""
|
||||
Command number. C{unsigned int} stored in 4 bytes at byte 0.
|
||||
|
||||
Command numbers are:
|
||||
0 GetUsbProtocolVersion
|
||||
1 ReqEndSession
|
||||
10 FskFileOpen
|
||||
11 FskFileClose
|
||||
12 FskGetSize
|
||||
13 FskSetSize
|
||||
14 FskFileSetPosition
|
||||
15 FskGetPosition
|
||||
16 FskFileRead
|
||||
17 FskFileWrite
|
||||
18 FskFileGetFileInfo
|
||||
19 FskFileSetFileInfo
|
||||
1A FskFileCreate
|
||||
1B FskFileDelete
|
||||
1C FskFileRename
|
||||
30 FskFileCreateDirectory
|
||||
31 FskFileDeleteDirectory
|
||||
32 FskFileRenameDirectory
|
||||
33 FskDirectoryIteratorNew
|
||||
34 FskDirectoryIteratorDispose
|
||||
35 FskDirectoryIteratorGetNext
|
||||
52 FskVolumeGetInfo
|
||||
53 FskVolumeGetInfoFromPath
|
||||
80 FskFileTerminate
|
||||
100 ConnectDevice
|
||||
101 GetProperty
|
||||
102 GetMediaInfo
|
||||
103 GetFreeSpace
|
||||
104 SetTime
|
||||
105 DeviceBeginEnd
|
||||
106 UnlockDevice
|
||||
107 SetBulkSize
|
||||
110 GetHttpRequest
|
||||
111 SetHttpRespponse
|
||||
112 Needregistration
|
||||
114 GetMarlinState
|
||||
200 ReqDiwStart
|
||||
201 SetDiwPersonalkey
|
||||
202 GetDiwPersonalkey
|
||||
203 SetDiwDhkey
|
||||
204 GetDiwDhkey
|
||||
205 SetDiwChallengeserver
|
||||
206 GetDiwChallengeserver
|
||||
207 GetDiwChallengeclient
|
||||
208 SetDiwChallengeclient
|
||||
209 GetDiwVersion
|
||||
20A SetDiwWriteid
|
||||
20B GetDiwWriteid
|
||||
20C SetDiwSerial
|
||||
20D GetDiwModel
|
||||
20C SetDiwSerial
|
||||
20E GetDiwDeviceid
|
||||
20F GetDiwSerial
|
||||
210 ReqDiwCheckservicedata
|
||||
211 ReqDiwCheckiddata
|
||||
212 ReqDiwCheckserialdata
|
||||
213 ReqDiwFactoryinitialize
|
||||
214 GetDiwMacaddress
|
||||
215 ReqDiwTest
|
||||
216 ReqDiwDeletekey
|
||||
300 UpdateChangemode
|
||||
301 UpdateDeletePartition
|
||||
302 UpdateCreatePartition
|
||||
303 UpdateCreatePartitionWithImage
|
||||
304 UpdateGetPartitionSize
|
||||
"""
|
||||
|
||||
type = field(start=4, fmt=DDWORD) #: Known types are 0x00 and 0x01. Acknowledge commands are always type 0x00
|
||||
|
||||
length = field(start=12, fmt=DWORD) #: Length of the data part of this packet
|
||||
# Known types are 0x00 and 0x01. Acknowledge commands are always type 0x00
|
||||
type = field(start=4, fmt=DDWORD)
|
||||
# Length of the data part of this packet
|
||||
length = field(start=12, fmt=DWORD)
|
||||
|
||||
@apply
|
||||
def data():
|
||||
doc = \
|
||||
"""
|
||||
The data part of this command. Returned/set as/by a TransferBuffer. Stored at byte 16.
|
||||
The data part of this command. Returned/set as/by a TransferBuffer.
|
||||
Stored at byte 16.
|
||||
|
||||
Setting it by default changes self.length to the length of the new buffer. You may have to reset it to
|
||||
the significant part of the buffer. You would normally use the C{command} property of L{ShortCommand} or L{LongCommand} instead.
|
||||
Setting it by default changes self.length to the length of the new
|
||||
buffer. You may have to reset it to the significant part of the buffer.
|
||||
You would normally use the C{command} property of
|
||||
L{ShortCommand} or L{LongCommand} instead.
|
||||
"""
|
||||
def fget(self):
|
||||
return self[16:]
|
||||
|
||||
def fset(self, buffer):
|
||||
self[16:] = buffer
|
||||
self.length = len(buffer)
|
||||
def fset(self, buff):
|
||||
self[16:] = buff
|
||||
self.length = len(buff)
|
||||
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget, fset=fset)
|
||||
|
||||
def __init__(self, packet):
|
||||
"""
|
||||
@param packet: len(packet) > 15 or packet > 15
|
||||
"""
|
||||
if ("__len__" in dir(packet) and len(packet) < 16) or ("__len__" not in dir(packet) and packet < 16):
|
||||
raise PacketError(str(self.__class__)[7:-2] + " packets must have length atleast 16")
|
||||
if ("__len__" in dir(packet) and len(packet) < 16) or\
|
||||
("__len__" not in dir(packet) and packet < 16):
|
||||
raise PacketError(str(self.__class__)[7:-2] + \
|
||||
" packets must have length atleast 16")
|
||||
TransferBuffer.__init__(self, packet)
|
||||
|
||||
|
||||
@ -306,8 +319,9 @@ class SetTime(Command):
|
||||
Set time on device. All fields refer to time in the GMT time zone.
|
||||
"""
|
||||
NUMBER = 0x104
|
||||
|
||||
timezone = field(start=0x10, fmt=DWORD) #: -time.timezone with negative numbers encoded as int(0xffffffff +1 -time.timezone/60.)
|
||||
# -time.timezone with negative numbers encoded
|
||||
# as int(0xffffffff +1 -time.timezone/60.)
|
||||
timezone = field(start=0x10, fmt=DWORD)
|
||||
year = field(start=0x14, fmt=DWORD) #: year e.g. 2006
|
||||
month = field(start=0x18, fmt=DWORD) #: month 1-12
|
||||
day = field(start=0x1c, fmt=DWORD) #: day 1-31
|
||||
@ -329,7 +343,9 @@ class SetTime(Command):
|
||||
self.day = t[2]
|
||||
self.hour = t[3]
|
||||
self.minute = t[4]
|
||||
self.second = t[5] if t[5] < 60 else 59 # Hack you should actually update the entire time tree is second is > 59
|
||||
# Hack you should actually update the entire time tree is
|
||||
# second is > 59
|
||||
self.second = t[5] if t[5] < 60 else 59
|
||||
|
||||
|
||||
class ShortCommand(Command):
|
||||
@ -337,7 +353,8 @@ class ShortCommand(Command):
|
||||
""" A L{Command} whoose data section is 4 bytes long """
|
||||
|
||||
SIZE = 20 #: Packet size in bytes
|
||||
command = field(start=16, fmt=DWORD) #: Usually carries additional information
|
||||
# Usually carries additional information
|
||||
command = field(start=16, fmt=DWORD)
|
||||
|
||||
def __init__(self, number=0x00, type=0x00, command=0x00):
|
||||
"""
|
||||
@ -354,38 +371,50 @@ class ShortCommand(Command):
|
||||
class DirRead(ShortCommand):
|
||||
""" The command that asks the device to send the next item in the list """
|
||||
NUMBER = 0x35 #: Command number
|
||||
def __init__(self, id):
|
||||
def __init__(self, _id):
|
||||
""" @param id: The identifier returned as a result of a L{DirOpen} command """
|
||||
ShortCommand.__init__(self, number=DirRead.NUMBER, type=0x01, command=id)
|
||||
ShortCommand.__init__(self, number=DirRead.NUMBER, type=0x01, \
|
||||
command=_id)
|
||||
|
||||
class DirClose(ShortCommand):
|
||||
""" Close a previously opened directory """
|
||||
NUMBER = 0x34 #: Command number
|
||||
def __init__(self, id):
|
||||
def __init__(self, _id):
|
||||
""" @param id: The identifier returned as a result of a L{DirOpen} command """
|
||||
ShortCommand.__init__(self, number=DirClose.NUMBER, type=0x01, command=id)
|
||||
ShortCommand.__init__(self, number=DirClose.NUMBER, type=0x01,
|
||||
command=_id)
|
||||
|
||||
class EndSession(ShortCommand):
|
||||
""" Ask device to change status to 'USB connected' i.e., tell the device that the present sequence of commands is complete """
|
||||
"""
|
||||
Ask device to change status to 'USB connected' i.e., tell the
|
||||
device that the present sequence of commands is complete
|
||||
"""
|
||||
NUMBER = 0x1 #: Command number
|
||||
def __init__(self):
|
||||
ShortCommand.__init__(self, number=EndSession.NUMBER, type=0x01, command=0x00)
|
||||
ShortCommand.__init__(self, \
|
||||
number=EndSession.NUMBER, type=0x01, command=0x00)
|
||||
|
||||
class GetUSBProtocolVersion(ShortCommand):
|
||||
""" Get USB Protocol version used by device """
|
||||
NUMBER = 0x0 #: Command number
|
||||
def __init__(self):
|
||||
ShortCommand.__init__(self, number=GetUSBProtocolVersion.NUMBER, type=0x01, command=0x00)
|
||||
ShortCommand.__init__(self, \
|
||||
number=GetUSBProtocolVersion.NUMBER, \
|
||||
type=0x01, command=0x00)
|
||||
|
||||
class SetBulkSize(ShortCommand):
|
||||
""" Set size for bulk transfers in this session """
|
||||
NUMBER = 0x107 #: Command number
|
||||
def __init__(self, size=0x028000):
|
||||
ShortCommand.__init__(self, number=SetBulkSize.NUMBER, type=0x01, command=size)
|
||||
ShortCommand.__init__(self, \
|
||||
number=SetBulkSize.NUMBER, type=0x01, command=size)
|
||||
|
||||
class UnlockDevice(ShortCommand):
|
||||
""" Unlock the device """
|
||||
NUMBER = 0x106 #: Command number
|
||||
def __init__(self, key=0x312d):
|
||||
ShortCommand.__init__(self, number=UnlockDevice.NUMBER, type=0x01, command=key)
|
||||
ShortCommand.__init__(self, \
|
||||
number=UnlockDevice.NUMBER, type=0x01, command=key)
|
||||
|
||||
class LongCommand(Command):
|
||||
|
||||
@ -410,7 +439,8 @@ class LongCommand(Command):
|
||||
doc = \
|
||||
"""
|
||||
Usually carries extra information needed for the command
|
||||
It is a list of C{unsigned integers} of length between 1 and 4. 4 C{unsigned int} stored in 16 bytes at byte 16.
|
||||
It is a list of C{unsigned integers} of length between 1 and 4. 4
|
||||
C{unsigned int} stored in 16 bytes at byte 16.
|
||||
"""
|
||||
def fget(self):
|
||||
return self.unpack(start=16, fmt="<"+str(self.length/4)+"I")
|
||||
@ -422,7 +452,7 @@ class LongCommand(Command):
|
||||
self.pack(command, start=start, fmt=DWORD)
|
||||
start += struct.calcsize(DWORD)
|
||||
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget, fset=fset)
|
||||
|
||||
class PathCommand(Command):
|
||||
""" Abstract class that defines structure common to all path related commands. """
|
||||
@ -452,7 +482,8 @@ class FreeSpaceQuery(ShortCommand):
|
||||
c = 0
|
||||
if where.startswith('a:'): c = 1
|
||||
elif where.startswith('b:'): c = 2
|
||||
ShortCommand.__init__(self, number=FreeSpaceQuery.NUMBER, type=0x01, command=c)
|
||||
ShortCommand.__init__(self, \
|
||||
number=FreeSpaceQuery.NUMBER, type=0x01, command=c)
|
||||
|
||||
class DirCreate(PathCommand):
|
||||
""" Create a directory """
|
||||
@ -470,12 +501,16 @@ class DirOpen(PathCommand):
|
||||
class AcknowledgeBulkRead(LongCommand):
|
||||
""" Must be sent to device after a bulk read """
|
||||
def __init__(self, bulk_read_id):
|
||||
""" bulk_read_id is an integer, the id of the bulk read we are acknowledging. See L{Answer.id} """
|
||||
LongCommand.__init__(self, number=0x1000, type=0x00, command=bulk_read_id)
|
||||
"""
|
||||
bulk_read_id is an integer, the id of the bulk read
|
||||
we are acknowledging. See L{Answer.id}
|
||||
"""
|
||||
LongCommand.__init__(self, number=0x1000, \
|
||||
type=0x00, command=bulk_read_id)
|
||||
|
||||
class DeviceInfoQuery(Command):
|
||||
""" The command used to ask for device information """
|
||||
NUMBER=0x0101 #: Command number
|
||||
NUMBER = 0x101 #: Command number
|
||||
def __init__(self):
|
||||
Command.__init__(self, 16)
|
||||
self.number = DeviceInfoQuery.NUMBER
|
||||
@ -484,8 +519,9 @@ class DeviceInfoQuery(Command):
|
||||
class FileClose(ShortCommand):
|
||||
""" File close command """
|
||||
NUMBER = 0x11 #: Command number
|
||||
def __init__(self, id):
|
||||
ShortCommand.__init__(self, number=FileClose.NUMBER, type=0x01, command=id)
|
||||
def __init__(self, _id):
|
||||
ShortCommand.__init__(self, number=FileClose.NUMBER, \
|
||||
type=0x01, command=_id)
|
||||
|
||||
class FileCreate(PathCommand):
|
||||
""" Create a file """
|
||||
@ -520,14 +556,17 @@ class FileOpen(PathCommand):
|
||||
@apply
|
||||
def mode():
|
||||
doc = \
|
||||
""" The file open mode. Is either L{FileOpen.READ} or L{FileOpen.WRITE}. C{unsigned int} stored at byte 16. """
|
||||
"""
|
||||
The file open mode. Is either L{FileOpen.READ}
|
||||
or L{FileOpen.WRITE}. C{unsigned int} stored at byte 16.
|
||||
"""
|
||||
def fget(self):
|
||||
return self.unpack(start=16, fmt=DWORD)[0]
|
||||
|
||||
def fset(self, val):
|
||||
self.pack(val, start=16, fmt=DWORD)
|
||||
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget, fset=fset)
|
||||
|
||||
|
||||
class FileIO(Command):
|
||||
@ -537,9 +576,9 @@ class FileIO(Command):
|
||||
id = field(start=16, fmt=DWORD) #: The file ID returned by a FileOpen command
|
||||
offset = field(start=20, fmt=DDWORD) #: offset in the file at which to read
|
||||
size = field(start=28, fmt=DWORD) #: The number of bytes to reead from file.
|
||||
def __init__(self, id, offset, size, mode=0x16):
|
||||
def __init__(self, _id, offset, size, mode=0x16):
|
||||
"""
|
||||
@param id: File identifier returned by a L{FileOpen} command
|
||||
@param _id: File identifier returned by a L{FileOpen} command
|
||||
@type id: C{unsigned int}
|
||||
@param offset: Position in file at which to read
|
||||
@type offset: C{unsigned long long}
|
||||
@ -551,7 +590,7 @@ class FileIO(Command):
|
||||
self.number = mode
|
||||
self.type = 0x01
|
||||
self.length = 16
|
||||
self.id = id
|
||||
self.id = _id
|
||||
self.offset = offset
|
||||
self.size = size
|
||||
|
||||
@ -572,81 +611,100 @@ class Response(Command):
|
||||
"""
|
||||
Defines the structure of response packets received from the device.
|
||||
|
||||
C{Response} inherits from C{Command} as the first 16 bytes have the same structure.
|
||||
C{Response} inherits from C{Command} as the
|
||||
first 16 bytes have the same structure.
|
||||
"""
|
||||
|
||||
SIZE = 32 #: Size of response packets in the SONY protocol
|
||||
rnumber = field(start=16, fmt=DWORD) #: Response number, the command number of a command packet sent sometime before this packet was received
|
||||
code = field(start=20, fmt=DWORD) #: Used to indicate error conditions. A value of 0 means there was no error
|
||||
data_size = field(start=28, fmt=DWORD) #: Used to indicate the size of the next bulk read
|
||||
# Response number, the command number of a command
|
||||
# packet sent sometime before this packet was received
|
||||
rnumber = field(start=16, fmt=DWORD)
|
||||
# Used to indicate error conditions. A value of 0 means
|
||||
# there was no error
|
||||
code = field(start=20, fmt=DWORD)
|
||||
# Used to indicate the size of the next bulk read
|
||||
data_size = field(start=28, fmt=DWORD)
|
||||
|
||||
def __init__(self, packet):
|
||||
""" C{len(packet) == Response.SIZE} """
|
||||
if len(packet) != Response.SIZE:
|
||||
raise PacketError(str(self.__class__)[7:-2] + " packets must have exactly " + str(Response.SIZE) + " bytes not " + str(len(packet)))
|
||||
raise PacketError(str(self.__class__)[7:-2] + \
|
||||
" packets must have exactly " + \
|
||||
str(Response.SIZE) + " bytes not " + str(len(packet)))
|
||||
Command.__init__(self, packet)
|
||||
if self.number != 0x00001000:
|
||||
raise PacketError("Response packets must have their number set to " + hex(0x00001000))
|
||||
raise PacketError("Response packets must have their number set to " \
|
||||
+ hex(0x00001000))
|
||||
|
||||
@apply
|
||||
def data():
|
||||
doc = \
|
||||
""" The last 3 DWORDs (12 bytes) of data in this response packet. Returned as a list of unsigned integers. """
|
||||
"""
|
||||
The last 3 DWORDs (12 bytes) of data in this
|
||||
response packet. Returned as a list of unsigned integers.
|
||||
"""
|
||||
def fget(self):
|
||||
return self.unpack(start=20, fmt="<III")
|
||||
|
||||
def fset(self, val):
|
||||
self.pack(val, start=20, fmt="<III")
|
||||
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget, fset=fset)
|
||||
|
||||
class ListResponse(Response):
|
||||
|
||||
""" Defines the structure of response packets received during list (ll) queries. See L{PathQuery}. """
|
||||
"""
|
||||
Defines the structure of response packets received
|
||||
during list (ll) queries. See L{PathQuery}.
|
||||
"""
|
||||
|
||||
IS_FILE = 0xffffffd2 #: Queried path is a file
|
||||
IS_INVALID = 0xfffffff9 #: Queried path is malformed/invalid
|
||||
IS_UNMOUNTED = 0xffffffc8 #: Queried path is not mounted (i.e. a removed storage card/stick)
|
||||
# Queried path is not mounted (i.e. a removed storage card/stick)
|
||||
IS_UNMOUNTED = 0xffffffc8
|
||||
IS_EOL = 0xfffffffa #: There are no more entries in the list
|
||||
PATH_NOT_FOUND = 0xffffffd7 #: Queried path is not found
|
||||
|
||||
@apply
|
||||
def is_file():
|
||||
""" True iff queried path is a file """
|
||||
doc = """ True iff queried path is a file """
|
||||
def fget(self):
|
||||
return self.code == ListResponse.IS_FILE
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget)
|
||||
|
||||
@apply
|
||||
def is_invalid():
|
||||
""" True iff queried path is invalid """
|
||||
doc = """ True iff queried path is invalid """
|
||||
def fget(self):
|
||||
return self.code == ListResponse.IS_INVALID
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget)
|
||||
|
||||
@apply
|
||||
def path_not_found():
|
||||
""" True iff queried path is not found """
|
||||
doc = """ True iff queried path is not found """
|
||||
def fget(self):
|
||||
return self.code == ListResponse.PATH_NOT_FOUND
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget)
|
||||
|
||||
@apply
|
||||
def is_unmounted():
|
||||
""" True iff queried path is unmounted (i.e. removed storage card) """
|
||||
doc = """ True iff queried path is unmounted (i.e. removed storage card) """
|
||||
def fget(self):
|
||||
return self.code == ListResponse.IS_UNMOUNTED
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget)
|
||||
|
||||
@apply
|
||||
def is_eol():
|
||||
""" True iff there are no more items in the list """
|
||||
doc = """ True iff there are no more items in the list """
|
||||
def fget(self):
|
||||
return self.code == ListResponse.IS_EOL
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget)
|
||||
|
||||
class Answer(TransferBuffer):
|
||||
""" Defines the structure of packets sent to host via a bulk transfer (i.e., bulk reads) """
|
||||
"""
|
||||
Defines the structure of packets sent to host via a
|
||||
bulk transfer (i.e., bulk reads)
|
||||
"""
|
||||
|
||||
number = field(start=0, fmt=DWORD) #: Answer identifier
|
||||
length = field(start=12, fmt=DWORD) #: Length of data to follow
|
||||
@ -655,21 +713,27 @@ class Answer(TransferBuffer):
|
||||
""" @param packet: C{len(packet)} S{>=} C{16} """
|
||||
if "__len__" in dir(packet):
|
||||
if len(packet) < 16 :
|
||||
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
|
||||
raise PacketError(str(self.__class__)[7:-2] + \
|
||||
" packets must have a length of atleast 16 bytes")
|
||||
elif packet < 16:
|
||||
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
|
||||
raise PacketError(str(self.__class__)[7:-2] + \
|
||||
" packets must have a length of atleast 16 bytes")
|
||||
TransferBuffer.__init__(self, packet)
|
||||
|
||||
|
||||
class FileProperties(Answer):
|
||||
|
||||
""" Defines the structure of packets that contain size, date and permissions information about files/directories. """
|
||||
"""
|
||||
Defines the structure of packets that contain size, date and
|
||||
permissions information about files/directories.
|
||||
"""
|
||||
|
||||
file_size = field(start=16, fmt=DDWORD) #: Size in bytes of the file
|
||||
file_type = field(start=24, fmt=DWORD) #: 1 == file, 2 == dir
|
||||
ctime = field(start=28, fmt=DWORD) #: Creation time as an epoch
|
||||
wtime = field(start=32, fmt=DWORD) #: Modification time as an epoch
|
||||
permissions = field(start=36, fmt=DWORD) #: 0 = default permissions, 4 = read only
|
||||
# 0 = default permissions, 4 = read only
|
||||
permissions = field(start=36, fmt=DWORD)
|
||||
|
||||
@apply
|
||||
def is_dir():
|
||||
@ -679,11 +743,13 @@ class FileProperties(Answer):
|
||||
return (self.file_type == 2)
|
||||
|
||||
def fset(self, val):
|
||||
if val: val = 2
|
||||
else: val = 1
|
||||
if val:
|
||||
val = 2
|
||||
else:
|
||||
val = 1
|
||||
self.file_type = val
|
||||
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget, fset=fset)
|
||||
|
||||
|
||||
@apply
|
||||
@ -694,14 +760,17 @@ class FileProperties(Answer):
|
||||
return self.unpack(start=36, fmt=DWORD)[0] != 0
|
||||
|
||||
def fset(self, val):
|
||||
if val: val = 4
|
||||
else: val = 0
|
||||
if val:
|
||||
val = 4
|
||||
else:
|
||||
val = 0
|
||||
self.pack(val, start=36, fmt=DWORD)
|
||||
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget, fset=fset)
|
||||
|
||||
|
||||
class USBProtocolVersion(Answer):
|
||||
""" Get USB Protocol version """
|
||||
version = field(start=16, fmt=DDWORD)
|
||||
|
||||
class IdAnswer(Answer):
|
||||
@ -711,7 +780,11 @@ class IdAnswer(Answer):
|
||||
@apply
|
||||
def id():
|
||||
doc = \
|
||||
""" The identifier. C{unsigned int} stored in 4 bytes at byte 16. Should be sent in commands asking for the next item in the list. """
|
||||
"""
|
||||
The identifier. C{unsigned int} stored in 4 bytes
|
||||
at byte 16. Should be sent in commands asking
|
||||
for the next item in the list.
|
||||
"""
|
||||
|
||||
def fget(self):
|
||||
return self.unpack(start=16, fmt=DWORD)[0]
|
||||
@ -719,7 +792,7 @@ class IdAnswer(Answer):
|
||||
def fset(self, val):
|
||||
self.pack(val, start=16, fmt=DWORD)
|
||||
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget, fset=fset)
|
||||
|
||||
class DeviceInfo(Answer):
|
||||
""" Defines the structure of the packet containing information about the device """
|
||||
@ -731,7 +804,8 @@ class DeviceInfo(Answer):
|
||||
|
||||
class TotalSpaceAnswer(Answer):
|
||||
total = field(start=24, fmt=DDWORD) #: Total space available
|
||||
free_space = field(start=32, fmt=DDWORD) #: Supposedly free space available, but it does not work for main memory
|
||||
# Supposedly free space available, but it does not work for main memory
|
||||
free_space = field(start=32, fmt=DDWORD)
|
||||
|
||||
class FreeSpaceAnswer(Answer):
|
||||
SIZE = 24
|
||||
@ -746,7 +820,10 @@ class ListAnswer(Answer):
|
||||
@apply
|
||||
def is_dir():
|
||||
doc = \
|
||||
""" True if list item points to a directory, False if it points to a file. C{unsigned int} stored in 4 bytes at byte 16. """
|
||||
"""
|
||||
True if list item points to a directory, False if it points to a file.
|
||||
C{unsigned int} stored in 4 bytes at byte 16.
|
||||
"""
|
||||
|
||||
def fget(self):
|
||||
return (self.unpack(start=16, fmt=DWORD)[0] == 2)
|
||||
@ -756,6 +833,6 @@ class ListAnswer(Answer):
|
||||
else: val = 1
|
||||
self.pack(val, start=16, fmt=DWORD)
|
||||
|
||||
return property(**locals())
|
||||
return property(doc=doc, fget=fget, fset=fset)
|
||||
|
||||
|
||||
|
239
prs-500.e4p
239
prs-500.e4p
@ -1,239 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Project SYSTEM "Project-4.0.dtd">
|
||||
<!-- eric4 project file for project prs-500 -->
|
||||
<!-- Saved: 2006-12-08, 23:24:29 -->
|
||||
<!-- Copyright (C) 2006 Kovid Goyal, kovid@kovidgoyal.net -->
|
||||
<Project version="4.0">
|
||||
<ProgLanguage mixed="0">Python</ProgLanguage>
|
||||
<UIType>Qt4</UIType>
|
||||
<Description>Library to communicate with the Sony Reader PRS-500 via USB</Description>
|
||||
<Version></Version>
|
||||
<Author>Kovid Goyal</Author>
|
||||
<Email>kovid@kovidgoyal.net</Email>
|
||||
<Sources>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>communicate.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>prstypes.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>errors.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>__init__.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Name>setup.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>cli</Dir>
|
||||
<Name>terminfo.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>cli</Dir>
|
||||
<Name>main.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>cli</Dir>
|
||||
<Name>__init__.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>main.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>__init__.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>lrf</Dir>
|
||||
<Name>meta.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>lrf</Dir>
|
||||
<Name>__init__.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>database.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>editbook.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>books.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>widgets.py</Name>
|
||||
</Source>
|
||||
</Sources>
|
||||
<Forms>
|
||||
<Form>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>main.ui</Name>
|
||||
</Form>
|
||||
<Form>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>editbook.ui</Name>
|
||||
</Form>
|
||||
</Forms>
|
||||
<Translations>
|
||||
</Translations>
|
||||
<Resources>
|
||||
</Resources>
|
||||
<Interfaces>
|
||||
</Interfaces>
|
||||
<Others>
|
||||
<Other>
|
||||
<Name>epydoc.conf</Name>
|
||||
</Other>
|
||||
<Other>
|
||||
<Name>epydoc-pdf.conf</Name>
|
||||
</Other>
|
||||
</Others>
|
||||
<MainScript>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>main.py</Name>
|
||||
</MainScript>
|
||||
<Vcs>
|
||||
<VcsType>Subversion</VcsType>
|
||||
<VcsOptions>
|
||||
<dict>
|
||||
<key>
|
||||
<string>status</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>log</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>global</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>update</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>remove</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>add</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>tag</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>export</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>commit</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>diff</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>checkout</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>history</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
</dict>
|
||||
</VcsOptions>
|
||||
<VcsOtherData>
|
||||
<dict>
|
||||
<key>
|
||||
<string>standardLayout</string>
|
||||
</key>
|
||||
<value>
|
||||
<bool>True</bool>
|
||||
</value>
|
||||
</dict>
|
||||
</VcsOtherData>
|
||||
</Vcs>
|
||||
<FiletypeAssociations>
|
||||
<FiletypeAssociation pattern="*.py" type="SOURCES" />
|
||||
<FiletypeAssociation pattern="*.ui.h" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.idl" type="INTERFACES" />
|
||||
<FiletypeAssociation pattern="*.ui" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.ptl" type="SOURCES" />
|
||||
</FiletypeAssociations>
|
||||
</Project>
|
Loading…
x
Reference in New Issue
Block a user