##############################################################################
#
# Copyright (c) 2002 Ingeniweb SARL
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################

"""
AttachmentField product
"""


import Acquisition
from Globals import Persistent
from Globals import MessageDialog, DTMLFile      # fakes a method from a DTML file
from Globals import InitializeClass
from AccessControl import Role
from AccessControl import ClassSecurityInfo
from AccessControl import Permissions
from AccessControl import Unauthorized
from AccessControl import getSecurityManager
from webdav.common import rfc1123_date
import OFS.SimpleItem
from OFS.ObjectManager import ObjectManager
from OFS.Traversable import Traversable
from global_symbols import *
try:
    from Products.CMFCore.utils import getToolByName
except:
    pass                # No CMF -> no charset converting

from Products.Archetypes import Field

from Products.AttachmentField.interfaces.attachmentfield import IAttachmentField
import AttachmentHandler

import urllib
import string
import os
import os.path
import sys

##DEFAULT_ID = "attach"
##ZAA_READ_PERMISSION = Permissions.access_contents_information
##ZAA_WRITE_PERMISSION = Permissions.change_images_and_files

#if _AF_VOLATILE_:
#    _indexed_ = "_v_%s_AF_indexed"
#    _preview_ = "_v_%s_AF_preview"
#else:
#    _indexed_ = "_%s_AF_indexed"
#    _preview_ = "_%s_AF_preview"

_indexed_ = "_%s_AF_indexed"
_preview_ = "_%s_AF_preview"

class AttachmentField(Field.FileField):
    """
    A base class to handle file fields. This is based on Archetypes.
    When the file is uploaded, it's stored, as the File field, as a File class.

    See FileField.set() :
    
        value = File(self.getName(), '', value, mimetype)
        setattr(value, 'filename', f_name or self.getName())
        ObjectField.set(self, instance, value, **kwargs)

    """
    __implements__ = (Field.FileField.__implements__, IAttachmentField)

    def get(self, instance, mimetype = None, **kwargs):
        """Get value. If mime_type is 'text/plain', we retreive the
        indexed string. If it's text/html, we get the preview back.
        """
        if mimetype == 'text/plain':
            return self.getIndexableValue(instance)
        if mimetype == 'text/html':
            return self.getPreview(instance)
        kwargs.update({'mimetype': mimetype})
        return Field.FileField.get(self, instance, **kwargs)

    def set(self, instance, value, **kwargs):
        """
        Assign input value to object. If mimetype is not specified,
        pass to processing method without one and add mimetype returned
        to kwargs. Assign kwargs to instance.
        """
        self._reset(instance)
        return Field.FileField.set(self, instance, value, **kwargs)

    def getFilename(self, instance, fromBaseUnit=True):
        """
        getFilename(self, instance, ) => return the filename
        We split to return only a file name and not a whole path.
        """
        
        # Get object or data
        accessor = self.getAccessor(instance)
        
        if self.isEmpty(instance) or accessor is None:
            return ''
        
        obj = accessor()
        fn = ''
        
        if hasattr(obj.aq_explicit, 'filename'):
            fn = obj.filename
        elif hasattr(obj.aq_explicit, 'getFilename'):
            fn = obj.getFilename()
        else:
            fn = Field.FileField.getFilename(self, instance, fromBaseUnit)
            
        fn = string.split(fn, '/')[-1]
        fn = string.split(fn, '\\')[-1]
        return fn

    def getContentType(self, instance, fromBaseUnit=True):
        """
        getContentType(self, instance, fromBaseUnit=True) => return the content type
        """
        
        # Get object or data
        accessor = self.getAccessor(instance)
        
        if self.isEmpty(instance) or accessor is None:
            return self.default_content_type
        
        obj = accessor()
        
        if hasattr(obj.aq_explicit, 'mimetype'):
            return obj.mimetype
        elif hasattr(obj.aq_explicit, 'getContentType'):
            return obj.getContentType()
        else:
            return Field.FileField.getContentType(self, instance, fromBaseUnit)

    def getSize(self, instance):
        """
        getSize(self, instance) => return file size
        """
        if self.isEmpty(instance):
            return 0
        return self.get(instance).get_size()    # This is cached by OFS.File

    def isEmpty(self, instance):
        """
        return true if empty
        """
        instance = self.get(instance)
        if instance is None:
            return 1
        if type(instance) in (type(''), type(u'')):
            return not len(instance)
        size = 0
        try:
            size = instance.get_size()
        except:
            pass
        return not size

    def getIndexableValue(self, instance):
        """
        getIndexableValue(self, instance) => Return the value we have to index
        """
        # Emptyness check
        if self.isEmpty(instance):
            return ""

        # Is the indexing up-to-date ?
        name = self.getName()
        if not getattr(instance, _indexed_ % name, None):
            handler = self._getHandler(instance)
            try:
                idx = handler.getIndexableValue(
                    self,
                    instance,
                    )
            except:
                LogException()
                idx = None
            setattr(instance, _indexed_ % name, idx)

        # Return the index
        return getattr(instance, _indexed_ % name, None)

    def isIndexed(self, instance):
        """return true if the document is indexed properly.
        """
        if self.isEmpty(instance):
            return None
        return not not self.getIndexableValue(instance)
    

    def isPreviewAvailable(self, instance):
        """True if a preview is available for that
        (we can compute it immediately)
        """
        return not not self.getPreview(instance)

    def _getHandler(self, instance):
        """
        _getHandler(self, instance) => get the handler object
        """
        handler = AttachmentHandler.getAttachmentHandler(
            self.getContentType(instance, ),
            self, instance,
            )
        return handler


    def getPreview(self, instance):
        """Return the preview for this object
        """
        if self.isEmpty(instance):
            return ""

        # Compute it if necessary
        name = self.getName()
        if not getattr(instance, _preview_ % name, None):
            handler = self._getHandler(instance)
            try:
                preview = handler.getPreview(self, instance, )
            except:
                LogException()
                preview = None
            setattr(instance, _preview_ % name, preview, )

        # Return it
        return getattr(instance, _preview_ % name, None)
    
    def getIcon(self, instance):
        """
        getIcon(self, instance) => return the underlying file class icon (object)
        """
        handler = self._getHandler(instance)
        Log(LOG_DEBUG, "getIcon for", self.getFilename(instance), "/", handler.converter_type, )
        return getattr(instance, handler.getIconFile(self, instance))

    def getSmallIcon(self, instance):
        """
        getIcon(self, instance) => return the underlying file class icon (object)
        """
        handler = self._getHandler(instance)
        return getattr(instance, handler.getSmallIconFile(self, instance))

    def _reset(self, instance, ):
        """reset volatile stuff (indexation, preview)
        """
        name = self.getName()
        setattr(instance, _preview_ % name, None)
        setattr(instance, _indexed_ % name, None)


