## -*- coding: utf-8 -*-
## AttchmentField
## Copyright (C)2006 Ingeniweb

## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.

## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.

## You should have received a copy of the GNU General Public License
## along with this program; see the file COPYING. If not, write to the
## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
This module provides an abstract layer to use portal_transforms instead of external
programs to convert / preview stuff.
It's an abstract class and is meant to be overloaded in content-type specific classes.
"""
__version__ = "$Revision: 23006 $"
# $Source: /cvsroot/ingeniweb/PloneSubscription/SubscriptionTool.py,v $
# $Id: PortalTransformsAttachment.py 23006 2006-05-02 20:25:43Z encolpe $
__docformat__ = 'restructuredtext'


import AttachmentHandler
import DummyAttachment

import os
import os.path
import string
import re
import sys

from AccessControl import ClassSecurityInfo
import striphtml
from global_symbols import *
from Products.PortalTransforms.utils import TransformException

AVAILABLE_ENCODINGS = ('utf8', 'Latin1', )      # Default encodings


# The actual base class
class PortalTransformsAttachment(AttachmentHandler.AbstractHandler):
    """
    A portal_transforms-based attachment.
    This special kind of handler will rely on PortalTransforms to get all the relevant
    information about your stuff, such as encoding, mime types and so on.

    It does most of the work for all plugins he's aware of.
    """
    __CHECK_INTERFACE__ = 0             # Disable IF checking
    preview_path = None
    index_path = None

    # Init method
    def getTransforms(self, field, instance):
        # Loop registered portal transforms to find those which are able to handle
        # either plain text or html conversion.
        pt = self.getPortalTransforms(field, instance, )
        mr = self.getMimetypesRegistry(field, instance, )
        _html_paths = {}               # output: path
        _text_paths = {}               # output: path
        _all_mimes = {}
        for mime in [ str(m) for m in mr.mimetypes() ]:
            try:
                p = pt._findPath(mime, "text/html")
                if p:
                    _html_paths[mime] = p
                    _all_mimes[mime] = 1
                    p = pt._findPath(mime, "text/plain")
                if p:
                    _text_paths[mime] = p
                    _all_mimes[mime] = 1
            except TransformException:
                # We ignore transform errors at this point
                pass
        return {
            "all_mimes": _all_mimes.keys(),
            "html_paths": _html_paths,
            "text_paths": _text_paths,
            }

    #                                                                   #
    #              Overridable interfaces for those methods             #
    #                                                                   #

    unknown_icon_file = DummyAttachment.DummyAttachment.icon_file
    unknown_small_icon_file = DummyAttachment.DummyAttachment.small_icon_file

    def getConverterType(self, field, instance):
        return "PortalTransforms"

    def getIconFile(self, field, instance):
        # We use Mimetypesregistry to get the icon path.
        # If no field has been supplied, we return a default 'unknown' icon
        if not field:
            return self.unknown_icon_file
        mime = self.getMimetypesRegistry(field, instance)
        t = mime.lookup(field.getContentType(instance))
        if not t:
            return self.unknown_icon_file
        return t[0].icon_path
        
    def getSmallIconFile(self, field, instance):
        # We use Mimetypesregistry to get the icon path
        # If no field has been supplied, we return a default 'unknown' icon
        if not field:
            return self.unknown_small_icon_file
        mime = self.getMimetypesRegistry(field, instance)
        t = mime.lookup(field.getContentType(instance))
        if not t:
            return self.unknown_small_icon_file
        return t[0].icon_path

    def getContentTypes(self, field, instance):
        """Return a list of the content types this tool is able to handle.
        This is quite ellaborated because we need to return only transformations with
        text/html output or pure text output.
        """
        return self.getTransforms(field, instance, )['all_mimes']

    def getIndexableValue(self, field, instance):
        """
        getIndexableValue(self, field, instance) => (possibliy big) string 
        Return the ZCatalog-indexable string for that type.
        """
        content = field.get(instance)
        content_type = field.getContentType(instance)
        return self.convertStringToIndex(content, content_type, instance)

    def getPreview(self, field, instance):
        """
        getPreview(self, field, instance) => string or None

        Return the HTML preview (generating it if it's not already done) for this attachement.
        If the attachment is not previewable, or if there's a problem in the preview,
        return None.
        """
        content = field.get(instance)
        content_type = field.getContentType(instance)
        return self.convertStringToPreview(content, content_type, instance)

    def convertStringToIndex(self, content, content_type, instance):
        """
        convertStringToIndex(self, content, content_type, instance) => Utility to convert a string to HTML
        using the converter stuff.
        """
        # Convert indexer output to plain "optimized" text
        index = self._convertStringToMime(content, content_type, instance, "text/plain")
        index = striphtml.strip(index)
        words = []
        for w in string.split(index):
            stripped = string.lower(string.strip(w))
            if not stripped in words:
                words.append(stripped)
        words.sort
        return string.join(words, " ")

    def convertStringToPreview(self, content, content_type, instance):
        """
        convertStringToPreview(self, content) => Utility to convert a string to HTML
        using the converter stuff.
        """    
        # Return the previewable string
        preview = self._convertStringToMime(content, content_type, instance, "text/html")
        return self._convertOutput(preview, "html")


    def _convertStringToMime(self, content, content_type, instance, output_mime):
        # Check if a transform is available
        trans = self.getTransforms(None, instance, )
        ct = content_type
        if not trans['html_paths'].get(ct, None):
            raise ValueError, "No converter found for content type '%s'" % (ct,)
        
        # Convert it to plain text
        pt = self.getPortalTransforms(None, instance, )
        out = pt.convertTo(
            target_mimetype = output_mime,
            orig = content,
            data = None,
            mimetype = content_type,
            )
        output = out.getData()

        # Try to use / guess the encoding
        out_encoding = out.getMetadata().get('encoding', None)
        if out_encoding:
            Log(LOG_DEBUG, "Have encoding", out_encoding)
            output = unicode(output, encoding = out_encoding, )
        else:
            # Convert from encoded string to unicode
            for enc in AVAILABLE_ENCODINGS:
                try:
                    Log(LOG_DEBUG, "Trying encoding", enc)
                    output = output.decode(enc, )
                    break
                    
                except UnicodeError:
                    Log(LOG_DEBUG, "Encoding", enc, "failed.")
                    pass

        # Return an encoded output
        return self.unicode2string(output, instance)

    def getSmallPreview(self,):
        """
        getSmallPreview(self,) => string or None

        Default behaviour : if the preview string is shorter than MAX_PREVIEW_SIZE, return it, else return None.
        You can override this, of course.
        """
        ret = self.getPreview()
        if not ret:
            return None
        if len(ret) < MAX_PREVIEW_SIZE:
            return ret
        return None


    #                                                                                   #
    #                                   Utility methods                                 #
    #                                                                                   #

    def getPortalTransforms(self, field, instance, ):
        """Return the portal_transforms tool"""
        return instance.portal_transforms

    def getMimetypesRegistry(self, field, instance, ):
        """Return the mimetypes_registry tool"""
        return instance.mimetypes_registry

    def _getTransformPath(self, input, output):
        """
        _getTransformPath(self, input, output) => chain or None
        Try to build a transform chain from 'input' mime type to 'output' mime type.
        If it's not possible to build such a chain, return None.
        Nota: this code is taken from TransformEngine.py
        """
        ## get a path to output mime type
        transform = self.getPortalTransforms()
        requirements = transform._policies.get(target_mt, [])
        path = transform._findPath(orig_mt, target_mt, list(requirements))
        if not path and requirements:
            Log(LOG_DEBUG, 'Unable to satisfy requirements %s' % ', '.join(requirements), )
            path = transform._findPath(orig_mt, target_mt)
        if not path:
            Log(LOG_DEBUG, 'NO PATH FROM %s TO %s : %s' % (orig_mt, target_mimetype, path), )
            return None
        return path


AttachmentHandler.registerHandler(PortalTransformsAttachment)
