view data/plugin/parser/text_x_arnica.py @ 188:a1a6a45edaf1

arnica: use StringIO file obj for cache.put(), remove unused code
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Thu, 17 Jul 2008 23:54:42 +0200
parents a2a5dedfeb1d
children 192f39a549e9
line wrap: on
line source
# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - arnica parser

    This parser is used to visualize a couple of images as a thumbnail gallery.
    Optionally, a description of an image can be added.

    By default the image name and its creation date is shown.
    If you click on a thumbnail you get navigation tools shown to slide through your images.

    Based on Gallery2 by ReimarBauer 2005-2008, ThomasWaldmann 2005, FlorianFesti 2006

    @copyright: 2008 by MoinMoin:ReimarBauer
    @license: GNU GPL, see COPYING for details.
"""

import os, re, StringIO
from random import randint

from MoinMoin import caching, config, wikiutil
from MoinMoin.action import AttachFile, cache
from MoinMoin.filter import EXIF
from MoinMoin.packages import packLine
from MoinMoin.Page import Page
from MoinMoin.util.dataset import TupleDataset, Column
from MoinMoin.widget.browser import DataBrowserWidget

try:
    import Image
except ImportError:
    Image = None

parser_name = __name__.split('.')[-1]


class _DataBrowserWidget(DataBrowserWidget):
    """ we have to remove the used form in format otherwise the needed form elements
        becomes a nested form and this raises lots of strict html4.01 errors.
    """
    def format(self):
        fmt = self.request.formatter
        result = []
        havefilters = False
        for col in self.data.columns:
            if col.autofilter:
                havefilters = True
                break
        if havefilters:
            result.append(fmt.rawHTML('<input type="submit" value="%s" %s>' % (self._filter, self._name('submit'))))

        result.append(fmt.table(1, id='%stable' % self.data_id))

        # add header line
        if self._show_header:
            result.append(fmt.table_row(1))
            for idx in range(len(self.data.columns)):
                col = self.data.columns[idx]
                if col.hidden:
                    continue
                result.append(fmt.table_cell(1))
                result.append(fmt.strong(1))
                result.append(col.label or col.name)
                result.append(fmt.strong(0))
                if col.autofilter:
                    result.append(fmt.linebreak(False))
                    select = """<select %s onchange="dbw_update_search('%s');">%s</select>""" % (
                                      self._name('filter%d' % idx),
                                      self.data_id,
                                      self._filteroptions(idx))
                    result.append(fmt.rawHTML(select))
                result.append(fmt.table_cell(0))
            result.append(fmt.table_row(0))

        # add data
        self.data.reset()
        row = self.data.next()
        if row is not None:
            filters = [None] * len(row)
            if havefilters:
                for idx in range(len(row)):
                    name = '%sfilter%d' % (self.data_id, idx)
                    if name in self.request.form:
                        filters[idx] = self.request.form[name][0]
                        if filters[idx] == self._all:
                            filters[idx] = None
        while row:
            hidden = False
            if havefilters:
                # check if row needs to be hidden due to filtering
                for idx in range(len(row)):
                    if filters[idx]:
                        if isinstance(row[idx], tuple):
                            data = unicode(row[idx][1])
                        else:
                            data = unicode(row[idx])
                        if data != '' and filters[idx] == self._notempty:
                            continue
                        if data == '' and filters[idx] == self._empty:
                            continue
                        if data != filters[idx]:
                            hidden = True
                            break
            if not hidden:
                result.append(fmt.table_row(1))
                for idx in range(len(row)):
                    if self.data.columns[idx].hidden:
                        continue
                    if isinstance(row[idx], tuple):
                        result.append(fmt.table_cell(1, abbr=unicode(row[idx][1])))
                        result.append(unicode(row[idx][0]))
                    else:
                        result.append(fmt.table_cell(1))
                        result.append(unicode(row[idx]))
                    result.append(fmt.table_cell(0))
                result.append(fmt.table_row(0))
            row = self.data.next()
        result.append(fmt.table(0))

        return ''.join(result)


def arnica_settings(target_page=u'', columns=4, file_regex=u'.',
                    image_for_webnail=False,
                    show_text=True, show_date=True, show_tools=False,
                    sort_by_date=False, sort_by_name=True, sort_by_alias=False,
                    reverse_sort=False,
                    only_items=False, template_itemlist=False,
                    album=False, album_title=unicode, album_image=u'',
                    renew=False,
                    thumbnail_width=wikiutil.UnitArgument('128', float, ['px', ], defaultunit='px'),
                    webnail_width=wikiutil.UnitArgument('640', float, ['px', ], defaultunit='px')):
    """ dummy function to initialize all default parameters for arnica. The parameters are checked for wrong input.

    @param target_page: page to read attachments from. empty pagename is current page.
    @param columns: number of columns for thumbnails
    @param file_regex: regex for selecting images
    @param image_for_webnail if set then the image is shown instead of the webnail
    @param show_text: default shows description
    @param show_date: default shows date from exif header, if available
    @param show_tools: default does not show the icon toolbar
    @param sort_by_name: default, sorts images by name
    @param sort_by_date: if set, sorts images by the modification time
    @param sort_by_alias: if set, sorts images by the alias name, this also needs only_items enabled
    @param reverse_sort: if set, the file list is sorted in reverse order
    @param only_items: if set, only images which are described in listitem are shown, e.g.
                       * [[image1.jpg|alias]]
                       * [[image2.jpg|alias]]
    @param template_itemlist: if set, an item list is shown which could be copied into the wiki page
    @param album: if set, selects album mode, only thumbnail from first image is shown, related is album title and album_image
    @param album_title: default is pagename of the images for the album.
    @param album_image: image to show on album default is the first image
    @param renew: if set then all selected thumbnails_* and webnails_* are removed and will be recreated
    @param thumbnail_width: default width of thumbnail is 128px
    @param webnail_width: default width of webnail is 640px
    """
    if thumbnail_width:
        if thumbnail_width[1] == 'px':
            thumbnail_width = '%dpx' % int(thumbnail_width[0])
        else:
            thumbnail_width = '%g%s' % thumbnail_width
    if webnail_width:
        if webnail_width[1] == 'px':
            webnail_width = '%dpx' % int(webnail_width[0])
        else:
            webnail_width = '%g%s' % webnail_width
    return locals()


# ToDo may be move to wikiutil
def get_exif_info(file_name):
    """ gets exif info from image file

    @param: image file name
    """
    date = "--"
    if wikiutil.isPicture(file_name):
        # if there is no exif data available, tags is an empty dict
        exif_file = open(file_name, 'rb')
        tags = EXIF.process_file(exif_file, 'DateTimeOriginal')
        exif_file.close()
        if 'EXIF DateTimeOriginal' in tags:
            date = str(tags['EXIF DateTimeOriginal'])
            date = date.replace(':', '-', 2)
    return date


def _get_files(request, pagename):
    # get files dependent on isPicture and ignores tmp. files
    # ToDo remove tmp. files check later
    files = AttachFile._get_files(request, pagename)
    files = [fn for fn in files if wikiutil.isPicture(fn) and not fn.startswith('tmp.')]
    return files


class Parser:
    """ arnica parser """
    extensions = '*.jpg'
    def __init__(self, raw, request, **kw):
        self.pagename = request.page.page_name
        self.raw = raw
        self.request = request
        self.formatter = request.formatter
        self.form = None
        self._ = request.getText

        args = kw.get('format_args', '')
        self.init_settings = False
        # we use a macro definition to initialize the default init parameters
        # if a user enters a wrong parameter the failure is shown by the exception
        try:
            settings = wikiutil.invoke_extension_function(request, arnica_settings, args)
            for key, value in settings.items():
                setattr(self, key, value)
            # saves the state of valid input
            self.init_settings = True
        except ValueError, err:
            msg = u"arnica: %s" % err.args[0]
            # ToDo use formatter
            request.write(self.formatter.text(msg))

        self.inner_table_style = ' style="border-style:none; margin:10px;"'
        self.td_style = ' align="center" style="padding:0; margin:2px 2px; border-style:none"'
        self.web_key = [] # array for webnail keys (medium_resolution_image)
        self.high_resolution_image = [] # array for images
        self.thumb_key = [] # array for thumbnail keys
        self.exif_date = [] # array of exif date description
        self.imgtype = [] # array of imagetype of high_resolution_image image
        self.description = [] # array of description
        self.webnail_image = [] # array of images for forms

    def html_tools_restricted(self, this_target):
        """ shows restricted tools
            @param this_target: image
        """
        if not self.request.user.may.delete(self.pagename):
            return ''
        return """
