bugfix : copier/coller hasardeux.
[Plinn.git] / File.py
1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Plinn - http://plinn.org #
4 # Copyright (C) 2005-2007 Benoît PIN <benoit.pin@ensmp.fr> #
5 # #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 """ This module implements a portal-managed File class that's inherits of CMFDefault
21 File. If exists, portal_transforms is called to extract text content, and publish
22 attachments.
23
24
25
26 """
27
28 from Globals import InitializeClass
29 from AccessControl import ClassSecurityInfo
30 import OFS
31 from zope.component.factory import Factory
32
33 from Products.CMFDefault.File import File as CMFFile
34 from Products.Photo.blobbases import File as BlobFile
35 from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl
36 from Products.CMFCore.permissions import View, ModifyPortalContent
37 from Products.CMFCore.utils import getToolByName
38 from hexagonit.swfheader import parse as parseswf
39
40 class File(BlobFile, CMFFile) :
41 #class File(CMFFile) :
42 """ file class with portal_transforms support """
43
44 security = ClassSecurityInfo()
45
46 _properties = CMFFile._properties + ({'id':'orig_name', 'type':'string', 'mode':'w', 'label':"Original Name"},)
47 orig_name = ''
48
49 def __init__( self
50 , id
51 , title=''
52 , file=''
53 , content_type=''
54 , precondition=''
55 , subject=()
56 , description=''
57 , contributors=()
58 , effective_date=None
59 , expiration_date=None
60 , format=None
61 , language='en-US'
62 , rights=''
63 ):
64 BlobFile.__init__(self, id, title, file, content_type=content_type, precondition=precondition)
65 self._setId(id)
66 #delattr(self, '__name__')
67 #
68 # If no file format has been passed in, rely on what OFS.Image.File
69 # detected. Unlike Images, which have code to try and pick the content
70 # type out of the binary data, File objects only provide the correct
71 # type if a "hint" in the form of a filename extension is given.
72 if format is None:
73 format = self.content_type
74
75 DefaultDublinCoreImpl.__init__( self, title, subject, description
76 , contributors, effective_date, expiration_date
77 , format, language, rights )
78
79
80 def __getattr__(self, name) :
81 try : return CMFFile.__getattr__(self, name)
82 except :
83 selfAttrs = self.__dict__
84 if selfAttrs.has_key('_v_transform_cache') :
85 cache = selfAttrs['_v_transform_cache']
86 cacheTuple = cache.get('text_html', None) # (time, value)
87 if cacheTuple :
88 cacheData = cacheTuple[1]
89
90 subObDict = cacheData.getSubObjects()
91 if subObDict.has_key(name) :
92 fileOb = OFS.Image.File(name, name, subObDict[name])
93 return fileOb
94
95 raise AttributeError, name
96
97 def manage_upload(self,file='',REQUEST=None):
98 ret = super(File, self).manage_upload(file=file, REQUEST=REQUEST)
99
100 orig_name = OFS.Image.cookId('', '', file)[0]
101 if orig_name :
102 self.orig_name = orig_name
103
104 print self.absolute_url(), self.Format()
105 if self.Format() == 'application/x-shockwave-flash' :
106 if file :
107 try :
108 swfmetadata = parseswf(file)
109 except IOError :
110 swfmetadata = {'width':600, 'height':600}
111
112 for name in ('width', 'height') :
113 value = swfmetadata[name]
114 if self.hasProperty(name) :
115 self._updateProperty(name, value)
116 else :
117 self.manage_addProperty(name, value, 'int')
118 self.reindexObject()
119 return ret
120
121
122
123 security.declareProtected(ModifyPortalContent, 'edit')
124 def edit(self, precondition='', file=''):
125 orig_name = OFS.Image.cookId('', '', file)[0]
126 if orig_name :
127 self.orig_name = orig_name
128 CMFFile.edit(self, precondition=precondition, file=file)
129 if hasattr(self, '_v_transform_cache') :
130 del self._v_transform_cache
131
132
133 security.declareProtected(View, 'SearchableText')
134 def SearchableText(self) :
135 """ Return full text"""
136 baseSearchableText = CMFFile.SearchableText(self)
137 transformTool = getToolByName(self, 'portal_transforms', default=None)
138 if transformTool is None :
139 return baseSearchableText
140 else :
141 f = self.bdata.open()
142 orig = f.read()
143 datastream_text = transformTool.convertTo('text/plain',
144 orig,
145 mimetype = self.content_type
146 )
147 f.close()
148 full_text = ''
149 if datastream_text is not None :
150 full_text = datastream_text.getData()
151
152 return baseSearchableText + full_text
153
154 security.declareProtected(View, 'preview')
155 def preview(self) :
156 """Return HTML preview if it's possible or empty string """
157 transformTool = getToolByName(self, 'portal_transforms', default = None)
158 if transformTool is None :
159 return ''
160 else :
161 filename = self.getId().replace(' ', '_')
162 f = self.bdata.open()
163 orig = f.read()
164 datastream = transformTool.convertTo('text/html',
165 orig,
166 object=self,
167 mimetype = self.content_type,
168 filename = filename)
169 f.close()
170 if datastream is not None : return datastream.getData()
171 else : return ''
172
173 security.declareProtected(View, 'download')
174 def download(self, REQUEST, RESPONSE):
175 """Download this item.
176
177 Calls OFS.Image.File.index_html to perform the actual transfer after
178 first setting Content-Disposition to suggest a filename.
179
180 This method is deprecated, use the URL of this object itself. Because
181 the default view of a File object is to download, rather than view,
182 this method is obsolete. Also note that certain browsers do not deal
183 well with a Content-Disposition header.
184
185 """
186
187 RESPONSE.setHeader('Content-Disposition',
188 'attachment; filename=%s' % (self.orig_name or self.getId()))
189 return OFS.Image.File.index_html(self, REQUEST, RESPONSE)
190
191 security.declarePublic('getIcon')
192 def getIcon(self, relative_to_portal=0):
193 """ return icon corresponding to mime-type
194 """
195 regTool = getToolByName(self, 'mimetypes_registry', default=None)
196 if regTool :
197 f = self.bdata.open()
198 mime = regTool(f, mimetype=self.content_type)[2]
199 f.close()
200 return mime.icon_path
201 else :
202 return CMFFile.getIcon(self, relative_to_portal=relative_to_portal)
203
204
205 InitializeClass(File)
206 FileFactory = Factory(File)
207
208
209 def addFile( dispatcher
210 , id
211 , title=''
212 , file=''
213 , content_type=''
214 , precondition=''
215 , subject=()
216 , description=''
217 , contributors=()
218 , effective_date=None
219 , expiration_date=None
220 , format='text/html'
221 , language=''
222 , rights=''
223 ):
224 """
225 Add a File
226 """
227
228 # cookId sets the id and title if they are not explicity specified
229 id, title = OFS.Image.cookId(id, title, file)
230
231 container = dispatcher.Destination()
232
233 # Instantiate the object and set its description.
234 fobj = File( id, title=title, file='', content_type=content_type,
235 precondition=precondition, subject=subject, description=description,
236 contributors=contributors, effective_date=effective_date,
237 expiration_date=expiration_date, format=format,
238 language=language, rights=rights
239 )
240
241 # Add the File instance to self
242 container._setObject(id, fobj)
243
244 # 'Upload' the file. This is done now rather than in the
245 # constructor because the object is now in the ZODB and
246 # can span ZODB objects.
247 container._getOb(id).manage_upload(file)