Ticket #7078: 7078-download-all-r10500.patch
| File 7078-download-all-r10500.patch, 8.9 KB (added by rblank, 16 months ago) |
|---|
-
trac/attachment.py
diff --git a/trac/attachment.py b/trac/attachment.py
a b 18 18 19 19 from __future__ import with_statement 20 20 21 from cStringIO import StringIO 21 22 from datetime import datetime 22 23 import os.path 23 24 import re … … from trac.mimeview import * 36 37 from trac.perm import PermissionError, IPermissionPolicy 37 38 from trac.resource import * 38 39 from trac.search import search_to_sql, shorten_result 39 from trac.util import get_reporter_id, create_unique_file40 from trac.util import content_disposition, create_unique_file, get_reporter_id 40 41 from trac.util.datefmt import format_datetime, from_utimestamp, \ 41 42 to_datetime, to_utimestamp, utc 42 43 from trac.util.text import exception_to_unicode, pretty_size, print_table, \ 43 44 unicode_quote, unicode_unquote 44 45 from trac.util.translation import _, tag_ 45 from trac.web import HTTPBadRequest, IRequestHandler 46 from trac.web import HTTPBadRequest, IRequestHandler, RequestDone 46 47 from trac.web.chrome import (INavigationContributor, add_ctxtnav, add_link, 47 48 add_stylesheet, web_context) 48 49 from trac.web.href import Href … … class AttachmentModule(Component): 398 399 # IRequestHandler methods 399 400 400 401 def match_request(self, req): 401 match = re.match(r'/(raw- )?attachment/([^/]+)(?:/(.*))?$',402 match = re.match(r'/(raw-|zip-)?attachment/([^/]+)(?:/(.*))?$', 402 403 req.path_info) 403 404 if match: 404 raw, realm, path = match.groups()405 if raw:406 req.args['format'] = 'raw'405 format, realm, path = match.groups() 406 if format: 407 req.args['format'] = format[:-1] 407 408 req.args['realm'] = realm 408 409 if path: 409 410 req.args['path'] = path … … class AttachmentModule(Component): 436 437 add_ctxtnav(req, _('Back to %(parent)s', parent=parent_name), 437 438 parent_url) 438 439 439 if action != 'new' and not filename: 440 # there's a trailing '/', show the list 441 return self._render_list(req, parent) 440 if not filename: # there's a trailing '/' 441 if req.args.get('format') == 'zip': 442 self._download_as_zip(req, parent) 443 elif action != 'new': 444 return self._render_list(req, parent) 442 445 443 446 attachment = Attachment(self.env, parent.child('attachment', filename)) 444 447 … … class AttachmentModule(Component): 481 484 attachments.append(attachment) 482 485 new_att = parent.child('attachment') 483 486 return {'attach_href': get_resource_url(self.env, new_att, 484 context.href, action='new'), 487 context.href), 488 'download_href': get_resource_url(self.env, new_att, 489 context.href, format='zip'), 485 490 'can_create': 'ATTACHMENT_CREATE' in context.perm(new_att), 486 491 'attachments': attachments, 487 492 'parent': context.resource} … … class AttachmentModule(Component): 565 570 return None 566 571 format = kwargs.get('format') 567 572 prefix = 'attachment' 568 if format == 'raw':573 if format in ('raw', 'zip'): 569 574 kwargs.pop('format') 570 prefix = 'raw-attachment'575 prefix = format + '-attachment' 571 576 parent_href = unicode_unquote(get_resource_url(self.env, 572 577 resource.parent(version=None), Href(''))) 573 if not resource.id: 578 if not resource.id: 574 579 # link to list of attachments, which must end with a trailing '/' 575 580 # (see process_request) 576 return href(prefix, parent_href ) + '/'581 return href(prefix, parent_href, '', **kwargs) 577 582 else: 578 583 return href(prefix, parent_href, resource.id, **kwargs) 579 584 … … class AttachmentModule(Component): 705 710 return {'mode': 'new', 'author': get_reporter_id(req), 706 711 'attachment': attachment, 'max_size': self.max_size} 707 712 713 def _download_as_zip(self, req, parent, attachments=None): 714 req.send_response(200) 715 req.send_header('Content-Type', 'application/zip') 716 filename = 'attachments-%s-%s.zip' % \ 717 (parent.realm, re.sub(r'[/\\:]', '-', unicode(parent.id))) 718 req.send_header('Content-Disposition', 719 content_disposition('inline', filename)) 720 if attachments is None: 721 attachments = Attachment.select(self.env, parent.realm, parent.id) 722 723 from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED 724 725 buf = StringIO() 726 zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) 727 for attachment in attachments: 728 if 'ATTACHMENT_VIEW' not in req.perm(attachment.resource): 729 continue 730 zipinfo = ZipInfo() 731 zipinfo.filename = attachment.filename.encode('utf-8') 732 zipinfo.date_time = attachment.date.utctimetuple()[:6] 733 zipinfo.compress_type = ZIP_DEFLATED 734 zipinfo.comment = attachment.description 735 zipinfo.external_attr = 0644 << 16L # needed since Python 2.5 736 with attachment.open() as fd: 737 zipfile.writestr(zipinfo, fd.read()) 738 zipfile.close() 739 740 zip_str = buf.getvalue() 741 req.send_header("Content-Length", len(zip_str)) 742 req.end_headers() 743 req.write(zip_str) 744 raise RequestDone() 745 708 746 def _render_list(self, req, parent): 709 747 data = { 710 748 'mode': 'list', … … class AttachmentModule(Component): 734 772 735 773 # Eventually send the file directly 736 774 format = req.args.get('format') 737 if format in ('raw', 'txt'): 775 if format == 'zip': 776 self._download_as_zip(req, attachment.resource.parent, 777 [attachment]) 778 elif format in ('raw', 'txt'): 738 779 if not self.render_unsafe_content: 739 780 # Force browser to download files instead of rendering 740 781 # them, since they might contain malicious code enabling -
trac/htdocs/css/trac.css
diff --git a/trac/htdocs/css/trac.css b/trac/htdocs/css/trac.css
a b div.trac-grip { 445 445 .attachment #preview { margin-top: 1em } 446 446 447 447 /* Styles for the list of attachments. */ 448 #attachments > div { border: 1px outset #996; padding: 1em }449 #attachments .attachments { margin-left: 2em; padding: 0 }448 #attachments > div.attachments { border: 1px outset #996; padding: 1em } 449 #attachments dl.attachments { margin-left: 2em; padding: 0 } 450 450 #attachments dt { display: list-item; list-style: square; } 451 451 #attachments dd { font-style: italic; margin-left: 0; padding-left: 0; } 452 #attachments p { margin-left: 2em; } 452 453 453 454 /* Styles for tabular listings such as those used for displaying directory 454 455 contents and report results. */ -
trac/templates/list_of_attachments.html
diff --git a/trac/templates/list_of_attachments.html b/trac/templates/list_of_attachments.html
a b Arguments: 26 26 <div id="attachments" py:choose=""> 27 27 <py:when test="compact and alist.attachments"> 28 28 <h3 class="${foldable and 'foldable' or None}">Attachments</h3> 29 <ul> 30 <py:for each="attachment in alist.attachments"> 31 <li> 32 ${show_one_attachment(attachment)} 33 <q py:if="compact and attachment.description">${wiki_to_oneliner(context, attachment.description)}</q> 34 </li> 35 </py:for> 36 </ul> 29 <div> 30 <ul> 31 <py:for each="attachment in alist.attachments"> 32 <li> 33 ${show_one_attachment(attachment)} 34 <q py:if="compact and attachment.description">${wiki_to_oneliner(context, attachment.description)}</q> 35 </li> 36 </py:for> 37 </ul> 38 <p py:if="alist.attachments"> 39 Download all attachments as: <a rel="nofollow" href="$alist.download_href">.zip</a> 40 </p> 41 </div> 37 42 </py:when> 38 43 <py:when test="not compact"> 39 44 <h2 class="${foldable and 'foldable' or None}">Attachments</h2> 40 <div py:if="alist.attachments or alist.can_create" >45 <div py:if="alist.attachments or alist.can_create" class="attachments"> 41 46 <dl py:if="alist.attachments" class="attachments"> 42 47 <py:for each="attachment in alist.attachments"> 43 48 <dt>${show_one_attachment(attachment)}</dt> … … Arguments: 46 51 </dd> 47 52 </py:for> 48 53 </dl> 54 <p py:if="alist.attachments"> 55 Download all attachments as: <a rel="nofollow" href="$alist.download_href">.zip</a> 56 </p> 49 57 <xi:include href="attach_file_form.html"/> 50 58 </div> 51 59 </py:when>