<form action="%(url)s" method="POST" enctype="multipart/form-data">
    <div class="rotate-to-left">
        <input type="hidden" name="action" value="arnica_slides">
        <input type="hidden" name="do" value="rotate_left">
        <input type="hidden" name="target" value="%(this_target)s">
        <input type="image" value="submit" src="%(htdocs)s/arnica/img/arnica_rotate_to_left.png" title="rotate to left">
    </div>
</form>
<form action="%(url)s" method="POST" enctype="multipart/form-data">
    <div class="rotate-to-right">
        <input type="hidden" name="action" value="arnica_slides">
        <input type="hidden" name="do" value="rotate_right">
         <input type="hidden" name="target" value="%(this_target)s">
        <input type="image"  value="submit" src="%(htdocs)s/arnica/img/arnica_rotate_to_right.png" title="rotate to right">
    </div>
</form>
<form action="%(url)s" method="POST" enctype="multipart/form-data">
     <div class="delete-image">
         <input type="hidden" name="action" value="arnica_slides">
         <input type="hidden" name="do" value="delete">
         <input type="hidden" name="target" value="%(this_target)s">
         <input type="image" value="submit" src="%(htdocs)s/arnica/img/arnica_remove_image.png" title="move to bak">
     </div>
</form>
""" % {
            'url': Page(self.request, self.pagename).url(self.request),
            'style': self.td_style,
            'htdocs': self.request.cfg.url_prefix_static,
            "pagename": wikiutil.quoteWikinameURL(self.pagename),
            "this_target": this_target,
        }

    def html_tools(self, idx):
        """ html code of thumbnails view with contol

        @param idx: index postion of corresponding data
        """
        this_image = self.high_resolution_image[idx]
        text = """
