1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Code for handling arbitrary file attachments.
17
18 'Attachment' is a base class for classes that represent arbitrary
19 attachments. Each 'Attachment' object has these four attributes:
20
21 'mime_type' -- The MIME type of the attachment contents. This
22 information enables user interfaces to handle attachment data in a
23 sensible fasion.
24
25 'description' -- The user's description of the attachment contents.
26
27 'file_name' -- A file name associated with the description. This is
28 usually the name of the file from which the attachment was originally
29 uploaded or inserted.
30
31 'location' -- A string containing the external location of the
32 attachment data. The semantics of this string are defined by
33 implementations of 'AttachmentStore', which use it to locate the
34 attachment's data.
35
36 A special 'TemporaryAttachmentStore', with a different interface, is
37 used to store attachment data temporarily, at most for the life of the
38 program. The 'temporary_store' global instance should be used."""
39
40
41
42
43
44 import common
45 import mimetypes
46 import os
47 import xmlutil
48 import temporary_directory
49
50
51
52
53
55 """An arbitrary file attachment.
56
57 Conceptually, an attachment is composed of these parts:
58
59 1. A MIME type, as a string.
60
61 2. A description, as a structured text string.
62
63 3. A file name, corresponding to the original name of the file from
64 which the attachment was uploaded, or the name of the file to
65 use when the attachment is presented to the user in a file
66 system.
67
68 4. A block of arbitrary data.
69
70 For efficiency reasons, the attachment data is not stored in the
71 attachment. Instead, a *location* is stored, which is a key into
72 the associated 'AttachmentStore' object."""
73
74 - def __init__(self,
75 mime_type,
76 description,
77 file_name,
78 location,
79 store):
80 """Create a new attachment.
81
82 'mime_type' -- The MIME type. If 'None' or an empty string, the
83 function attempts to guess the MIME type from other information.
84
85 'description' -- A description of the attachment contents.
86
87 'file_name' -- The user-visible file name to associate the
88 attachment.
89
90 'location' -- The location in an attachment store at which to
91 find the attachment data.
92
93 'store' -- The attachment store in which the data is stored."""
94
95
96
97 if mime_type == "" or mime_type is None:
98 mime_type = mimetypes.guess_type(file_name)[0]
99 if mime_type is None:
100
101
102 mime_type = "application/octet-stream"
103 self.__mime_type = mime_type
104
105 self.__description = description
106 self.__file_name = file_name
107 self.__location = location
108 self.__store = store
109
110
112 """Return the attachment's MIME type."""
113
114 return self.__mime_type
115
116
118 """Return the attachment's description."""
119
120 return self.__description
121
122
124 """Return the attachment's file name."""
125
126 return self.__file_name
127
128
130 """Return the attachment's location in an attachment store."""
131
132 return self.__location
133
134
141
142
144 """Return the path to a file containing attachment data.
145
146 returns -- A file system path. The file should be considered
147 read-only, and should not be modified in any way."""
148
149 return self.GetStore().GetDataFile(self.GetLocation())
150
151
153 """Return the store in which this attachment is located.
154
155 returns -- The 'AttachmentStore' that contains this attachment."""
156
157 return self.__store
158
159
160 - def Move(self, store, location):
161 """Move the 'Attachment' to a new location.
162
163 'store' -- The 'AttachmentStore' that will contain the
164 attachment.
165
166 'location' -- The location of the attachment within its current
167 store."""
168
169
170
171
172 store.Store(self, location)
173
174 self.__store.Remove(self.__location)
175
176 self.__store = store
177 self.__location = location
178
179
183
184
192
193
194
196 """Interface for classes which store attachment data.
197
198 An attachment store stores the raw data for an attachment. The
199 store is not responsible for storing auxiliary information,
200 including the attachment's description, file name, or MIME type.
201
202 Users of an 'AttachmentStore' reference attachment data by a
203 *location*, which is stored with the attachment.
204
205 Please note that the 'AttachmentStore' interface provides methods
206 for retrieving attachment data only; not for storing it. The
207 interface for storing may be defined in any way by implementations."""
208
210 """Return the data for an attachment.
211
212 returns -- A string containing the attachment data."""
213
214 raise NotImplementedError
215
216
218 """Return the path to a file containing the data for
219 'attachment'.
220
221 returns -- A file system path.
222
223 The file is read-only, and may be a temporary file. The caller
224 should not modify the file in any way."""
225
226 raise NotImplementedError
227
228
230 """Return the size of the data for an attachment.
231
232 returns -- The length of the attachment data, in bytes.
233
234 This method may be overridden by derived classes."""
235
236 return len(self.GetData(location))
237
238
240 """Handle a web request to download attachment data.
241
242 'request' -- A 'WebRequest' object. The location of the
243 attachment data is stored in the 'location' property, and the
244 MIME type in the 'mime_type' property.
245
246 returns -- A pair '(mime_type, data)' where 'mime_type' is the
247 MIME type stored in the request and 'data' is the contents of
248 the attachment."""
249
250 location = request["location"]
251 mime_type = request["mime_type"]
252 data = self.GetData(location)
253 return (mime_type, data)
254
255
256 - def Store(self, attachment, location):
257 """Add an attachment to the store.
258
259 'attachment' -- The 'Attachment' to store.
260
261 'location' -- The location in which to store the 'attachment'."""
262
263 raise NotImplementedError
264
265
266
268 """An attachment store based on the file system.
269
270 The locations are the names of files in the file system."""
271
273 """Construct a new 'FileAttachmentStore'
274
275 'root' -- If not 'None', the root directory for the store. All
276 locations are relative to this directory. If 'None', all
277 locations are relative to the current directory."""
278
279 super(AttachmentStore, self).__init__()
280 self.__root = root
281
282
284
285
286 f = open(self.GetDataFile(location))
287
288 s = f.read()
289
290 f.close()
291
292 return s
293
294
296
297 if self.__root is not None:
298
299
300
301
302
303 return os.path.join(self.__root, location)
304 else:
305 return location
306
307
311
312
313 - def Store(self, attachment, location):
321
322
324 """Remove an attachment.
325
326 'location' -- The location whose data should be removed."""
327
328 os.remove(self.GetDataFile(location))
329
330
331
333 """Temporary storage for attachment data.
334
335 A 'TemporaryAttachmentStore' stores attachment data in a temporary
336 location, for up to the lifetime of the running program. When the
337 program ends, all temporarily stored attachment data is deleted.
338
339 A data object in the temporary store is identified by its location.
340 Locations should be generated by 'make_temporary_location'."""
341
353
354
356 """Handle a web request to upload attachment data.
357
358 Store the attachment data contained in the request as a
359 temporary attachment. It is assumed that the request is being
360 submitted from a popup upload browser window, so the returned
361 HTML page instructs the window to close itself.
362
363 'request' -- A 'WebRequest' object.
364
365 returns -- HTML text of a page that instructs the browser window
366 to close."""
367
368 location = request["location"]
369
370 file = open(self.GetDataFile(location), "w")
371
372 file.write(request["file_data"])
373
374 file.close()
375
376
377 return '''
378 <html><body>
379 <script type="text/javascript" language="JavaScript">
380 window.close();
381 </script>
382 </body></html>
383 '''
384
385
386
387
388
389 _temporary_location_prefix = "_temporary"
390
391
396
397
399 """Create a DOM element node for this attachment.
400
401 'document' -- A DOM document node in which to create the
402 element.
403
404 returns -- A DOM element node."""
405
406
407 node = document.createElement("attachment")
408
409 if attachment is None:
410
411 return node
412
413 mime_type = attachment.GetMimeType()
414
415
416 child = xmlutil.create_dom_text_element(
417 document, "description", attachment.GetDescription())
418 node.appendChild(child)
419
420 child = xmlutil.create_dom_text_element(
421 document, "mime-type", mime_type)
422 node.appendChild(child)
423
424 child = xmlutil.create_dom_text_element(
425 document, "filename", attachment.GetFileName())
426 node.appendChild(child)
427
428
429 location = attachment.GetLocation()
430 child = xmlutil.create_dom_text_element(document, "location", location)
431
432 node.appendChild(child)
433 return node
434
435
437 """Construct an attachment object from a DOM element node.
438
439 'node' -- A DOM attachment element node.
440
441 'store' -- The associated attachment store.
442
443 returns -- An attachment instance. The type is determined by
444 'attachment_class'.
445
446 If the attachment object requires additional context information to
447 interpret the location (if it's specified in the attachment
448 element), the caller must provide it directly to the object."""
449
450 if len(node.childNodes) == 0:
451
452 return None
453
454
455
456 description = xmlutil.get_child_text(node, "description", "")
457 mime_type = xmlutil.get_child_text(
458 node, "mime-type", "application/octet-stream")
459 file_name = xmlutil.get_child_text(node, "filename", "")
460 location = xmlutil.get_child_text(node, "location")
461
462 return Attachment(mime_type, description, file_name, location, store)
463
464
465
466
467
468
469
470