Edgewall Software

Ticket #2827: thumbailPreviewOfAttachImages.r8466.patch

File thumbailPreviewOfAttachImages.r8466.patch, 14.7 KB (added by anonymous, 2 years ago)

renew patch for r8466 (/tags/trac-0.11.4)

Line 
1Index: trac/attachment.py
2===================================================================
3--- trac/attachment.py  (revision 8466)
4+++ trac/attachment.py  (working copy)
5@@ -20,6 +20,7 @@
6 import os
7 import re
8 import shutil
9+import Image
10 import time
11 import unicodedata
12 
13@@ -116,6 +117,7 @@
14         self.env = env
15         self.parent_realm = self.resource.parent.realm
16         self.parent_id = unicode(self.resource.parent.id)
17+        self.first1000bytes = None
18         if self.resource.id:
19             self._fetch(self.resource.id, db)
20         else:
21@@ -125,6 +127,8 @@
22             self.date = None
23             self.author = None
24             self.ipnr = None
25+            self.mimetype = None
26+            self.charset = None
27 
28     def _set_filename(self, val):
29         self.resource.id = val
30@@ -152,7 +156,48 @@
31         self.date = datetime.fromtimestamp(time, utc)
32         self.author = row[4]
33         self.ipnr = row[5]
34+        self._get_mimetype()
35+        self._get_charset()
36 
37+    def _get_first_1000_bytes(self):
38+       if not self.first1000bytes:
39+           fd = self.open()
40+           try:
41+               str_data = fd.read(1000)
42+               fd.seek(0)
43+           finally:
44+               fd.close()
45+
46+    def _get_charset(self):
47+       self._get_first_1000_bytes()
48+       mimeview = Mimeview(self.env)
49+        self.charset = mimeview.get_charset(self.first1000bytes, self.mimetype)
50+
51+    def _get_mimetype(self):
52+       self._get_first_1000_bytes()
53+       mimeview = Mimeview(self.env)
54+        self.mimetype = mimeview.get_mimetype(self.filename, self.first1000bytes)
55+        if not self.mimetype:
56+            self.mimetype = 'application/octet-stream'
57+
58+    def getThumbail(self, size = (160,160)):
59+       image = Image.open(self.path)
60+       thumbDir=os.path.join(self._get_dirPath(),'.thumb')
61+       thumbFile=os.path.join(thumbDir,unicode_quote(self.filename))
62+       if not os.path.isdir(thumbDir):
63+           os.mkdir(thumbDir)
64+       # 200 x 150
65+       if os.path.exists(thumbFile):
66+           return thumbFile
67+       if image.size[0] < size[0] and image.size[0] < size[0]:
68+           shutil.copyfile(self.path,thumbFile)
69+           return thumbFile
70+       image.thumbnail(size)
71+       image.save(thumbFile)
72+       image.close()
73+    def _get_dirPath(self):
74+       return os.path.normpath(os.path.join(self.env.path, 'attachments', self.parent_realm,
75+                            unicode_quote(self.parent_id)))
76     def _get_path(self):
77         path = os.path.join(self.env.path, 'attachments', self.parent_realm,
78                             unicode_quote(self.parent_id))
79@@ -266,6 +311,8 @@
80             attachment.date = datetime.fromtimestamp(time, utc)
81             attachment.author = author
82             attachment.ipnr = ipnr
83+           attachment._get_mimetype()
84+           attachment._get_charset()
85             yield attachment
86 
87     def delete_all(cls, env, parent_realm, parent_id, db):
88@@ -349,12 +396,14 @@
89     # IRequestHandler methods
90 
91     def match_request(self, req):
92-        match = re.match(r'/(raw-)?attachment/([^/]+)(?:/(.*))?$',
93+        match = re.match(r'/(raw-|thumb-)?attachment/([^/]+)(?:/(.*))?$',
94                          req.path_info)
95         if match:
96             raw, realm, path = match.groups()
97-            if raw:
98+            if raw == 'raw-':
99                 req.args['format'] = 'raw'
100+            elif raw == 'thumb-':
101+                req.args['format'] = 'thumb'
102             req.args['realm'] = realm
103             if path:
104                 req.args['path'] = path
105@@ -415,6 +464,7 @@
106 
107     def get_link_resolvers(self):
108         yield ('raw-attachment', self._format_link)
109+        yield ('thumb-attachment', self._format_link)
110         yield ('attachment', self._format_link)
111 
112     # Public methods
113@@ -520,6 +570,9 @@
114         if format == 'raw':
115             kwargs.pop('format')
116             prefix = 'raw-attachment'
117+        elif format == 'thumb':
118+           kwargs.pop('format')
119+           prefix = 'thumb-attachment'
120         parent_href = unicode_unquote(get_resource_url(self.env,
121                             resource.parent(version=None), Href('')))
122         if not resource.id:
123@@ -544,14 +597,10 @@
124 
125     # Internal methods
126 
127-    def _do_save(self, req, attachment):
128+    def _do_save_one(self, req, attachment, upload, description):
129         req.perm(attachment.resource).require('ATTACHMENT_CREATE')
130 
131-        if 'cancel' in req.args:
132-            req.redirect(get_resource_url(self.env, attachment.resource.parent,
133-                                          req.href))
134 
135-        upload = req.args['attachment']
136         if not hasattr(upload, 'filename') or not upload.filename:
137             raise TracError(_('No file uploaded'))
138         if hasattr(upload.file, 'fileno'):
139@@ -579,7 +628,7 @@
140             raise TracError(_('No file uploaded'))
141         # Now the filename is known, update the attachment resource
142         # attachment.filename = filename
143-        attachment.description = req.args.get('description', '')
144+        attachment.description = description
145         attachment.author = get_reporter_id(req, 'author')
146         attachment.ipnr = req.remote_addr
147 
148@@ -608,8 +657,31 @@
149             attachment.filename = None
150         attachment.insert(filename, upload.file, size)
151 
152-        req.redirect(get_resource_url(self.env, attachment.resource(id=None),
153+    def _do_save_many(self,req, attachment):
154+       curr_attach = 0
155+       while curr_attach < int(req.args['multiple_attachments']):
156+           upload=req.args['attachment-%d' % curr_attach]
157+           if hasattr(upload, 'filename') and upload.filename:
158+               attachment.filename = None
159+               self._do_save_one(req, attachment, upload, req.args.get('description-%d' % curr_attach, ''))
160+           curr_attach=curr_attach+1
161+
162+    def _do_save(self, req, attachment):
163+        req.perm(attachment.resource).require('ATTACHMENT_CREATE')
164+
165+        if 'cancel' in req.args:
166+            req.redirect(get_resource_url(self.env, attachment.resource.parent,
167+                                          req.href))
168+
169+       if 'multiple_attachments' in req.args:
170+           self._do_save_many(req, attachment)
171+           req.redirect(get_resource_url(self.env, attachment.resource(id=None),
172                                       req.href))
173+       else:
174+           self._do_save_one(req, attachment, req.args['attachment'], req.args.get('description', ''))
175+           # Redirect the user to list of attachments (must add a trailing '/')
176+           req.redirect(get_resource_url(self.env, attachment.resource(id=None),
177+                                      req.href))
178 
179     def _do_delete(self, req, attachment):
180         req.perm(attachment.resource).require('ATTACHMENT_DELETE')
181@@ -655,20 +727,20 @@
182                 'title': get_resource_name(self.env, attachment.resource),
183                 'attachment': attachment}
184 
185+        mime_type = attachment.mimetype
186         fd = attachment.open()
187         try:
188             mimeview = Mimeview(self.env)
189 
190-            # MIME type detection
191-            str_data = fd.read(1000)
192-            fd.seek(0)
193-           
194-            mime_type = mimeview.get_mimetype(attachment.filename, str_data)
195-
196             # Eventually send the file directly
197             format = req.args.get('format')
198-            if format in ('raw', 'txt'):
199+            if format == 'thumb':
200                 if not self.render_unsafe_content:
201+                    req.send_header('Content-Disposition', 'attachment')
202+               if attachment.mimetype[0:5] in ('image'):
203+                   req.send_file(attachment.getThumbail(), mime_type)
204+            elif format in ('raw', 'txt'):
205+                if not self.render_unsafe_content:
206                     # Force browser to download files instead of rendering
207                     # them, since they might contain malicious code enabling
208                     # XSS attacks
209@@ -730,6 +802,8 @@
210                 format = None
211                 if ns.startswith('raw'):
212                     format = 'raw'
213+                elif ns.startswith('thumb'):
214+                    format = 'thumb'
215                 href = get_resource_url(self.env, attachment, formatter.href,
216                                         format=format)
217                 return tag.a(label, class_='attachment', href=href + params,
218Index: trac/htdocs/js/trac.js
219===================================================================
220--- trac/htdocs/js/trac.js      (revision 8466)
221+++ trac/htdocs/js/trac.js      (working copy)
222@@ -71,5 +71,79 @@
223   window.getAncestorByTagName = function(elem, tagName) {
224     return $(elem).parents(tagName).get(0);
225   }
226+})(jQuery);
227 
228-})(jQuery);
229\ No newline at end of file
230+var ATTACHFILE_COUNTER=0;
231+function manageMultipleAttachFields(_obj){
232+    if (_obj.value == '') {
233+        if($("#multiAttach_tbody").get(0).rows.length == 1) return;
234+        $("#multiAttach_tbody").get(0).deleteRow($(_obj).attr("attachnum")*1);
235+        ATTACHFILE_COUNTER=0;
236+        $("#multiAttach_tbody tr").each(function(index, element){
237+            $("input", $("td", element).get(0))
238+                .attr("attachnum",ATTACHFILE_COUNTER)
239+                .get(0).name = "attachment-"+ATTACHFILE_COUNTER;
240+            $("input", $("td", element).get(1))
241+                .get(0).name = "description-"+ATTACHFILE_COUNTER;
242+            ATTACHFILE_COUNTER++;
243+        });
244+        $("#multiAttach_count").get(0).value = $("#multiAttach_count").val()*1-1;
245+    } else {
246+        if ($(_obj).attr("attachnum") != $("#multiAttach_count").val()-1) return;
247+        var tr = $("#multiAttach_tbody").get(0).insertRow(-1);
248+        var td = tr.insertCell(-1);
249+        $('<input type="file" onchange="manageMultipleAttachFields(this)">')
250+           .attr("attachnum",$("#multiAttach_count").val())
251+           .appendTo(td)
252+           .get(0).name = "attachment-"+$("#multiAttach_count").val();
253+
254+        td = tr.insertCell(-1);
255+        $('<input type="text">')
256+            .attr("size",60)
257+            .appendTo(td)
258+            .get(0).name = "description-"+$("#multiAttach_count").val();
259+        $("#multiAttach_count").get(0).value = $("#multiAttach_count").val()*1+1;
260+    }
261+}
262+
263Index: trac/templates/macros.html
264===================================================================
265--- trac/templates/macros.html  (revision 8466)
266+++ trac/templates/macros.html  (working copy)
267@@ -189,16 +189,45 @@
268           </ul>
269         </py:when>
270         <py:when test="not compact">
271-          <h2>Attachments</h2>
272           <div py:if="alist.attachments or alist.can_create" id="attachments">
273+          <h2>Attachments (Files)</h2>
274             <dl py:if="alist.attachments" class="attachments">
275-              <py:for each="attachment in alist.attachments">
276+              <py:for each="attachment in [f for f in alist.attachments if f.mimetype[0:5] not in ('image') ]">
277                 <dt>${show_one_attachment(attachment)}</dt>
278                 <dd py:if="attachment.description">
279                   ${wiki_to_oneliner(context, attachment.description)}
280                 </dd>
281               </py:for>
282             </dl>
283+          <h2>Attachments  (Images)</h2>
284+             <py:with vars="attachs = [f for f in alist.attachments if f.mimetype[0:5] in ('image') ]; fullrow = len(attachs)">
285+                <table align="center">
286+                <tr py:for="row in group(attachs, 3)">
287+                <py:for each="oneattach in row">
288+                <td py:if="not oneattach" width="210">
289+                &nbsp;
290+                </td>
291+                <td py:if="oneattach" align="center" style="border: 1px solid #f0f0f0; text-align: center;">
292+                <div style="width: 200px; height: 200px;">
293+                    <a href="${url_of(oneattach.resource)}">
294+                        <img src="${url_of(oneattach.resource,format='thumb')}"
295+                        style="cursor: pointer;"
296+                        border="0"
297+                        id="img_${oneattach.filename}"
298+                        />
299+                    </a>
300+                </div>
301+                <div style="background-color: #f0f0f0;">
302+                    <py:with vars="attach_size = round(float(oneattach.size)/float(1024),3)">
303+                    <small>${oneattach.filename} (${attach_size} kb)<br />
304+                    <i>${oneattach.description}</i>
305+                    </small>
306+                    </py:with>
307+                </div>
308+                </td>
309+                </py:for>
310+                </tr></table>
311+             </py:with>
312             ${attach_file_form(alist, add_button_title)}
313           </div>
314         </py:when>
315Index: trac/templates/attachment.html
316===================================================================
317--- trac/templates/attachment.html      (revision 8466)
318+++ trac/templates/attachment.html      (working copy)
319@@ -12,10 +12,7 @@
320 
321   <body py:with="parent = attachments and attachments.parent or
322                           attachment.resource.parent">
323-    <div py:choose="mode" id="content" class="attachment">
324-      <py:when test="'new'">
325-        <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1>
326-        <form id="attachment" method="post" enctype="multipart/form-data" action="">
327+    <py:def function="attach_oneFile()">
328           <div class="field">
329             <label>File<py:if test="max_size >= 0"> (size limit
330                   ${pretty_size(max_size, format='%d')})</py:if>:<br />
331@@ -41,6 +38,44 @@
332             </div>
333             <br />
334           </fieldset>
335+   
336+    </py:def>
337+    <py:def function="attach_multiFile()">
338+        <table><thead>
339+          <tr><th align="left">File</th><th align="left">Description</th></tr>
340+        </thead>
341+       <tbody id="multiAttach_tbody">
342+       <tr>
343+           <td><input type="file" attachnum="0" name="attachment-0" onchange="manageMultipleAttachFields(this);"/></td>
344+           <td><input type="text" name="description-0" size="60" /></td>
345+       </tr>
346+       </tbody></table>
347+        <input type="hidden" id="multiAttach_count" name="multiple_attachments" value="1" />
348+
349+          <fieldset>
350+            <legend>Attachment Info</legend>
351+            <py:if test="authname == 'anonymous'">
352+              <div class="field">
353+                <label>Your email or username:<br />
354+                  <input type="text" name="author" size="30" value="$author" />
355+                </label>
356+              </div>
357+              </py:if>
358+            <div class="options">
359+              <label><input type="checkbox" name="replace" />
360+                Replace existing attachments of the same name</label>
361+            </div>
362+            <br />
363+          </fieldset>
364+   
365+    </py:def>
366+
367+    <div py:choose="mode" id="content" class="attachment">
368+      <py:when test="'new'">
369+        <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1>
370+        <form id="attachment" method="post" enctype="multipart/form-data" action="">
371+       
372+        ${attach_multiFile()}
373           <div class="buttons">
374             <input type="hidden" name="action" value="new" />
375             <input type="hidden" name="realm" value="$parent.realm" />