##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# 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
#
##############################################################################

"""Support for database export and import.
"""

import POSException, string

from utils import p64, u64
from referencesf import referencesf
from cStringIO import StringIO
from cPickle import Pickler, Unpickler
TupleType=type(())

class ExportImport:

    def exportFile(self, oid, file=None):

        if file is None: file=TemporaryFile()
        elif type(file) is StringType: file=open(file,'w+b')
        write=file.write
        write('ZEXP')
        version=self._version
        ref=referencesf
        oids=[oid]
        done_oids={}
        done=done_oids.has_key
        load=self._storage.load
        while oids:
            oid=oids[0]
            del oids[0]
            if done(oid): continue
            done_oids[oid]=1
            try: p, serial = load(oid, version)
            except: pass # Ick, a broken reference
            else:
                ref(p, oids)
                write(oid)
                write(p64(len(p)))
                write(p)
        write(export_end_marker)
        return file

    def importFile(self, file, clue='', customImporters=None):
        # This is tricky, because we need to work in a transaction!

        if type(file) is StringType:
            file_name=file
            file=open(file,'rb')
        else:
            try: file_name=file.name
            except: file_name='(unknown)'
        read=file.read

        magic=read(4)

        if magic != 'ZEXP':
            if customImporters and customImporters.has_key(magic):
                file.seek(0)
                return customImporters[magic](self, file, clue)
            raise POSException.ExportError, 'Invalid export header'

        t = get_transaction()
        if clue: t.note(clue)

        return_oid_list = []
        self.onCommitAction('_importDuringCommit', file, return_oid_list)
        t.commit(1)
        # Return the root imported object.
        if return_oid_list:
            return self[return_oid_list[0]]
        else:
            return None

    def _importDuringCommit(self, transaction, file, return_oid_list):
        '''
        Invoked by the transaction manager mid commit.
        Appends one item, the OID of the first object created,
        to return_oid_list.
        '''
        oids = {}
        storage = self._storage
        new_oid = storage.new_oid
        store = storage.store
        read = file.read

        def persistent_load(ooid,
                            Ghost=Ghost, StringType=StringType,
                            atoi=string.atoi, TupleType=type(()),
                            oids=oids, wrote_oid=oids.has_key,
                            new_oid=storage.new_oid):

            "Remap a persistent id to a new ID and create a ghost for it."

            if type(ooid) is TupleType: ooid, klass = ooid
            else: klass=None

            if wrote_oid(ooid): oid=oids[ooid]
            else:
                if klass is None: oid=new_oid()
                else: oid=new_oid(), klass
                oids[ooid]=oid

            Ghost=Ghost()
            Ghost.oid=oid
            return Ghost

        version = self._version

        while 1:
            h=read(16)
            if h==export_end_marker: break
            if len(h) != 16:
                raise POSException.ExportError, 'Truncated export file'
            l=u64(h[8:16])
            p=read(l)
            if len(p) != l:
                raise POSException.ExportError, 'Truncated export file'

            ooid=h[:8]
            if oids:
                oid=oids[ooid]
                if type(oid) is TupleType: oid=oid[0]
            else:
                oids[ooid] = oid = storage.new_oid()
                return_oid_list.append(oid)

            pfile=StringIO(p)
            unpickler=Unpickler(pfile)
            unpickler.persistent_load=persistent_load

            newp=StringIO()
            pickler=Pickler(newp,1)
            pickler.persistent_id=persistent_id

            pickler.dump(unpickler.load())
            pickler.dump(unpickler.load())
            p=newp.getvalue()
            plen=len(p)

            store(oid, None, p, version, transaction)


StringType=type('')

def TemporaryFile():
    # This is sneaky suicide
    global TemporaryFile
    import tempfile
    TemporaryFile=tempfile.TemporaryFile
    return TemporaryFile()

export_end_marker='\377'*16

class Ghost: pass

def persistent_id(object, Ghost=Ghost):
    if getattr(object, '__class__', None) is Ghost:
        return object.oid
