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