InitializeClass(AttachmentField)

from Products.Archetypes.Registry import registerField

registerField(
    AttachmentField,
    title='Attachment',
    description='Used for storing files with advanced features.',
    )

    

##class ZAttachmentAttribute(Acquisition.Implicit, Persistent, Traversable, ):
##    """
##    ZAttachmentAttribute is a (base) class that can be used to store attachment information
##    WARNING : SUBCLASSING THIS CLASS may break our security policy !
##    """
##    security = ClassSecurityInfo()
    
##    # Other information
##    content_type = ""
##    filename = ""
##    attachment_id = DEFAULT_ID

##    def __init__(
##        self,
##        access_permission = Permissions.access_contents_information,
##        change_permission = Permissions.change_images_and_files,
##        title = "",
##        id = None,                                                          # Default id
##        ):
##        """
##        __init__(
##            self,
##            access_permission = Permissions.access_contents_information,
##            change_permission = Permissions.change_images_and_files
##            title = ''
##        ) => Init method
##        access_permission is the permission that will be mapped as access content permission
##        and file modifying permission.
##        """
##        # Store permission information
##        self.__access_permission = access_permission
##        self.__change_permission = change_permission

##        # Store title and attachement id
##        if id is None:
##            id = DEFAULT_ID
##            Log(LOG_WARNING, "Warning: ZAttachmentAttribute initialization without an id is deprecated!")
##        self.title = title
##        self.attachment_id = id

##        # Underlying file : this is the variable the actual file abstraction class will be stored in
##        self.__underlyingFile__ = ZDummyAttachment.ZDummyAttachment()
##        self.updateCache()

