# -*-coding: utf-8 -*-
#
# File: MassLoader/views.py
#
# Copyright (c) 2007 atReal
#
# GNU General Public License (GPL)
#
# 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; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#

"""
$Id$
"""

__author__ = """Matthias Broquet <contact@atreal.net>"""
__docformat__ = 'plaintext'
__licence__ = 'GPL'

# Imports: Zope
from Acquisition import aq_inner
from Products.Five  import BrowserView
from zope.event import notify
from zope.interface import implements
import transaction


# Imports: CMF
from Products.CMFCore.utils import getToolByName

# Import Plone
#from Products.CMFPlone import MessageFactory
#mf = MessageFactory('eventsubscription')

# Imports: MassLoader
from Products.MassLoader.interfaces import IMassLoaderProvider

# Imports: Python
from zipfile import ZipFile
from StringIO import StringIO
import re



class MassLoaderProvider(BrowserView):

    implements(IMassLoaderProvider)

    maxFileSize = 20000000L
    
    encoding = 'cp850'
    
    default_type = 'File'

    msg = {
            'noUpFile':"Uploaded file doesn't exist",
            'notZipFile':'Uploaded file is not a valid zip file',                
            'generalError':'Some errors occured while loading files',
            'success':'All files were successfully loaded',
            'noReport':'Error while building the report',
            'createError':'Error while creating object',
            'updateError':'Error while updating object',
            'updateOK':'File updated',
            'createOK':'File created',
            # the win patch create already the folders, so they are all conserved
            # We change the message for neutral one, XXX to be fixed later...  
            #'folderExisted':'Folder conserved',
            'folderExisted':'Folder OK',
            'folderOK':'Folder created',
            }

    log = {}
    wannaReport = {}
    report = {} 


    def __init__(self, context, request):
        self.context = context
        self.request = request


    def initReport (self,md5Id):
        """ init the string used for building the report """
        from macro_tabs import tabHead
        self.report[md5Id] = tabHead


    def addLine (self,entry,md5Id):
        """ Add a line into the report """
        from macro_tabs import tabLine
        values = [entry[item] for item in ['filename','url','title','size','status','info']]
        self.report[md5Id] += tabLine % tuple(values) 
    
    
    def finalizeReport(self,zipFileName,md5Id):
        from macro_tabs import tabFooter
        putils = getToolByName(self, 'plone_utils')
        ptypes = getToolByName(self, 'portal_types')
        self.report[md5Id] += tabFooter
        id = self.safeNormalize(putils,zipFileName+'-'+md5Id)
        try:
            ptypes.constructContent(type_name='Document', container=self.context,id=id)
            report = self.context[id]
            report.setText(self.report[md5Id])
            report.setTitle('Report %s' % zipFileName)
            report.setDescription('Import report for the zip file %s' % zipFileName)
            report.reindexObject()
        except:
            return False
        return True


    def setLog (self,md5Id,filename,title='N/A',size='0',url=None,
                status='Ok',info=None):
        entry = {
                    'filename':filename,
                    'title':title,
                    'size':size,
                    'url':url, 
                    'status':status,
                    'info':info,
                }
        if self.wannaReport[md5Id]:
            self.addLine(entry,md5Id)
        self.log[md5Id].append(entry)


    def getLog (self,md5Id):
        return self.log.pop(md5Id,[])


    def getFileTitle(self,item,lPath):
        """ Return type and title """
        if item.endswith('/'):
            return lPath[len(lPath)-2],True
        else:
            return lPath[len(lPath)-1],False


    def manage_additionalOperation(self,type,obj,**kwargs):
        """ Here we hardcoded the object creation, 
            until we find a way to proceed genericly.
            Add your CT specific operation here.
        """
        if type == 'Image':
            obj.setImage(kwargs['data'])

        elif type == 'File':
            obj.setFile(kwargs['data'])
    
    
    def reencode(self,txt):
        try:
            return txt.decode('utf-8')
        except:
            return txt.decode(self.encoding).encode('utf-8')
    
    
    def safeNormalize(self,putils,txt):
        try:
            return putils.normalizeString(txt)
        except:
            return putils.normalizeString(unicode(txt,self.encoding))

    
    def getContainer(self,path,item):
        """ Return the container object """
        ind = path.index(item)
        if not ind:
            return self.context
        else:
            putils = getToolByName(self, 'plone_utils')
            pcatalog = getToolByName(self,'portal_catalog')
            id = self.safeNormalize(putils,path[ind-1])
            normPath = [self.safeNormalize(putils,item) for item in path[:ind]]
            cPath = '/'.join(list(self.context.getPhysicalPath())+normPath)
            brain = pcatalog(path=cPath,getId=id)
            if len(brain) == 1:
                return brain[0].getObject()
            else:
                return False

    def validateFile (self,filename,zFile):
        """ Validate few parameters of the compressed file """
        zInfo = zFile.NameToInfo[filename]
        if zInfo.file_size > self.maxFileSize:
            return False,zInfo.file_size
        return True,zInfo.file_size         
  

    def createObject(self,id,isFolder,title,container,zFile,filename):
        """ Create the object """
        putils = getToolByName(self, 'plone_utils')
        ptypes = getToolByName(self, 'portal_types')
        if isFolder:
            if id in container.keys():
            # Object exists yet, it's a folder, so we keep it without change
                code = "folderExisted"
                obj = container[id]
            else:     
                try:
                    type = 'Folder'
                    ptypes.constructContent(type_name=type, container=container, 
                                            id=id, title=title)
                    obj = container[id]
                    obj.reindexObject()
                except:
                    return False,'createError',None,''
                code = 'folderOK'
        else:
            if id in container.keys():
                try:
                    obj = container[id]
                    data = zFile.read(filename)
                    type = obj.getPortalTypeName()
                    self.manage_additionalOperation(type,obj,data=data,
                                                    filename=title)
                    obj.reindexObject()
                    transaction.savepoint(optimistic=True)
                except:
                    return False,"updateError",None,''
                code = 'updateOK'
            else:
                try:
                    mtr = getToolByName(self,'mimetypes_registry')
                    ctr = getToolByName(self,'content_type_registry')                
                    data = zFile.read(filename)
                    mimetype = mtr.classify(data,filename=filename).__str__()
                    type = ctr.findTypeName(filename,mimetype,None)
                    # AH AH AH very dirty harcoded stuff... Wait for the next version
                    # to have more genericity 
                    if type != "Image" :
                      type = self.default_type   
                    ptypes.constructContent(type_name=type, container=container, 
                                            id=id)
                    obj = container[id]
                    self.manage_additionalOperation(type,obj,data=data,
                                                    filename=title)
                    obj.setTitle(title)
                    obj.reindexObject()
                    transaction.savepoint(optimistic=True)
                except:
                    return False,"createError",None,''
                code = 'createOK'
        return True,code,obj.absolute_url(),obj.getObjSize()


    def loadZipFile (self):
        """ Load the incoming file into a ZipFile object """
        if 'up_file' in self.request.keys() and self.request['up_file']:
            try:
                zFile = ZipFile (self.request['up_file'])
                md5Id = self.uniqueId(zFile.NameToInfo.keys())
                self.log[md5Id] = []
            except:
                return 'notZipFile',None
            return zFile,md5Id
        return 'noUpFile',None


    def winPatch (self,zFile,md5Id):
        """ When using the zip functionnality of Microsoft Windows,
           the zip specification is not well implemented. 
           So, we have to check it first ...  
        """
        putils = getToolByName(self, 'plone_utils')
        rootFolderList = []
        for item in zFile.namelist():
            lPath = item.split('/')
            if len(lPath)>=2 and lPath[0] not in rootFolderList:
                rootFolderList.append(lPath[0])
                id = self.safeNormalize(putils,lPath[0])
                container = self.context
                rt,code,url,size = self.createObject(id,True,
                                                self.safeNormalize(putils,lPath[0]),
                                                container,zFile,item)
                if not rt:
                    self.setLog(md5Id,self.reencode(item),status='failed',
                                info=self.msg[code])
                continue
                self.setLog(md5Id,item,title=self.reencode(lPath[0]),url=url,
                            info=self.msg[code])


    def buildTree (self,zFile,md5Id):
        """ Build the tree matching zFile """
        putils = getToolByName(self, 'plone_utils')
        error = False

        # if the user wants a saved report
        if 'build_report' in self.request.keys() and self.request['build_report']:
            self.wannaReport[md5Id] = True
            self.initReport(md5Id) 
        else:
            self.wannaReport[md5Id] = False

        self.winPatch(zFile,md5Id)
        for item in zFile.namelist():
            normalizeItem = self.reencode(item)
            rPath = []
            lPath = item.split('/')
            title,isFolder = self.getFileTitle(item,lPath) 
            if not isFolder:
                isCorrectSize,size = self.validateFile(item,zFile)
                if not isCorrectSize:
                    self.setLog(md5Id,normalizeItem,size=size,status='failed',
                                info='File size exceeds %s Mo' \
                                % str(self.maxFileSize/1000000))
                    error = True
                    continue 
            id = self.safeNormalize(putils,title)
            container = self.getContainer(lPath,title)
            title= self.reencode(title)
            if not container:
                self.setLog(md5Id,normalizeItem,size=size,status='failed',
                        info='Unable to find parent object')
                error = True
                continue 
            rt,code,url,size = self.createObject(id,isFolder,title,container,
                                                zFile,item)
            if not rt:
                self.setLog(md5Id,normalizeItem,size=size,status='failed',
                            info=self.msg[code])
                error = True
                continue
            self.setLog(md5Id,normalizeItem,title,size,url,info=self.msg[code])
        if self.wannaReport[md5Id]:
            if not self.finalizeReport(self.request['up_file'].filename,md5Id):
                return 'noReport'
        if error:
            return 'generalError'
        else:
            return 'success'


    def outMassLoader (self,out,url):
        """ Add a portal message within the return status """
        putils = getToolByName(self, 'plone_utils')
        putils.addPortalMessage(out)
        self.request.RESPONSE.redirect(url)


    def uniqueId (self,mlist):
        """ Generate a unique ID to identifiate each file contained
            into the zip file.
        """
        import md5
        return md5.new('-'.join(mlist)).hexdigest()


    def runMassLoader (self):   
        """ runMassLoader """
        out = StringIO()
        zf,md5Id = self.loadZipFile()
        curl = self.context.absolute_url()
        if isinstance(zf,ZipFile):
            code = self.buildTree(zf,md5Id)
            out.write(self.msg[code])
            url = curl+'/@@massloader_result?log='+md5Id
        else:
            out.write(self.msg[zf])
            url = curl+'/@@massloader_import'
        self.outMassLoader(out.getvalue(),url)
    
