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, os, time
48 from array import array
49
50 from prstypes import *
51 from errors import *
52
53 MINIMUM_COL_WIDTH = 12
54 _packet_number = 0
55 KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L]
56
58 """ Log C{packet} to stream C{stream}. Header should be a small word describing the type of packet. """
59 global _packet_number
60 _packet_number += 1
61 print >>stream, str(_packet_number), header, "Type:", packet.__class__.__name__
62 print >>stream, packet
63 print >>stream, "--"
64
66 """ Wrapper that allows easy access to all information about files/directories """
77
79 """ Return path to self """
80 return "File:"+self.path
81
84
85
87 """
88 Describes a USB device.
89
90 A description is composed of the Vendor Id, Product Id and Interface Id.
91 See the U{USB spec<http://www.usb.org/developers/docs/usb_20_05122006.zip>}
92 """
93
94 - def __init__(self, vendor_id, product_id, interface_id) :
95 self.vendor_id = vendor_id
96 self.product_id = product_id
97 self.interface_id = interface_id
98
100 """
101 Return the device corresponding to the device descriptor if it is
102 available on a USB bus. Otherwise, return None. Note that the
103 returned device has yet to be claimed or opened.
104 """
105 buses = usb.busses()
106 for bus in buses :
107 for device in bus.devices :
108 if device.idVendor == self.vendor_id :
109 if device.idProduct == self.product_id :
110 return device
111 return None
112
113
115
116 """
117 Contains the logic for performing various tasks on the reader.
118
119 The implemented tasks are:
120 0. Getting information about the device
121 1. Getting a file from the device
122 2. Listing of directories. See the C{list} method.
123 """
124
125 SONY_VENDOR_ID = 0x054c
126 PRS500_PRODUCT_ID = 0x029b
127 PRS500_INTERFACE_ID = 0
128 PRS500_BULK_IN_EP = 0x81
129 PRS500_BULK_OUT_EP = 0x02
130
132 """
133 Decorator that wraps a call to C{func} to ensure that exceptions are handled correctly.
134
135 As a convenience, C{safe} automatically sends the a L{USBConnect} after calling func, unless func has
136 a keyword argument named C{end_session} set to C{False}.
137
138 An L{ArgumentError} will cause the L{USBConnect} command to be sent to the device, unless end_session is set to C{False}.
139 An L{usb.USBError} will cause the library to release control of the USB interface via a call to L{close}.
140 """
141 def run_session(*args, **kwargs):
142 dev = args[0]
143 res = None
144 try:
145 res = func(*args, **kwargs)
146 except ArgumentError, e:
147 if not kwargs.has_key("end_session") or kwargs["end_session"]:
148 dev._send_validated_command(USBConnect())
149 raise e
150 except usb.USBError, e:
151 dev.close()
152 raise e
153 if not kwargs.has_key("end_session") or kwargs["end_session"]:
154 dev._send_validated_command(USBConnect())
155 return res
156
157 return run_session
158
159 - def __init__(self, log_packets=False) :
167
168 @classmethod
170 """ Raise a ProtocolError if the type and number of C{res} is not the same as C{type} and C{number}. """
171 if type != res.type or number != res.rnumber:
172 raise ProtocolError("Inavlid response.\ntype: expected="+hex(type)+" actual="+hex(res.type)+
173 "\nrnumber: expected="+hex(number)+" actual="+hex(res.rnumber))
174
200
201
203 """ Release device interface """
204 self.handle.releaseInterface()
205 self.handle, self.device = None, None
206
208 """
209 Send L{command<Command>} to device and return its L{response<Response>}.
210
211 @param command: an object of type Command or one of its derived classes
212 @param response_type: an object of type 'type'. The return packet from the device is returned as an object of type response_type.
213 @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.
214 """
215 if self._log_packets: _log_packet(command, "Command")
216 bytes_sent = self.handle.controlMsg(0x40, 0x80, command)
217 if bytes_sent != len(command):
218 raise ControlError(desc="Could not send control request to device\n" + str(query.query))
219 response = response_type(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=timeout))
220 if self._log_packets: _log_packet(response, "Response")
221 return response
222
232
234 """
235 Send data to device via a bulk transfer.
236 @type data: Any listable type supporting __getslice__
237 @param packet_size: Size of packets to be sent to device. C{data} is broken up into packets to be sent to device.
238 """
239 def bulk_write_packet(packet):
240 self.handle.bulkWrite(PRS500Device.PRS500_BULK_OUT_EP, packet)
241 if self._log_packets: _log_packet(Answer(packet), "Answer h->d")
242
243 bytes_left = len(data)
244 if bytes_left + 16 <= packet_size:
245 packet_size = bytes_left +16
246 first_packet = Answer(bytes_left+16)
247 first_packet[16:] = data
248 first_packet.length = len(data)
249 else:
250 first_packet = Answer(packet_size)
251 first_packet[16:] = data[0:packet_size-16]
252 first_packet.length = packet_size-16
253 first_packet.number = 0x10005
254 bulk_write_packet(first_packet)
255 pos = first_packet.length
256 bytes_left -= first_packet.length
257 while bytes_left > 0:
258 endpos = pos + packet_size if pos + packet_size <= len(data) else len(data)
259 bulk_write_packet(data[pos:endpos])
260 bytes_left -= endpos - pos
261 pos = endpos
262 res = Response(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=5000))
263 if self._log_packets: _log_packet(res, "Response")
264 if res.rnumber != 0x10005 or res.code != 0:
265 raise ProtocolError("Sending via Bulk Transfer failed with response:\n"+str(res))
266 if res.data_size != len(data):
267 raise ProtocolError("Unable to transfer all data to device. Response packet:\n"+str(res))
268
269
270 - def _bulk_read(self, bytes, command_number=0x00, packet_size=4096, data_type=Answer):
271 """
272 Read in C{bytes} bytes via a bulk transfer in packets of size S{<=} C{packet_size}
273 @param data_type: an object of type type. The data packet is returned as an object of type C{data_type}.
274 @return: A list of packets read from the device. Each packet is of type data_type
275 @todo: Figure out how to make bulk reads work in OSX
276 """
277 def bulk_read_packet(data_type=Answer, size=0x1000):
278 data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size))
279 if self._log_packets: _log_packet(data, "Answer d->h")
280 return data
281
282 bytes_left = bytes
283 packets = []
284 while bytes_left > 0:
285 if packet_size > bytes_left: packet_size = bytes_left
286 packet = bulk_read_packet(data_type=data_type, size=packet_size)
287 bytes_left -= len(packet)
288 packets.append(packet)
289 self._send_validated_command(AcknowledgeBulkRead(packets[0].number), cnumber=command_number)
290 return packets
291
292 @safe
301
302 @safe
314
315 @safe
316 - def get_file(self, path, outfile, end_session=True):
317 """
318 Read the file at path on the device and write it to outfile. For the logic see L{_get_file}.
319
320 The data is fetched in chunks of size S{<=} 32K. Each chunk is make of packets of size S{<=} 4K. See L{FileOpen},
321 L{FileRead} and L{FileClose} for details on the command packets used.
322
323 @param outfile: file object like C{sys.stdout} or the result of an C{open} call
324 """
325 if path.endswith("/"): path = path[:-1]
326 file = self.path_properties(path, end_session=False)
327 if file.is_dir: raise PathError("Cannot read as " + path + " is a directory")
328 bytes = file.file_size
329 res = self._send_validated_command(FileOpen(path))
330 if res.code != 0:
331 raise PathError("Unable to open " + path + " for reading. Response code: " + hex(res.code))
332 id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id
333 bytes_left, chunk_size, pos = bytes, 0x8000, 0
334 while bytes_left > 0:
335 if chunk_size > bytes_left: chunk_size = bytes_left
336 res = self._send_validated_command(FileIO(id, pos, chunk_size))
337 if res.code != 0:
338 self._send_validated_command(FileClose(id))
339 raise ProtocolError("Error while reading from " + path + ". Response code: " + hex(res.code))
340 packets = self._bulk_read(chunk_size+16, command_number=FileIO.RNUMBER, packet_size=4096)
341 try:
342 array('B', packets[0][16:]).tofile(outfile)
343 for i in range(1, len(packets)):
344 array('B', packets[i]).tofile(outfile)
345 except IOError, e:
346 self._send_validated_command(FileClose(id))
347 raise ArgumentError("File get operation failed. Could not write to local location: " + str(e))
348 bytes_left -= chunk_size
349 pos += chunk_size
350 self._send_validated_command(FileClose(id))
351
352
353
354
355 @safe
356 - def list(self, path, recurse=False, end_session=True):
357 """
358 Return a listing of path. See the code for details. See L{DirOpen},
359 L{DirRead} and L{DirClose} for details on the command packets used.
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. If it
368 is not recursive the length of the outermost list will be 1.
369 """
370 def _list(path):
371 if not path.endswith("/"): path += "/"
372 files = []
373 candidate = self.path_properties(path, end_session=False)
374 if not candidate.is_dir:
375 path = path[:-1]
376 data = self.path_properties(path, end_session=False)
377 files = [ File((path, data)) ]
378 else:
379
380 res = self._send_validated_command(DirOpen(path))
381 if res.code != 0:
382 raise PathError("Unable to open directory " + path + " for reading. Response code: " + hex(res.code))
383 id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id
384
385 next = DirRead(id)
386 items = []
387 while True:
388 res = self._send_validated_command(next, response_type=ListResponse)
389 size = res.data_size + 16
390 data = self._bulk_read(size, data_type=ListAnswer, command_number=DirRead.NUMBER)[0]
391
392 if res.is_eol or res.path_not_found: break
393 elif res.code != 0:
394 raise ProtocolError("Unknown error occured while reading contents of directory " + path + ". Response code: " + haex(res.code))
395 items.append(data.name)
396 self._send_validated_command(DirClose(id))
397 for item in items:
398 ipath = path + item
399 data = self.path_properties(ipath, end_session=False)
400 files.append( File( (ipath, data) ) )
401 files.sort()
402 return files
403
404 files = _list(path)
405 dirs = [(path, files)]
406
407 for file in files:
408 if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")):
409 dirs[len(dirs):] = self.list(file.path, recurse=True, end_session=False)
410 return dirs
411
412 @safe
429
431 """ Return (True, FileProperties) if path exists or (False, None) otherwise """
432 dest = None
433 try:
434 dest = self.path_properties(path, end_session=False)
435 except PathError, e:
436 if "does not exist" in str(e): return (False, None)
437 else: raise e
438 return (True, dest)
439
440 @safe
441 - def touch(self, path, end_session=True):
455
456
457
458
459
460
461
462 @safe
463 - def put_file(self, infile, path, end_session=True):
464 exists, dest = self._exists(path)
465 if exists:
466 if not dest.is_dir: raise PathError("Cannot write to " + path + " as it already exists")
467 if not path.endswith("/"): path += "/"
468 path += os.path.basename(infile.name)
469 exists, dest = self._exists(path)
470 if exists: raise PathError("Cannot write to " + path + " as it already exists")
471 res = self._send_validated_command(FileCreate(path))
472 if res.code != 0:
473 raise ProtocolError("There was an error creating device:"+path+". Response code: "+hex(res.code))
474 chunk_size = 0x8000
475 data_left = True
476 res = self._send_validated_command(FileOpen(path, mode=FileOpen.WRITE))
477 if res.code != 0:
478 raise ProtocolError("Unable to open " + path + " for writing. Response code: " + hex(res.code))
479 id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id
480 pos = 0
481 while data_left:
482 data = array('B')
483 try:
484 data.fromfile(infile, chunk_size)
485 except EOFError:
486 data_left = False
487 res = self._send_validated_command(FileIO(id, pos, len(data), mode=FileIO.WNUMBER))
488 if res.code != 0:
489 raise ProtocolError("Unable to write to " + path + ". Response code: " + hex(res.code))
490 self._bulk_write(data)
491 pos += len(data)
492 self._send_validated_command(FileClose(id))
493 file = self.path_properties(path, end_session=False)
494 if file.file_size != pos:
495 raise ProtocolError("Copying to device failed. The file was truncated by " + str(data.file_size - pos) + " bytes")
496
497 @safe
498 - def del_file(self, path, end_session=True):
504
505 @safe
506 - def mkdir(self, path, end_session=True):
518
519 @safe
520 - def rm(self, path, end_session=True):
532
533
534
535
536
537
538
539