##    #We need manage_afterAdd, manage_beforeDelete and manage_afterClone
##    #For example, without manage_beforeDelete, the method of the container
##    #will be called, which can lead to strange bugs. We have to support
##    #the interface of SimpleItem or Item to some level here.
##    def manage_afterAdd(self, item, container):
##        pass

##    def manage_beforeDelete(self, item, container): 
##        pass

##    def manage_afterClone(self, item):
##        pass

##    #                                                                           #
##    #                             Public interface                              #
##    #                                                                           #

##    security.declareProtected(ZAA_READ_PERMISSION, 'getContentType')
##    def getContentType(self,):
##        """
##        getContentType(self,) => this is explicit ! ;-)
##        """
##        return self.content_type

##    security.declareProtected(ZAA_READ_PERMISSION, 'getIcon')
##    def getIcon(self,):
##        """
##        getIcon(self,) => return the underlying file class icon
##        """
##        try:
##            dummy = self.icon
##        except AttributeError:
##            self.updateCache()
##        return self.icon

##    security.declareProtected(ZAA_READ_PERMISSION, 'getSmallIcon')
##    def getSmallIcon(self,):
##        """
##        getSmallIcon(self,) => return the underlying file class icon
##        """
##        try:
##            dummy = self.icon_small
##        except AttributeError:
##            self.updateCache()
##        return self.icon_small

##    security.declareProtected(ZAA_WRITE_PERMISSION, 'delete')
##    def delete(self, REQUEST = {}):
##        """
##        delete(self, REQUEST = {}) => delete the file
##        """
##        self.__underlyingFile__ = ZDummyAttachment.ZDummyAttachment()
##        self.title = ''
##        self.filename = ''
##        self.updateCache()


##    security.declareProtected(ZAA_WRITE_PERMISSION, 'upload')
##    def upload(self, file, REQUEST = {}):
##        """
##        upload(self, file, REQUEST = {}) => uploads the file

##        This method selects the right ZAbstractAttachment-based attachment class
##        from the file's content_type and delegates the job to self.uploadAttachment().

##        As usual, a REDIRECT variable can be transmitted in REQUEST so that the
##        method will redirect to a nice place.
##        """
##        # Check the file type
##        content_type = string.lower(file.headers['Content-Type'])
##        Log(LOG_DEBUG, content_type)
##        (attachment_class, class_globals, converter) = ZAttachmentRegistry.AttachmentRegistry.getAttachmentClass(content_type)
##        self.content_type = content_type

##        # Compute filename.
##        # We cannot use os.path.split here because, for example, if the server is under Unix and the client under Windows, the os.path.split would remove "/" and not "\\".
##        fn = file.filename
##        fn = string.split(fn, '/')[-1]
##        fn = string.split(fn, '\\')[-1]
##        self.filename = fn
        
##        # Manual uploading
##        Log(LOG_DEBUG, content_type, attachment_class.__name__)
##        self.uploadAttachment(attachment_class, class_globals, converter, content_type, file)

##        # Update the internal persistancy state
##        self._p_changed = 1                             # (See http://www.zope.org/Documentation/Developer/Models/ZODB/ZODB_Persistent_Objects_Persistent__p_changed_Doc.html)

##        # Redirect
##        Log(LOG_DEBUG, "File uploaded...")
##        if REQUEST.has_key('RESPONSE'):
##            redir = REQUEST.get('REDIRECT', None)
##            if redir:
##                return REQUEST.RESPONSE.redirect(redir)

##    security.declareProtected(ZAA_READ_PERMISSION, 'getIndexableValue')
##    def getIndexableValue(self,):
##        """
##        getIndexableValue(self,) => return the underlying indexable value as a (possibly big) string
##        """
##        return "%s %s" % (self.__underlyingFile__.getIndexableValue(), self.title_or_id(), )


##    security.declareProtected(ZAA_READ_PERMISSION, 'listIndexableValues')
##    def listIndexableValues(self,):
##        """
##        listIndexableValues(self,) => return the undelying indexable value as a lowercased string of unique words
##        """
##        return self.__underlyingFile__.listIndexableValues()