<form action="%(url)s" method="POST" enctype="multipart/form-data">
    <div class="get-image">
        <input type="hidden" name="action" value="AttachFile">
        <input type="hidden" name="do" value="get">
        <input type="hidden" name="target" value="%(this_target)s">
        <input type="image" value="submit" src="%(htdocs)s/arnica/img/arnica_full_image.png" title="load image">
    </div>
</form>
<form action="%(url)s" method="POST" enctype="multipart/form-data">
    <div class="slide-show">
        <input type="hidden" name="action" value="arnica_slides">
        <input type="hidden" name="do" value="slide_show">
        <input type="hidden" name="alias" value="%(description)s">
        <input type="hidden" name="target" value="%(target)s">
        <input type="hidden" name="pagename" value="%(pagename)s">
        <input type="hidden" name="images" value="%(images)s">
        <input type="hidden" name="original_images" value="%(original_images)s">
        <input type="hidden" name="exif_date" value="%(exif_date)s">
        <input type="hidden" name="image_for_webnail" value="%(image_for_webnail)s">
        <input type="image" value="submit" title="slide show" src="%(htdocs)s/arnica/img/arnica_load_slide_show.png">
    </div>
</form>
%(html_tools_restricted)s
""" % {
            "url": Page(self.request, self.pagename).url(self.request),
            "pagename": self.pagename,
            "htdocs": self.request.cfg.url_prefix_static,
            "tablestyle": self.inner_table_style,
            "style": self.td_style,
            "thumbnail_width": self.thumbnail_width,
            "description": packLine([self.description[idx]] + self.description),
            "image_for_webnail": self.image_for_webnail,
            "exif_date": packLine([self.exif_date[idx]] + self.exif_date),
            "target": self.webnail_image[idx],
            "original_images": packLine([self.high_resolution_image[idx]] + self.high_resolution_image),
            "images": packLine([self.webnail_image[idx]] + self.webnail_image),
            "original_images": packLine([self.high_resolution_image[idx]] + self.high_resolution_image),
            "this_target": self.high_resolution_image[idx],
            "thumbnail": "%s%s" % (AttachFile.getAttachUrl(self.pagename, '', self.request), self.thumb_key[idx]),
            "html_tools_restricted": self.html_tools_restricted(this_image),
        }
        return text

    def html_show_tools(self, idx):
        """ shows toolbox """
        text = ''
        if self.show_tools:
            text = '<div class="html-show-tools">%(tools)s</div>' % {
                "style": self.td_style,
                "tools": self.html_tools(idx)}
        return text

    def html_show_date(self, idx):
        """ shows date """
        text = ''
        if self.show_date:
            text = '<div class="html-show-date">%(this_exif_date)s</div>' % {
                "this_exif_date": self.formatter.text(self.exif_date[idx])}
        return text

    def html_show_alias(self, idx):
        """ view mode 1 alias text below image """
        text = ''
        if self.show_text:
            text = '<div class="html-show-alias"> %(this_alias)s</div>' % {
                    "this_alias": self.to_wikitext(self.description[idx])}
        return text

    def html_arrange_thumbnails(self, idx):
        """ defines arrangement of thumbnail, text, date and tools """
        title = ""
        if self.album:
            title = '<div class="title">%(n)d images (%(album_title)s)</div>' % {"n": len(self.high_resolution_image),
                                                        "album_title": self.album_title or self.pagename}
        text = """
