Merge commit 'a4cb4d96face514924387d34746b3148848ac092' into zope-2.13
[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 """ file class with portal_transforms support """
42
43 security = ClassSecurityInfo()
44
45 _properties = CMFFile._properties + ({'id':'orig_name', 'type':'string', 'mode':'w', 'label':"Original Name"},)
46 orig_name = ''
47
48 def __init__( self
49 , id
50 , title=''
51 , file=''
52 , content_type=''
53 , precondition=''
54 , subject=()
55 , description=''
56 , contributors=()
57 , effective_date=None
58 , expiration_date=None
59 , format=None
60 , language='en-US'
61 , rights=''
62 ):
63 self._setId(id)
64 if format is None:
65 format = self.content_type or 'application/octet-stream'
66
67 DefaultDublinCoreImpl.__init__( self, title, subject, description
68 , contributors, effective_date, expiration_date
69 , format, language, rights )
70 BlobFile.__init__(self, id, title, file, content_type=content_type, precondition=precondition)
71
72
73 def __getattr__(self, name) :
74 try : return CMFFile.__getattr__(self, name)
75 except :
76 selfAttrs = self.__dict__
77 if selfAttrs.has_key('_v_transform_cache') :
78 cache = selfAttrs['_v_transform_cache']
79 cacheTuple = cache.get('text_html', None) # (time, value)
80 if cacheTuple :
81 cacheData = cacheTuple[1]
82
83 subObDict = cacheData.getSubObjects()
84 if subObDict.has_key(name) :
85 fileOb = OFS.Image.File(name, name, subObDict[name])
86 return fileOb
87
88 raise AttributeError, name
89
90 def manage_upload(self,file='',REQUEST=None):
91 ret = super(File, self).manage_upload(file=file, REQUEST=REQUEST)
92
93 orig_name = OFS.Image.cookId('', '', file)[0]
94 if orig_name :
95 self.orig_name = orig_name
96
97 if self.Format() == 'application/x-shockwave-flash' :
98 if file :
99 try :
100 swfmetadata = parseswf(file)
101 except IOError :
102 swfmetadata = {'width':600, 'height':600}
103
104 for name in ('width', 'height') :
105 value = swfmetadata[name]
106 if self.hasProperty(name) :
107 self._updateProperty(name, value)
108 else :
109 self.manage_addProperty(name, value, 'int')
110 self.reindexObject()
111 return ret
112
113
114
115 security.declareProtected(ModifyPortalContent, 'edit')
116 def edit(self, precondition='', file=''):
117 orig_name = OFS.Image.cookId('', '', file)[0]
118 if orig_name :
119 self.orig_name = orig_name
120 CMFFile.edit(self, precondition=precondition, file=file)
121 if hasattr(self, '_v_transform_cache') :
122 del self._v_transform_cache
123
124
125 security.declareProtected(View, 'SearchableText')
126 def SearchableText(self) :
127 """ Return full text"""
128 baseSearchableText = CMFFile.SearchableText(self)
129 transformTool = getToolByName(self, 'portal_transforms', default=None)
130 if transformTool is None :
131 return baseSearchableText
132 else :
133 f = self.bdata.open()
134 orig = f.read()
135 datastream_text = transformTool.convertTo('text/plain',
136 orig,
137 mimetype = self.content_type
138 )
139 f.close()
140 full_text = ''
141 if datastream_text is not None :
142 full_text = datastream_text.getData()
143
144 return baseSearchableText + full_text
145
146 security.declareProtected(View, 'preview')
147 def preview(self) :
148 """Return HTML preview if it's possible or empty string """
149 transformTool = getToolByName(self, 'portal_transforms', default = None)
150 if transformTool is None :
151 return ''
152 else :
153 filename = self.getId().replace(' ', '_')
154 f = self.bdata.open()
155 orig = f.read()
156 datastream = transformTool.convertTo('text/html',
157 orig,
158 object=self,
159 mimetype = self.content_type,
160 filename = filename)
161 f.close()
162 if datastream is not None : return datastream.getData()
163 else : return ''
164
165 security.declareProtected(View, 'download')
166 def download(self, REQUEST, RESPONSE):
167 """Download this item.
168
169 Calls OFS.Image.File.index_html to perform the actual transfer after
170 first setting Content-Disposition to suggest a filename.
171
172 This method is deprecated, use the URL of this object itself. Because
173 the default view of a File object is to download, rather than view,
174 this method is obsolete. Also note that certain browsers do not deal
175 well with a Content-Disposition header.
176
177 """
178
179 RESPONSE.setHeader('Content-Disposition',
180 'attachment; filename=%s' % (self.orig_name or self.getId()))
181 return OFS.Image.File.index_html(self, REQUEST, RESPONSE)
182
183 security.declarePublic('getIcon')
184 def getIcon(self, relative_to_portal=0):
185 """ return icon corresponding to mime-type
186 """
187 regTool = getToolByName(self, 'mimetypes_registry', default=None)
188 if regTool :
189 f = self.bdata.open()
190 mime = regTool(f, mimetype=self.content_type)[2]
191 f.close()
192 return mime.icon_path
193 else :
194 return CMFFile.getIcon(self, relative_to_portal=relative_to_portal)
195
196
197 InitializeClass(File)
198 FileFactory = Factory(File)
199
200
201 def addFile( dispatcher
202 , id
203 , title=''
204 , file=''
205 , content_type=''
206 , precondition=''
207 , subject=()
208 , description=''
209 , contributors=()
210 , effective_date=None
211 , expiration_date=None
212 , format='text/html'
213 , language=''
214 , rights=''
215 ):
216 """
217 Add a File
218 """
219
220 # cookId sets the id and title if they are not explicity specified
221 id, title = OFS.Image.cookId(id, title, file)
222
223 container = dispatcher.Destination()
224
225 # Instantiate the object and set its description.
226 fobj = File( id, title=title, file='', content_type=content_type,
227 precondition=precondition, subject=subject, description=description,
228 contributors=contributors, effective_date=effective_date,
229 expiration_date=expiration_date, format=format,
230 language=language, rights=rights
231 )
232
233 # Add the File instance to self
234 container._setObject(id, fobj)
235
236 # 'Upload' the file. This is done now rather than in the
237 # constructor because the object is now in the ZODB and
238 # can span ZODB objects.
239 container._getOb(id).manage_upload(file)