1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 """
43 Contains the logic for communication with the device (a SONY PRS-500).
44
45 The public interface of class L{PRS500Device} defines the methods for performing various tasks.
46 """
47 import usb, sys
48 from array import array
49
50 from prstypes import AcknowledgeBulkRead, Answer, Command, DeviceInfo, DirOpen, DirRead, DirClose, \
51 FileOpen, FileClose, FileRead, IdAnswer, ListAnswer, \
52 ListResponse, LongCommand, FileProperties, PathQuery, Response, \
53 ShortCommand, DeviceInfoQuery
54 from errors import *
55
56 MINIMUM_COL_WIDTH = 12
57 _packet_number = 0
58
60 """ Log C{packet} to stream C{stream}. Header should be a small word describing the type of packet. """
61 global _packet_number
62 _packet_number += 1
63 print >>stream, header, "(Packet #", str(_packet_number) + ")\n"
64 print >>stream, packet
65 print >>stream, "--"
66
68 """ Wrapper that allows easy access to all information about files/directories """
79
81 """ Return path to self """
82 return self.path
83
84
86 """
87 Describes a USB device.
88
89 A description is composed of the Vendor Id, Product Id and Interface Id.
90 See the U{USB spec<http://www.usb.org/developers/docs/usb_20_05122006.zip>}
91 """
92
93 - def __init__(self, vendor_id, product_id, interface_id) :
94 self.vendor_id = vendor_id
95 self.product_id = product_id
96 self.interface_id = interface_id
97
99 """
100 Return the device corresponding to the device descriptor if it is
101 available on a USB bus. Otherwise, return None. Note that the
102 returned device has yet to be claimed or opened.
103 """
104 buses = usb.busses()
105 for bus in buses :
106 for device in bus.devices :
107 if device.idVendor == self.vendor_id :
108 if device.idProduct == self.product_id :
109 return device
110 return None
111
112
114
115 """
116 Contains the logic for performing various tasks on the reader.
117
118 The implemented tasks are:
119 0. Getting information about the device
120 1. Getting a file from the device
121 2. Listing of directories. See the C{list} method.
122 """
123
124 SONY_VENDOR_ID = 0x054c
125 PRS500_PRODUCT_ID = 0x029b
126 PRS500_INTERFACE_ID = 0
127 PRS500_BULK_IN_EP = 0x81
128 PRS500_BULK_OUT_EP = 0x02
129
130 - def __init__(self, log_packets=False) :
138
139 @classmethod
141 """ Raise a ProtocolError if the type and number of C{res} is not the same as C{type} and C{number}. """
142 if type != res.type or number != res.rnumber:
143 raise ProtocolError("Inavlid response.\ntype: expected="+hex(type)+" actual="+hex(res.type)+
144 "\nrnumber: expected="+hex(number)+" actual="+hex(res.rnumber))
145
147 """
148 Claim an interface on the device for communication. Requires write privileges to the device file.
149
150 @todo: Check this on Mac OSX
151 """
152 self.device = self.device_descriptor.getDevice()
153 if not self.device:
154 print >> sys.stderr, "Unable to find Sony Reader. Is it connected?"
155 sys.exit(1)
156 self.handle = self.device.open()
157 if sys.platform == 'darwin' :
158
159
160 self.handle.setConfiguration(1)
161 self.handle.claimInterface(self.device_descriptor.interface_id)
162 self.handle.reset()
163
165 """ Release device interface """
166 self.handle.releaseInterface()
167 self.handle, self.device = None, None
168
170 """
171 Send L{command<Command>} to device and return its L{response<Response>}.
172
173 @param command: an object of type Command or one of its derived classes
174 @param response_type: an object of type 'type'. The return packet from the device is returned as an object of type response_type.
175 @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.
176 """
177 if self._log_packets: _log_packet(command, "Command")
178 bytes_sent = self.handle.controlMsg(0x40, 0x80, command)
179 if bytes_sent != len(command):
180 raise ControlError(desc="Could not send control request to device\n" + str(query.query))
181 response = response_type(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=timeout))
182 if self._log_packets: _log_packet(response, "Response")
183 return response
184
194
196 """
197 Read in a data packet via a Bulk Read.
198
199 @param data_type: an object of type type. The data packet is returned as an object of type C{data_type}.
200 @param size: the expected size of the data packet.
201 """
202 data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size))
203 if self._log_packets: _log_packet(data, "Answer d->h")
204 return data
205
206 - def _bulk_read(self, bytes, command_number=0x00, packet_size=4096, data_type=Answer):
207 """ Read in C{bytes} bytes via a bulk transfer in packets of size S{<=} C{packet_size} """
208 bytes_left = bytes
209 packets = []
210 while bytes_left > 0:
211 if packet_size > bytes_left: packet_size = bytes_left
212 packet = self._bulk_read_packet(data_type=data_type, size=packet_size)
213 bytes_left -= len(packet)
214 packets.append(packet)
215 self._send_validated_command(AcknowledgeBulkRead(packets[0].id), cnumber=command_number)
216 return packets
217
222
234
238
240 """
241 Wrapper that automatically calls L{_start_session} and L{_end_session}.
242
243 @param args: An array whose first element is the method to call and whose remaining arguments are passed to that mathos as an array.
244 """
245 self._start_session()
246 res = None
247 try:
248 res = args[0](args[1:])
249 except ArgumentError, e:
250 self._end_session()
251 raise e
252 self._end_session()
253 return res
254
260
264
274
276 """
277 Read the file at path on the device and write it to outfile. For the logic see L{_get_file}.
278
279 @param outfile: file object like C{sys.stdout} or the result of an C{open} call
280 """
281 self._run_session(self._get_file, path, outfile)
282
284 """
285 Fetch a file from the device and write it to an output stream.
286
287 The data is fetched in chunks of size S{<=} 32K. Each chunk is make of packets of size S{<=} 4K. See L{FileOpen},
288 L{FileRead} and L{FileClose} for details on the command packets used.
289
290 @param args: C{path, outfile = arg[0], arg[1]}
291 """
292 path, outfile = args[0], args[1]
293 if path.endswith("/"): path = path[:-1]
294 res, data = self._get_path_properties(path)
295 if data.is_dir: raise PathError("Cannot read as " + path + " is a directory")
296 bytes = data.file_size
297 self._send_validated_command(FileOpen(path))
298 id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id
299 bytes_left, chunk_size, pos = bytes, 0x8000, 0
300 while bytes_left > 0:
301 if chunk_size > bytes_left: chunk_size = bytes_left
302 res = self._send_validated_command(FileRead(id, pos, chunk_size))
303 packets = self._bulk_read(chunk_size+16, command_number=FileRead.NUMBER, packet_size=4096)
304 try:
305 array('B', packets[0][16:]).tofile(outfile)
306 for i in range(1, len(packets)):
307 array('B', packets[i]).tofile(outfile)
308 except IOError, e:
309 self._send_validated_command(FileClose(id))
310 raise ArgumentError("File get operation failed. Could not write to local location: " + str(e))
311 bytes_left -= chunk_size
312 pos += chunk_size
313 self._send_validated_command(FileClose(id))
314
315
317 """
318 Ask the device to list a path. See the code for details. See L{DirOpen},
319 L{DirRead} and L{DirClose} for details on the command packets used.
320
321 @param args: C{path=args[0]}
322 @return: A list of tuples. The first element of each tuple is a string, the path. The second is a L{FileProperties}.
323 If the path points to a file, the list will have length 1.
324 """
325 path = args[0]
326 if not path.endswith("/"): path += "/"
327 files = []
328 res, data = self._get_path_properties(path)
329 if res.is_file:
330 path = path[:-1]
331 res, data = self._get_path_properties(path)
332 files = [ (path, data) ]
333 else:
334
335 self._send_validated_command(DirOpen(path), response_type=ListResponse)
336 id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id
337
338 next = DirRead(id)
339 items = []
340 while True:
341 res = self._send_validated_command(next, response_type=ListResponse)
342 size = res.data[2] + 16
343 data = self._bulk_read(size, data_type=ListAnswer, command_number=DirRead.NUMBER)[0]
344
345 if res.is_eol or res.path_not_found: break
346 items.append(data.name)
347 self._send_validated_command(DirClose(id))
348 for item in items:
349 ipath = path + item
350 res, data = self._get_path_properties(ipath)
351 files.append( (ipath, data) )
352 files.sort()
353 return files
354
355 - def list(self, path, recurse=False):
356 """
357 Return a listing of path.
358
359 See L{_list} for the communication logic.
360
361 @type path: string
362 @param path: The path to list
363 @type recurse: boolean
364 @param recurse: If true do a recursive listing
365 @return: A list of tuples. The first element of each tuple is a path. The second element is a list of L{Files<File>}.
366 The path is the path we are listing, the C{Files} are the files/directories in that path. If it is a recursive
367 list, then the first element will be (C{path}, children), the next will be (child, its children) and so on.
368 """
369 files = self._run_session(self._list, path)
370 files = [ File(file) for file in files ]
371 dirs = [(path, files)]
372 for file in files:
373 if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")):
374 dirs[len(dirs):] = self.list(file.path, recurse=True)
375 return dirs
376