<div class="arrange-thumbnails">
    <form action="%(url)s" method="POST" enctype="multipart/form-data">
        <div class="image"> %(title)s
             <input type="hidden" name="action" value="arnica_slides">
             <input type="hidden" name="do" value="slide_show">
             <input type="hidden" name="alias" value="%(description)s">
             <input type="hidden" name="target" value="%(target)s">
             <input type="hidden" name="pagename" value="%(pagename)s">
             <input type="hidden" name="images" value="%(images)s">
             <input type="hidden" name="original_images" value="%(original_images)s">
             <input type="hidden" name="exif_date" value="%(exif_date)s">
             <input type="hidden" name="image_for_webnail" value="%(image_for_webnail)s">
             <input type="image" value="submit" title="slide show" src="%(thumbnail)s">
        </div>
    </form>
    %(alias_html)s
    %(date_html)s
    %(html_tools)s
</div>
""" % {
            "title": title,
            "tdstyle": self.td_style,
            "style": self.inner_table_style,
            "url": Page(self.request, self.pagename).url(self.request),
            "pagename": self.pagename,
            "description": packLine([self.description[idx]] + self.description),
            "exif_date": packLine([self.exif_date[idx]] + self.exif_date),
            "image_for_webnail": self.image_for_webnail,
            "target": self.webnail_image[idx],
            "original_images": packLine([self.high_resolution_image[idx]] + self.high_resolution_image),
            "images": packLine([self.webnail_image[idx]] + self.webnail_image),
            "thumbnail": cache.url(self.request, self.thumb_key[idx]),
            "thumbnail_width": self.thumbnail_width,
            "html_tools": self.html_show_tools(idx),
            "date_html": self.html_show_date(idx),
            "alias_html": self.html_show_alias(idx),
        }
        return text

    def define_thumb_webnails(self, path, files, quotes):
        """ creates lists for thumbnails and webnails
        @param path: path to attachment
        @param files: file names of images
        @param quotes: text alias for image file
        """
        ddict = {}
        if len(quotes['image']) > 0:
            i = 0
            for txt in quotes['image']:
                ddict[txt] = quotes['alias'][i]
                i += 1
        for attfile in files:
            # only files not thumb_key or webnails
            self.description.append(ddict.get(attfile, attfile))
            self.high_resolution_image.append(attfile)
            fname, ext = os.path.splitext(attfile)
            key = cache.key(self.request, itemname=self.pagename, attachname=attfile)
            webnail = 'W' + key
            thumbfile = 'T' + key
            if ext in ('.gif', '.png'):
                self.imgtype.append('PNG')
            else:
                self.imgtype.append("JPEG")

            att_file = os.path.join(path, attfile)
            if os.path.exists(att_file):
                self.web_key.append(webnail)
                self.thumb_key.append(thumbfile)
                date = get_exif_info(att_file)
                self.exif_date.append(self.formatter.text(date))

    def to_wikitext(self, text):
        """ converts text to wiki name if it is written as WikiName or [[wikiname]]

        @param text: text to parse and render
        """
        text = ''.join(text)
        from MoinMoin.parser.text_moin_wiki import Parser as WikiParser
        return wikiutil.renderText(self.request, WikiParser, text)

    def get_quotes(self):
        """  gets the quotes from the item list  """
        # ToDo make this more generic and use common libs of similar modules
        quotes = self.raw.split('\n')
        quotes = [quote.strip() for quote in quotes]
        quotes = [quote[2:] for quote in quotes if quote.startswith('* ')]
        image = []
        text = []
        for line in quotes:
            if line.startswith('[[') and line.endswith(']]'):
                img, alias = line[2:-2].split('|', 1)
                alias = alias.strip()
                alias = self.formatter.text(alias)
                text.append(alias)
                image.append(img.strip())
        return {
            'alias': text,
            'image': image,
        }

    def select_files(self, formatter):
        """ select files """
        # we need to take the page_name from the formatter.page otherwise
        # include does not work
        self.pagename = formatter.page.page_name
        if self.target_page and Page(self.request, self.target_page).exists() and self.request.user.may.read(self.target_page):
            self.pagename = self.target_page
        path = AttachFile.getAttachDir(self.request, self.pagename, create=1)
        image_dict = {}
        quotes = self.get_quotes()
        if self.only_items:
            files = quotes['image']
            all_files = [fn for fn in files if wikiutil.isPicture(fn) and
                         AttachFile.exists(self.request, self.pagename, fn)]
            if self.sort_by_alias:
                alias_text = quotes['alias']
                i = 0
                for attfile in all_files:
                    image_dict[alias_text[i]] = attfile
                    i += 1
                keys = image_dict.keys()
                keys.sort()
                all_files = [image_dict[txt] for txt in keys]
        else:
            all_files = _get_files(self.request, self.pagename)
        if self.file_regex != u'.':
            all_files = [attfile for attfile in all_files if re.match(self.file_regex, attfile)]
            # ToDo and not self.only_items
        if self.sort_by_name and self.only_items is False:
            all_files.sort()
        if self.sort_by_date:
            for attfile in all_files:
                infile = os.path.join(path, attfile)
                ft_file = "%s%x" % (os.path.getmtime(infile), randint(0, 256))
                image_dict[ft_file] = attfile
            keys = image_dict.keys()
            keys.sort()
            all_files = [image_dict[txt] for txt in keys]
        image_dict.clear()
        if self.reverse_sort:
            all_files.reverse()
        if all_files:
            self.define_thumb_webnails(path, all_files, quotes)
        return all_files

    def create_thumbnail_and_webnail_image(self, image, webnail, thumbnail, image_type):
        """ creates thumbnails and webnails cache files for given image type

        @param image: filename of image
        @param webnail: name of webnail file
        @param thumbnail: name of thumbnail file
        @param image_type: filetype of image
        """
        _ = self.request.getText
        if not Image:
            msg = _('The parser %(parser)s needs python imaging library (PIL) installed') % {'parser': parser_name}
            self.request.write(self.formatter.text(msg))
            return

        path = AttachFile.getAttachDir(self.request, self.pagename, create=1)
        imagef = os.path.join(path, image)

        if os.path.getsize(imagef) == 0:
            return

        page = Page(self.request, self.pagename)

        cache_web = caching.CacheEntry(self.request, cache.cache_arena, webnail+'.data',
                                       cache.cache_scope, do_locking=False)
        cache_thumb = caching.CacheEntry(self.request, cache.cache_arena, thumbnail+'.data',
                                         cache.cache_scope, do_locking=False)

        if self.renew:
            cache_web.remove()
            cache_thumb.remove()

        if not cache_web.exists() or not cache_thumb.exists():
            try:
                im_obj = Image.open(imagef)
            except IOError:
                msg = _("attachment: %(filename)s not exists") % {"filename": os.path.basename(imagef)}
                self.request.write(self.formatter.text(msg))
                return

           # XXX later, a unit conversion method for using other units than 'px' is needed
            if not cache_web.exists():
                if not self.image_for_webnail:
                    im_obj.thumbnail((int(self.webnail_width.strip('px')), int(self.webnail_width.strip('px'))), Image.ANTIALIAS)
                    buf = StringIO.StringIO()
                    im_obj.save(buf, image_type)
                    buf.flush()
                    buf.seek(0)
                    cache.put(self.request, webnail, buf, filename=image)
                    buf.close()

            if not cache_thumb.exists():
                im_obj.thumbnail((int(self.thumbnail_width.strip('px')), int(self.thumbnail_width.strip('px'))), Image.ANTIALIAS)
                buf = StringIO.StringIO()
                im_obj.save(buf, image_type)
                buf.flush()
                buf.seek(0)
                cache.put(self.request, thumbnail, buf, filename=image)
                buf.close()

    def render(self, formatter):
        """ renders thumbnails """

        _ = self._

        # checks if initializing of all attributes in __init__ was done
        if not self.init_settings:
            return

        if self.target_page and (not Page(self.request, self.target_page).exists() or not self.request.user.may.read(self.target_page)):
            text = _("""Page '%(new_pagename)s' does not exist or you don't have enough rights.""") % {"pagename": self.target_page}
            self.request.write(self.formatter.text(text))
            return

        if not self.select_files(formatter):
            text = _("No matching image file found!")
            self.request.write(self.formatter.text(text))
            return

        if self.template_itemlist:
            self.request.write(self.formatter.div(1, css_class="text"))
            text = _("""\
Copy the following listitems into the script.
Replace alias with the label you want.
Afterwards disable template_itemlist by setting it to False:""")
            self.request.write(self.formatter.text(text))
            self.request.write(self.formatter.div(1))
            self.request.write(self.formatter.preformatted(1))
            for image in self.high_resolution_image:
                text = ' * [[%(image)s|alias]]\n' % {
                           'image': image,
                        }
                self.request.write(self.formatter.text(text))
            self.request.write(self.formatter.preformatted(0))
            self.request.write(self.formatter.div(0))
            self.request.write(self.formatter.div(0))
        COLUMNS = min([self.columns, len(self.high_resolution_image)])
        if self.album:
            COLUMNS = 1
        data = TupleDataset()
        data.columns = []
        for dummy in range(COLUMNS):
            data.columns.extend([Column('', label=(''))])
        col_count = 1
        result = []
        for image in self.high_resolution_image:
            i = self.high_resolution_image.index(image)
            self.create_thumbnail_and_webnail_image(image, self.web_key[i],
                                                    self.thumb_key[i], self.imgtype[i])
            if not self.album:
                self.webnail_image = self.web_key
                if self.image_for_webnail:
                    self.webnail_image = self.high_resolution_image
                text = self.html_arrange_thumbnails(i)
                if col_count <= COLUMNS:
                    result.append(''.join(text))
                if col_count == COLUMNS:
                    col_count = 0
                    data.addRow(tuple(result))
                    # resets the result list after the row is added
                    result = []
                col_count += 1
        if result and not self.album:
            for i in range(COLUMNS - col_count + 1):
                result.append('')
        if self.album:
            album_image = self.album_image or self.high_resolution_image[0]
            try:
                i = self.high_resolution_image.index(album_image)
                self.create_thumbnail_and_webnail_image(image, self.web_key[i], self.thumb_key[i], self.imgtype[i])
                self.webnail_image = self.web_key
                if self.image_for_webnail:
                    self.webnail_image = self.high_resolution_image
                text = self.html_arrange_thumbnails(i)
            except ValueError:
                text = self.formatter.text(_("""You can't use as album image: 
"%(album_image)s" because it does not exist or it is not listed 
in your item list!""") % {"album_image": album_image, })

            result.append(''.join(text))

        # adds the last row if it is not filled up
        if result:
            data.addRow(tuple(result))
        browser = _DataBrowserWidget(self.request, show_header=False)
        browser.setData(data)
        return browser.format()

    def format(self, formatter):
        """ parser output """
        # checks if initializing of all attributes in __init__ was done
        if self.init_settings:
            self.request.write(self.formatter.div(1, css_class="arnica"))
            self.request.write(self.render(formatter))
            self.request.write(self.formatter.div(0))