##    security.declareProtected(ZAA_READ_PERMISSION, 'SearchableText')
##    def SearchableText(self,):
##        """
##        SearchableText(self,) => ZCatalog support
##        """
##        Log(LOG_DEBUG, "SearchableText")
##        return "%s %s" % (self.__underlyingFile__.SearchableText(), self.title_or_id(), )


##    security.declareProtected(ZAA_READ_PERMISSION, 'getFile')
##    def getFile(self, **kw): 
##        """
##        getFile(self, **kw) => return file content
##        """
##        return self.__underlyingFile__.getContent()


##    security.declareProtected(ZAA_READ_PERMISSION, 'isEmpty')
##    def isEmpty(self,):
##        """
##        isEmpty(self,) => return 1 if file is empty
##        """
##        try:
##            dummy = self.cached_size
##        except AttributeError:
##            self.updateCache()
##        return not self.cached_size

##    security.declareProtected(ZAA_READ_PERMISSION, 'getSize')
##    def getSize(self,):
##        """
##        getSize(self,) => return 1 if file is empty
##        """
##        try:
##            dummy = self.cached_size
##        except AttributeError:
##            self.updateCache()
##        return self.cached_size

##    security.declareProtected(ZAA_READ_PERMISSION, 'getFilename')
##    def getFilename(self,):
##        """
##        getFilename(self,) => return filename
##        """
##        return self.filename


##    def __bobo_traverse__(self, REQUEST, name=None):
##        # Return file content if it is accessed through its name
##        if name == self.getFilename():
##            Log(LOG_DEBUG, "filename matches")
##            return self.index_html

##        # if a known attribute is referenced (e.g. title_or_id), return that
##        if hasattr(self, name):
##            return getattr(self, name)

##        # Return additional files that may be stored along with the attachment data.
##        # (especially images with MSWord documents)
##        related = self.__underlyingFile__.getRelatedFile(name, None)
##        if related:
##            Log(LOG_DEBUG, "Found related for", name)
##            raise "Redirect", "%s/related_content?content=%s" % (REQUEST.URL1, urllib.quote_plus(name), )

##        # Pass the request
##        return getattr(self, name)
    

##    security.declareProtected(ZAA_READ_PERMISSION, 'related_content')
##    def related_content(self, content):
##        """
##        related_content(self, content) => HTTP stream

##        This is useful to specify related content through an HTTP request.
##        """
##        Log(LOG_DEBUG, "In related_content")
##        REQUEST = self.REQUEST
##        related = self.__underlyingFile__.getRelatedFile(content)
##        return self.returnContent(REQUEST, REQUEST.RESPONSE, related['data'], related['content_type'], )
        

##    security.declareProtected(ZAA_READ_PERMISSION, 'returnContent')
##    def returnContent(self, REQUEST, RESPONSE, data, content_type, ):
##        """
##        returnContent(self, REQUEST, RESPONSE, data, content_type, ) => HTTP response
##        """
##        # HTTP If-Modified-Since header handling.
##        Log(LOG_DEBUG, "In returnContent")
##        header=REQUEST.get_header('If-Modified-Since', None)
##        if header is not None:
##            header=header.split( ';')[0]
##            try:    mod_since=long(DateTime(header).timeTime())
##            except: mod_since=None

##        RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
##        RESPONSE.setHeader('Content-Type', content_type)
##        RESPONSE.setHeader('Content-Length', len(data))
##        RESPONSE.setHeader('Accept-Ranges', 'none')

##        Log(LOG_DEBUG, "Returning data")
##        return data
        

##    security.declareProtected(ZAA_READ_PERMISSION, 'index_html')
##    def index_html(self, REQUEST, RESPONSE, ):
##        """
##        The default view of the contents of a File or Image.

##        Returns the contents of the file or image.  Also, sets the
##        Content-Type HTTP header to the objects content type.

##        THIS WAS TAKEN FROM OFS.Image MODULE !
##        """
##        return self.returnContent(REQUEST, RESPONSE, self.__underlyingFile__.content, self.content_type)

##    #                                                                           #
##    #                              Title management                             #
##    #                                                                           #

##    security.declareProtected(ZAA_READ_PERMISSION, 'title_or_id')
##    def title_or_id(self):
##        """Utility that returns the title if it is not blank and the id
##        otherwise."""
##        return self.title or self.getFilename()

