Edgewall Software

Ticket #2827: thumbailPreviewOfAttachImages.r6370.patch

File thumbailPreviewOfAttachImages.r6370.patch, 14.1 KB (added by Slava Zanko, 11 months ago)

renew patch

  • trac/attachment.py

     
    2020import os 
    2121import re 
    2222import shutil 
     23import Image 
    2324import time 
    2425import unicodedata 
    2526 
     
    114115        self.env = env 
    115116        self.parent_realm = self.resource.parent.realm 
    116117        self.parent_id = unicode(self.resource.parent.id) 
     118        self.first1000bytes = None 
    117119        if self.resource.id: 
    118120            self._fetch(self.resource.id, db) 
    119121        else: 
     
    123125            self.date = None 
    124126            self.author = None 
    125127            self.ipnr = None 
     128            self.mimetype = None 
     129            self.charset = None 
    126130 
    127131    def _set_filename(self, val): 
    128132        self.resource.id = val 
     
    150154        self.date = datetime.fromtimestamp(time, utc) 
    151155        self.author = row[4] 
    152156        self.ipnr = row[5] 
     157        self._get_mimetype() 
     158        self._get_charset() 
    153159 
     160    def _get_first_1000_bytes(self): 
     161        if not self.first1000bytes: 
     162            fd = self.open() 
     163            try: 
     164                str_data = fd.read(1000) 
     165                fd.seek(0) 
     166            finally: 
     167                fd.close() 
     168 
     169    def _get_charset(self): 
     170        self._get_first_1000_bytes() 
     171        mimeview = Mimeview(self.env) 
     172        self.charset = mimeview.get_charset(self.first1000bytes, self.mimetype) 
     173 
     174    def _get_mimetype(self): 
     175        self._get_first_1000_bytes() 
     176        mimeview = Mimeview(self.env) 
     177        self.mimetype = mimeview.get_mimetype(self.filename, self.first1000bytes) 
     178        if not self.mimetype: 
     179            self.mimetype = 'application/octet-stream' 
     180 
     181    def getThumbail(self, size = (160,160)): 
     182        image = Image.open(self.path) 
     183        thumbDir=os.path.join(self._get_dirPath(),'.thumb') 
     184        thumbFile=os.path.join(thumbDir,unicode_quote(self.filename)) 
     185        if not os.path.isdir(thumbDir): 
     186            os.mkdir(thumbDir) 
     187        # 200 x 150 
     188        if os.path.exists(thumbFile): 
     189            return thumbFile 
     190        if image.size[0] < size[0] and image.size[0] < size[0]: 
     191            shutil.copyfile(self.path,thumbFile) 
     192            return thumbFile 
     193        image.thumbnail(size) 
     194        image.save(thumbFile) 
     195        image.close() 
     196    def _get_dirPath(self): 
     197        return os.path.normpath(os.path.join(self.env.path, 'attachments', self.parent_realm, 
     198                            unicode_quote(self.parent_id))) 
    154199    def _get_path(self): 
    155200        path = os.path.join(self.env.path, 'attachments', self.parent_realm, 
    156201                            unicode_quote(self.parent_id)) 
     
    261306            attachment.date = datetime.fromtimestamp(time, utc) 
    262307            attachment.author = author 
    263308            attachment.ipnr = ipnr 
     309            attachment._get_mimetype() 
     310            attachment._get_charset() 
    264311            yield attachment 
    265312 
    266313    def delete_all(cls, env, parent_realm, parent_id, db): 
     
    343390    # IRequestHandler methods 
    344391 
    345392    def match_request(self, req): 
    346         match = re.match(r'^/(raw-)?attachment/([^/]+)(?:[/:](.*))?$', 
     393        match = re.match(r'^/(raw-|thumb-)?attachment/([^/]+)(?:[/:](.*))?$', 
    347394                         req.path_info) 
    348395        if match: 
    349396            raw, realm, path = match.groups() 
    350             if raw: 
     397            if raw == 'raw-': 
    351398                req.args['format'] = 'raw' 
     399            elif raw == 'thumb-': 
     400                req.args['format'] = 'thumb' 
    352401            req.args['realm'] = realm 
    353402            if path: 
    354403                req.args['path'] = path.replace(':', '/') 
     
    405454 
    406455    def get_link_resolvers(self): 
    407456        yield ('raw-attachment', self._format_link) 
     457        yield ('thumb-attachment', self._format_link) 
    408458        yield ('attachment', self._format_link) 
    409459 
    410460    # Public methods 
     
    487537        if format == 'raw': 
    488538            kwargs.pop('format') 
    489539            prefix = 'raw-attachment' 
     540        elif format == 'thumb': 
     541            kwargs.pop('format') 
     542            prefix = 'thumb-attachment' 
    490543        parent_href = unicode_unquote(get_resource_url(self.env, 
    491544                            resource.parent(version=None), Href(''))) 
    492545        if not resource.id: 
     
    510563 
    511564    # Internal methods 
    512565 
    513     def _do_save(self, req, attachment): 
     566    def _do_save_one(self, req, attachment, upload, description): 
    514567        req.perm(attachment.resource).require('ATTACHMENT_CREATE') 
    515568 
    516         if 'cancel' in req.args: 
    517             req.redirect(get_resource_url(self.env, attachment.resource.parent, 
    518                                           req.href)) 
    519569 
    520         upload = req.args['attachment'] 
    521570        if not hasattr(upload, 'filename') or not upload.filename: 
    522571            raise TracError(_('No file uploaded')) 
    523572        if hasattr(upload.file, 'fileno'): 
     
    545594            raise TracError(_('No file uploaded')) 
    546595        # Now the filename is known, update the attachment resource 
    547596        # attachment.filename = filename 
    548         attachment.description = req.args.get('description', '') 
     597        attachment.description = description 
    549598        attachment.author = get_reporter_id(req, 'author') 
    550599        attachment.ipnr = req.remote_addr 
    551600 
     
    574623            attachment.filename = None 
    575624        attachment.insert(filename, upload.file, size) 
    576625 
    577         req.redirect(get_resource_url(self.env, attachment.resource(id=None), 
     626    def _do_save_many(self,req, attachment): 
     627        curr_attach = 0 
     628        while curr_attach < int(req.args['multiple_attachments']): 
     629            upload=req.args['attachment-%d' % curr_attach] 
     630            if hasattr(upload, 'filename') and upload.filename: 
     631                attachment.filename = None 
     632                self._do_save_one(req, attachment, upload, req.args.get('description-%d' % curr_attach, '')) 
     633            curr_attach=curr_attach+1 
     634 
     635    def _do_save(self, req, attachment): 
     636        req.perm(attachment.resource).require('ATTACHMENT_CREATE') 
     637 
     638        if 'cancel' in req.args: 
     639            req.redirect(get_resource_url(self.env, attachment.resource.parent, 
     640                                          req.href)) 
     641 
     642        if 'multiple_attachments' in req.args: 
     643            self._do_save_many(req, attachment) 
     644            req.redirect(get_resource_url(self.env, attachment.resource(id=None), 
    578645                                      req.href)) 
     646        else: 
     647            self._do_save_one(req, attachment, req.args['attachment'], req.args.get('description', '')) 
     648            # Redirect the user to list of attachments (must add a trailing '/') 
     649            req.redirect(get_resource_url(self.env, attachment.resource(id=None), 
     650                                      req.href)) 
    579651 
    580652    def _do_delete(self, req, attachment): 
    581653        req.perm(attachment.resource).require('ATTACHMENT_DELETE') 
     
    623695                'title': get_resource_name(self.env, attachment.resource), 
    624696                'attachment': attachment} 
    625697 
     698        mime_type = attachment.mimetype 
    626699        fd = attachment.open() 
    627700        try: 
    628701            mimeview = Mimeview(self.env) 
    629702 
    630             # MIME type detection 
    631             str_data = fd.read(1000) 
    632             fd.seek(0) 
    633              
    634             mime_type = mimeview.get_mimetype(attachment.filename, str_data) 
    635  
    636703            # Eventually send the file directly 
    637704            format = req.args.get('format') 
    638             if format in ('raw', 'txt'): 
     705            if format == 'thumb': 
    639706                if not self.render_unsafe_content: 
     707                    req.send_header('Content-Disposition', 'attachment') 
     708                if attachment.mimetype[0:5] in ('image'): 
     709                    req.send_file(attachment.getThumbail(), mime_type) 
     710            elif format in ('raw', 'txt'): 
     711                if not self.render_unsafe_content: 
    640712                    # Force browser to download files instead of rendering 
    641713                    # them, since they might contain malicious code enabling  
    642714                    # XSS attacks 
     
    698770                format = None 
    699771                if ns.startswith('raw'): 
    700772                    format = 'raw' 
     773                elif ns.startswith('thumb'): 
     774                    format = 'thumb' 
    701775                href = get_resource_url(self.env, attachment, formatter.href, 
    702776                                        format=format) 
    703777                return tag.a(label, class_='attachment', href=href + params, 
  • trac/htdocs/js/trac.js

     
    6969function getAncestorByTagName(elem, tagName) { 
    7070  return $(elem).parents(tagName).get(0); 
    7171} 
     72var ATTACHFILE_COUNTER=0; 
     73function manageMultipleAttachFields(_obj){ 
     74    if (_obj.value == '') { 
     75        if($("#multiAttach_tbody").get(0).rows.length == 1) return; 
     76        $("#multiAttach_tbody").get(0).deleteRow($(_obj).attr("attachnum")*1); 
     77        ATTACHFILE_COUNTER=0; 
     78        $("#multiAttach_tbody tr").each(function(index, element){ 
     79            $("input", $("td", element).get(0)) 
     80                .attr("attachnum",ATTACHFILE_COUNTER) 
     81                .get(0).name = "attachment-"+ATTACHFILE_COUNTER; 
     82            $("input", $("td", element).get(1)) 
     83                .get(0).name = "description-"+ATTACHFILE_COUNTER; 
     84            ATTACHFILE_COUNTER++; 
     85        }); 
     86        $("#multiAttach_count").get(0).value = $("#multiAttach_count").val()*1-1; 
     87    } else { 
     88        if ($(_obj).attr("attachnum") != $("#multiAttach_count").val()-1) return; 
     89        var tr = $("#multiAttach_tbody").get(0).insertRow(-1); 
     90        var td = tr.insertCell(-1); 
     91        $('<input type="file" onchange="manageMultipleAttachFields(this)">') 
     92           .attr("attachnum",$("#multiAttach_count").val()) 
     93           .appendTo(td) 
     94           .get(0).name = "attachment-"+$("#multiAttach_count").val(); 
     95 
     96        td = tr.insertCell(-1); 
     97        $('<input type="text">') 
     98            .attr("size",60) 
     99            .appendTo(td) 
     100            .get(0).name = "description-"+$("#multiAttach_count").val(); 
     101        $("#multiAttach_count").get(0).value = $("#multiAttach_count").val()*1+1; 
     102    } 
     103} 
  • trac/templates/macros.html

     
    187187          </ul> 
    188188        </py:when> 
    189189        <py:when test="not compact"> 
    190           <h2>Attachments</h2> 
    191190          <div py:if="alist.attachments or alist.can_create" id="attachments"> 
     191          <h2>Attachments  (Files)</h2> 
    192192            <dl py:if="alist.attachments" class="attachments"> 
    193               <py:for each="attachment in alist.attachments"> 
     193               <py:for each="attachment in  [f for f in alist.attachments if f.mimetype[0:5] not in ('image') ]"> 
    194194                <dt>${show_one_attachment(attachment)}</dt> 
    195195                <dd py:if="attachment.description"> 
    196196                  ${wiki_to_oneliner(context, attachment.description)} 
    197197                </dd> 
    198198              </py:for> 
    199199            </dl> 
     200          <h2>Attachments  (Images)</h2> 
     201             <py:with vars="attachs = [f for f in alist.attachments if f.mimetype[0:5] in ('image') ]; fullrow = len(attachs)"> 
     202                <table align="center"> 
     203                <tr py:for="row in group(attachs, 3)"> 
     204                <py:for each="oneattach in row"> 
     205                <td py:if="not oneattach" width="210"> 
     206                &nbsp; 
     207                </td> 
     208                <td py:if="oneattach" align="center" style="border: 1px solid #f0f0f0; text-align: center;"> 
     209                <div style="width: 200px; height: 200px;"> 
     210                    <a href="${url_of(oneattach.resource)}"> 
     211                        <img src="${url_of(oneattach.resource,format='thumb')}" 
     212                        style="cursor: pointer;" 
     213                        border="0" 
     214                        id="img_${oneattach.filename}" 
     215                        /> 
     216                    </a> 
     217                </div> 
     218                <div style="background-color: #f0f0f0;"> 
     219                    <py:with vars="attach_size = round(float(oneattach.size)/float(1024),3)"> 
     220                    <small>${oneattach.filename} (${attach_size} kb)<br /> 
     221                    <i>${oneattach.description}</i> 
     222                    </small> 
     223                    </py:with> 
     224                </div> 
     225                </td> 
     226                </py:for> 
     227                </tr></table> 
     228             </py:with> 
    200229            ${attach_file_form(alist)} 
    201230          </div> 
    202231        </py:when> 
  • trac/templates/attachment.html

     
    1212 
    1313  <body py:with="parent = attachments and attachments.parent or 
    1414                          attachment.resource.parent"> 
    15     <div py:choose="mode" id="content" class="attachment"> 
    16       <py:when test="'new'"> 
    17         <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1> 
    18         <form id="attachment" method="post" enctype="multipart/form-data" action=""> 
     15    <py:def function="attach_oneFile()"> 
    1916          <div class="field"> 
    2017            <label>File<py:if test="max_size"> (size limit 
    2118                  ${pretty_size(max_size, format='%d')})</py:if>:<br /> 
     
    4138            </div> 
    4239            <br /> 
    4340          </fieldset> 
     41     
     42    </py:def> 
     43    <py:def function="attach_multiFile()"> 
     44         <table><thead> 
     45          <tr><th align="left">File</th><th align="left">Description</th></tr> 
     46         </thead> 
     47        <tbody id="multiAttach_tbody"> 
     48        <tr> 
     49            <td><input type="file" attachnum="0" name="attachment-0" onchange="manageMultipleAttachFields(this);"/></td> 
     50            <td><input type="text" name="description-0" size="60" /></td> 
     51        </tr> 
     52        </tbody></table> 
     53        <input type="hidden" id="multiAttach_count" name="multiple_attachments" value="1" /> 
     54 
     55          <fieldset> 
     56            <legend>Attachment Info</legend> 
     57            <py:if test="authname == 'anonymous'"> 
     58              <div class="field"> 
     59                <label>Your email or username:<br /> 
     60                  <input type="text" name="author" size="30" value="$author" /> 
     61                </label> 
     62              </div> 
     63              </py:if> 
     64            <div class="options"> 
     65              <label><input type="checkbox" name="replace" /> 
     66                Replace existing attachments of the same name</label> 
     67            </div> 
     68            <br /> 
     69          </fieldset> 
     70     
     71    </py:def> 
     72 
     73    <div py:choose="mode" id="content" class="attachment"> 
     74      <py:when test="'new'"> 
     75        <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1> 
     76        <form id="attachment" method="post" enctype="multipart/form-data" action=""> 
     77         
     78        ${attach_multiFile()} 
    4479          <div class="buttons"> 
    4580            <input type="hidden" name="action" value="new" /> 
    4681            <input type="hidden" name="realm" value="$parent.realm" />