##    security.declareProtected(ZAA_READ_PERMISSION, 'title_and_id')
##    def title_and_id(self):
##        """Utility that returns the title if it is not blank and the id
##        otherwise.  If the title is not blank, then the id is included
##        in parens."""
##        t=self.title
##        return t and ("%s (%s)" % (t, self.getFilename())) or self.getFilename()

##    security.declareProtected(ZAA_WRITE_PERMISSION, 'setTitle')
##    def setTitle(self, title):
##        """
##        setTitle(self, title)
##        """
##        self.title = title


##    #                                                                           #
##    #                              Other facilities                             #
##    #                                                                           #

##    security.declareProtected(ZAA_READ_PERMISSION, 'isIndexed')
##    def isIndexed(self,):
##        """
##        return 1 if the attach. is indexed properly
##        """
##        return self.__underlyingFile__.indexing_done

##    security.declareProtected(ZAA_READ_PERMISSION, 'getCharset')
##    def getCharset(self):
##        """
##        Return site default charset, or utf-8
##        """
##        try:
##            getToolByName
##        except NameError:
##            return
##        purl = getToolByName(self, 'portal_url')
##        container = purl.getPortalObject()
##        if getattr(container, 'getCharset', None):
##            return container.getCharset()

##        encoding = 'utf-8'
##        p_props = getToolByName(self, 'portal_properties', None)
##        if p_props is not None:
##            site_props = getattr(p_props, 'site_properties', None)
##            if site_props is not None:
##                encoding = site_props.getProperty('default_charset')

##        return encoding

##    security.declareProtected(ZAA_READ_PERMISSION, 'preview')
##    def preview(self,):
##        """
##        Return an HTML rendering of the document
##        """
##        # Get preview content
##        datum = self.__underlyingFile__.getPreview()

##        # Convert it if possible / necessary
##        # FIXME: we really need an unicode policy !
##        Log(LOG_DEBUG, "preview is type", type(datum))
##        if type(datum) is type(''):
##            cs = self.__underlyingFile__.getEncoding()
##            if cs:
##                Log(LOG_DEBUG, "Converting", cs, "to unicode")
##                datum = unicode(datum, cs, "ignore")
            
##        if type(datum) is type(u''):
##            cs = self.getCharset()
##            if cs:
##                Log(LOG_DEBUG, "Converting unicode to", cs)
##                datum = datum.encode(cs, "ignore")
                
##        return datum

##    security.declareProtected(ZAA_READ_PERMISSION, 'getPreview')
##    def getPreview(self,):
##        """
##        Alias / Same as preview()
##        """
##        return self.preview()

##    security.declareProtected(ZAA_READ_PERMISSION, 'getSmallPreview')
##    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.
##        """
##        return self.__underlyingFile__.getSmallPreview()

##    security.declareProtected(ZAA_READ_PERMISSION, 'isPreviewAvailable')
##    def isPreviewAvailable(self,):
##        """
##        Return true if it possible to preview this attachment as HTML
##        """
##        return self.__underlyingFile__.isPreviewAvailable()


##    #                                                                           #
##    #                                 Internals...                              #
##    #                                                                           #

##    security.declareProtected(ZAA_WRITE_PERMISSION, 'uploadAttachment')
##    def uploadAttachment(self, attachment_class, attach_globals, converter, content_type, data_stream):
##        """
##        uploadAttachment(self, attachment_class, attach_globals, converter, content_type, data_stream) => actual subclassing & uploading.
##        This method should be only called for test purposes. It delegates actual data
##        management to attachmentClass (ZAbstractAttachment-based) class.
##        data_stream is the file data as a STREAM.
##        """
##        self.__underlyingFile__ = eval("""attachment_class(content_type, data_stream, converter, )""", attach_globals, locals())
##        self.updateCache()

    
##    security.declarePrivate('updateCache')
##    def updateCache(self):
##        Log(LOG_DEBUG, "updateCache")
##        self.icon       = self.__underlyingFile__.getIcon()
##        self.icon_small = self.__underlyingFile__.getSmallIcon()
##        self.cached_size= len(self.__underlyingFile__.content)
##        self._p_changed = 1



##InitializeClass(ZAttachmentAttribute)
