/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: storlckb.cxx,v $
 *
 *  $Revision: 1.7 $
 *
 *  last change: $Author: hr $ $Date: 2006-06-20 00:33:44 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

#define _STORE_STORLCKB_CXX_ "$Revision: 1.7 $"

#ifndef _SAL_TYPES_H_
#include <sal/types.h>
#endif
#ifndef _SAL_MACROS_H_
#include <sal/macros.h>
#endif

#ifndef _RTL_MEMORY_H_
#include <rtl/memory.h>
#endif
#ifndef _RTL_TEXTCVT_H_
#include <rtl/textcvt.h>
#endif
#ifndef _RTL_STRING_HXX_
#include <rtl/string.hxx>
#endif
#ifndef _RTL_REF_HXX_
#include <rtl/ref.hxx>
#endif

#ifndef _OSL_MUTEX_HXX_
#include <osl/mutex.hxx>
#endif

#ifndef _STORE_TYPES_H_
#include <store/types.h>
#endif
#ifndef _STORE_OBJECT_HXX_
#include <store/object.hxx>
#endif
#ifndef _STORE_LOCKBYTE_HXX_
#include <store/lockbyte.hxx>
#endif

#ifndef _STORE_STORBASE_HXX_
#include <storbase.hxx>
#endif
#ifndef _STORE_STORDATA_HXX_
#include <stordata.hxx>
#endif
#ifndef _STORE_STORPAGE_HXX_
#include <storpage.hxx>
#endif

#ifndef _STORE_STORLCKB_HXX_
#include <storlckb.hxx>
#endif

using namespace store;

/*========================================================================
 *
 * OStore... internals.
 *
 *======================================================================*/
/*
 * __store_convertTextToUnicode.
 */
inline sal_Size __store_convertTextToUnicode (
    rtl_TextToUnicodeConverter  hConverter,
    const sal_Char *pSrcBuffer, sal_Size nSrcLength,
    sal_Unicode    *pDstBuffer, sal_Size nDstLength)
{
    sal_uInt32 nCvtInfo = 0;
    sal_Size nCvtBytes = 0;
    return rtl_convertTextToUnicode (
        hConverter, 0,
        pSrcBuffer, nSrcLength,
        pDstBuffer, nDstLength,
        OSTRING_TO_OUSTRING_CVTFLAGS,
        &nCvtInfo, &nCvtBytes);
}

/*
 * __store_iget.
 * Precond: exclusive access.
 */
static storeError __store_iget (
    OStorePageManager       &rManager,
    OStoreDirectoryPageData &rNode,
    sal_uInt32               nAttrib,
    const rtl_String        *pPath,
    const rtl_String        *pName,
    storeAccessMode          eMode)
{
    // Setup inode page key.
    OStorePageKey aKey;
    storeError eErrCode = OStorePageNameBlock::namei (pPath, pName, aKey);
    if (eErrCode != store_E_None)
        return eErrCode;

    // Check for directory.
    if (nAttrib & STORE_ATTRIB_ISDIR)
    {
        // Ugly, but necessary (backward compatibility).
        aKey.m_nLow = OStorePageGuard::crc32 (aKey.m_nLow, "/", 1);
    }

    // Load inode page.
    OStoreDirectoryPageObject aPage (rNode);
    eErrCode = rManager.load (aKey, aPage);
    if (eErrCode != store_E_None)
    {
        // Check mode and reason.
        if (eErrCode != store_E_NotExists)
            return eErrCode;

        if (eMode == store_AccessReadWrite)
            return store_E_NotExists;
        if (eMode == store_AccessReadOnly)
            return store_E_NotExists;

        if (!rManager.isWriteable())
            return store_E_AccessViolation;

        // Setup inode nameblock.
        rNode.m_aNameBlock.m_aKey    = aKey;
        rNode.m_aNameBlock.m_nAttrib = nAttrib;

        rtl_copyMemory (
            &rNode.m_aNameBlock.m_pData[0],
            pName->buffer, pName->length);

        // Save inode page.
        eErrCode = rManager.save (aKey, aPage);
        if (eErrCode != store_E_None)
            return eErrCode;
    }

    // Check for symbolic link.
    if (aPage.attrib() & STORE_ATTRIB_ISLINK)
    {
        // Obtain 'Destination' page key.
        OStorePageKey aDstKey;
        rtl_copyMemory (&aDstKey, &rNode.m_pData[0], sizeof(aDstKey));

#ifdef OSL_BIGENDIAN
        // Swap to internal representation.
        aDstKey.swap();
#endif /* OSL_BIGENDIAN */

        // Load 'Destination' inode.
        eErrCode = rManager.load (aDstKey, aPage);
        if (eErrCode != store_E_None)
            return eErrCode;
    }

    // Done.
    return store_E_None;
}

/*========================================================================
 *
 * OStoreDirectory implementation.
 *
 *======================================================================*/
const sal_uInt32 OStoreDirectory::m_nTypeId = sal_uInt32(0x89191107);

/*
 * OStoreDirectory.
 */
OStoreDirectory::OStoreDirectory (void)
    : m_xManager (NULL),
      m_pNode    (NULL),
      m_aDescr   (0, 0, 0),
      m_nPath    (0),
      m_hTextCvt (NULL)
{
}

/*
 * ~OStoreDirectory.
 */
OStoreDirectory::~OStoreDirectory (void)
{
    if (m_xManager.is())
    {
        osl::MutexGuard aGuard (*m_xManager);
        if (m_pNode)
        {
            m_xManager->releasePage (m_aDescr);
        }
    }
    delete m_pNode;
    rtl_destroyTextToUnicodeConverter (m_hTextCvt);
}

/*
 * isKindOf.
 */
sal_Bool SAL_CALL OStoreDirectory::isKindOf (sal_uInt32 nTypeId)
{
    return (nTypeId == m_nTypeId);
}

/*
 * create.
 */
storeError OStoreDirectory::create (
    OStorePageManager *pManager,
    rtl_uString       *pPath,
    rtl_uString       *pName,
    storeAccessMode    eMode)
{
    rtl::Reference<OStorePageManager> xManager (pManager);
    if (!xManager.is())
        return store_E_InvalidAccess;

    if (!(pPath && pName))
        return store_E_InvalidParameter;

    osl::MutexGuard aGuard (*xManager);
    storeError eErrCode = xManager->getPageSize (m_aDescr.m_nSize);
    if (eErrCode != store_E_None)
        return eErrCode;

    delete m_pNode;
    m_pNode = new(m_aDescr.m_nSize) inode(m_aDescr.m_nSize);
    if (!m_pNode)
        return store_E_OutOfMemory;

    rtl::OString aPath (pPath->buffer, pPath->length, RTL_TEXTENCODING_UTF8);
    rtl::OString aName (pName->buffer, pName->length, RTL_TEXTENCODING_UTF8);

    eErrCode = __store_iget (
        *xManager, *m_pNode, STORE_ATTRIB_ISDIR,
        aPath.pData, aName.pData, eMode);
    if (eErrCode != store_E_None)
        return eErrCode;

    sal_uInt32 nAttrib = m_pNode->m_aNameBlock.m_nAttrib;
    if (!(nAttrib & STORE_ATTRIB_ISDIR))
        return store_E_NotDirectory;

    m_aDescr = m_pNode->m_aDescr;
    eErrCode = xManager->acquirePage (m_aDescr, store_AccessReadOnly);
    if (eErrCode == store_E_None)
    {
        // Evaluate iteration path from NameBlock.
        typedef OStorePageGuard G;
        sal_Char *pszName = m_pNode->m_aNameBlock.m_pData;

        m_nPath = m_pNode->m_aNameBlock.m_aKey.m_nHigh;
        m_nPath = G::crc32 (m_nPath, pszName, rtl_str_getLength(pszName));
        m_nPath = G::crc32 (m_nPath, "/", 1);

        // Accept page manager.
        m_xManager = xManager;
    }
    return eErrCode;
}

/*
 * iterate.
 */
storeError OStoreDirectory::iterate (storeFindData &rFindData)
{
    if (!m_xManager.is())
        return store_E_InvalidAccess;

    storeError eErrCode = store_E_NoMoreFiles;
    if (!rFindData.m_nReserved)
        return eErrCode;

    // Acquire exclusive access.
    osl::MutexGuard aGuard (*m_xManager);

    // Check TextConverter.
    if (m_hTextCvt == NULL)
        m_hTextCvt = rtl_createTextToUnicodeConverter(RTL_TEXTENCODING_UTF8);

    // Setup iteration key and page buffer.
    OStorePageKey aKey (rFindData.m_nReserved, m_nPath);
    OStoreDirectoryPageObject aPage (*m_pNode);

    // Iterate.
    for (;;)
    {
        eErrCode = m_xManager->iterate (aKey, aPage, rFindData.m_nAttrib);
        if (!((eErrCode == store_E_None) && (aKey.m_nHigh == m_nPath)))
            break;

        if (!(rFindData.m_nAttrib & STORE_ATTRIB_ISLINK))
        {
            // Load page.
            eErrCode = m_xManager->load (aPage);
            if (eErrCode == store_E_None)
            {
                // Setup FindData.
                sal_Char *p = m_pNode->m_aNameBlock.m_pData;
                sal_Size  n = rtl_str_getLength (p);
                sal_Size  k = rFindData.m_nLength;

                n = __store_convertTextToUnicode (
                    m_hTextCvt, p, n,
                    rFindData.m_pszName, STORE_MAXIMUM_NAMESIZE - 1);
                if (k > n)
                {
                    k = (k - n) * sizeof(sal_Unicode);
                    rtl_zeroMemory (&rFindData.m_pszName[n], k);
                }

                rFindData.m_nLength  = n;
                rFindData.m_nAttrib |= aPage.attrib();
                rFindData.m_nSize    = aPage.dataLength();

                // Leave.
                rFindData.m_nReserved = aKey.m_nLow;
                return store_E_None;
            }
        }

        if (aKey.m_nLow > 0)
            aKey.m_nLow -= 1;
        else
            break;
    }

    // Finished.
    rtl_zeroMemory (&rFindData, sizeof (storeFindData));
    return store_E_NoMoreFiles;
}

/*========================================================================
 *
 * OStoreLockBytes implementation.
 *
 *======================================================================*/
const sal_uInt32 OStoreLockBytes::m_nTypeId = sal_uInt32(0x94190310);

/*
 * OStoreLockBytes.
 */
OStoreLockBytes::OStoreLockBytes (void)
    : m_xManager   (NULL),
      m_pNode      (NULL),
      m_pData      (NULL),
      m_pSingle    (NULL),
      m_pDouble    (NULL),
      m_pTriple    (NULL),
      m_nPageSize  (0),
      m_bWriteable (sal_False)
{
}

/*
 * ~OStoreLockBytes.
 */
OStoreLockBytes::~OStoreLockBytes (void)
{
    if (m_xManager.is())
    {
        osl::MutexGuard aGuard (*m_xManager);
        if (m_pNode)
        {
            OStorePageDescriptor aDescr (m_pNode->m_aDescr);
            m_xManager->releasePage (aDescr);
        }
    }

    delete m_pNode;
    delete m_pData;

    delete m_pSingle;
    delete m_pDouble;
    delete m_pTriple;
}

/*
 * isKindOf.
 */
sal_Bool SAL_CALL OStoreLockBytes::isKindOf (sal_uInt32 nTypeId)
{
    return (nTypeId == m_nTypeId);
}

/*
 * acquire.
 */
oslInterlockedCount SAL_CALL OStoreLockBytes::acquire (void)
{
    return OStoreObject::acquire();
}

/*
 * release.
 */
oslInterlockedCount SAL_CALL OStoreLockBytes::release (void)
{
    return OStoreObject::release();
}

/*
 * create.
 */
storeError OStoreLockBytes::create (
    OStorePageManager *pManager,
    rtl_uString       *pPath,
    rtl_uString       *pName,
    storeAccessMode    eMode)
{
    rtl::Reference<OStorePageManager> xManager (pManager);
    if (!xManager.is())
        return store_E_InvalidAccess;

    if (!(pPath && pName))
        return store_E_InvalidParameter;

    osl::MutexGuard aGuard (*xManager);
    storeError eErrCode = xManager->getPageSize (m_nPageSize);
    if (eErrCode != store_E_None)
        return eErrCode;

    delete m_pNode;
    m_pNode = new(m_nPageSize) inode(m_nPageSize);
    if (!m_pNode)
        return store_E_OutOfMemory;

    rtl::OString aPath (pPath->buffer, pPath->length, RTL_TEXTENCODING_UTF8);
    rtl::OString aName (pName->buffer, pName->length, RTL_TEXTENCODING_UTF8);

    eErrCode = __store_iget (
        *xManager, *m_pNode, STORE_ATTRIB_ISFILE,
        aPath.pData, aName.pData, eMode);
    if (eErrCode != store_E_None)
        return eErrCode;

    sal_uInt32 nAttrib = m_pNode->m_aNameBlock.m_nAttrib;
    if (!(nAttrib & STORE_ATTRIB_ISFILE))
    {
        // No ISFILE in older versions (backward compatibility).
        if (nAttrib & STORE_ATTRIB_ISLINK)
            return store_E_NotFile;
    }

    // ...
    OStorePageDescriptor aDescr (m_pNode->m_aDescr);
    if (eMode != store_AccessReadOnly)
        eErrCode = xManager->acquirePage (aDescr, store_AccessReadWrite);
    else
        eErrCode = xManager->acquirePage (aDescr, store_AccessReadOnly);
    if (eErrCode != store_E_None)
        return eErrCode;

    // ...
    m_xManager   = xManager;
    m_bWriteable = (eMode != store_AccessReadOnly);

    // Check for truncation.
    if (eMode == store_AccessCreate)
    {
        // Truncate to zero length.
        eErrCode = setSize(0);
    }
    return eErrCode;
}

/*
 * readAt.
 */
storeError OStoreLockBytes::readAt (
    sal_uInt32  nOffset,
    void       *pBuffer,
    sal_uInt32  nBytes,
    sal_uInt32 &rnDone)
{
    rnDone = 0;

    if (!m_xManager.is())
        return store_E_InvalidAccess;

    if (!pBuffer)
        return store_E_InvalidParameter;
    if (!nBytes)
        return store_E_None;

    // Acquire exclusive access.
    osl::MutexGuard aGuard (*m_xManager);

    // Determine data length.
    OStoreDirectoryPageObject aPage (*m_pNode);

    sal_uInt32 nDataLen = aPage.dataLength();
    if ((nOffset + nBytes) > nDataLen)
        nBytes = nDataLen - nOffset;

    // Read data.
    sal_uInt8 *pData = (sal_uInt8*)pBuffer;
    while ((0 < nBytes) && (nOffset < nDataLen))
    {
        // Determine 'Offset' scope.
        inode::ChunkScope eScope = m_pNode->scope (nOffset);
        if (eScope == inode::SCOPE_INTERNAL)
        {
            // Read from inode page (internal scope).
            inode::ChunkDescriptor aDescr (
                nOffset, m_pNode->capacity());

            sal_uInt32 nLength = sal_uInt32(aDescr.m_nLength);
            nLength = SAL_MIN(nLength, nBytes);

            rtl_copyMemory (
                &pData[rnDone],
                &m_pNode->m_pData[aDescr.m_nOffset],
                nLength);

            // Adjust counters.
            rnDone  += nLength;
            nOffset += nLength;
            nBytes  -= nLength;
        }
        else
        {
            // Read from data page (external scope).
            if (!m_pData)
                m_pData = new(m_nPageSize) data(m_nPageSize);
            if (!m_pData)
                return store_E_OutOfMemory;
            OStoreDataPageObject aData (*m_pData);

            inode::ChunkDescriptor aDescr (
                nOffset - m_pNode->capacity(), m_pData->capacity());

            sal_uInt32 nLength = sal_uInt32(aDescr.m_nLength);
            nLength = SAL_MIN(nLength, nBytes);

            storeError eErrCode = aPage.get (
                aDescr.m_nPage, m_pSingle, m_pDouble, m_pTriple,
                aData, *m_xManager, NULL);
            if (eErrCode != store_E_None)
            {
                if (eErrCode != store_E_NotExists)
                    return eErrCode;

                rtl_zeroMemory (
                    &pData[rnDone],
                    nLength);
            }
            else
            {
                rtl_copyMemory (
                    &pData[rnDone],
                    &m_pData->m_pData[aDescr.m_nOffset],
                    nLength);
            }

            // Adjust counters.
            rnDone  += nLength;
            nOffset += nLength;
            nBytes  -= nLength;
        }
    }

    // Done.
    return store_E_None;
}

/*
 * writeAt.
 */
storeError OStoreLockBytes::writeAt (
    sal_uInt32  nOffset,
    const void *pBuffer,
    sal_uInt32  nBytes,
    sal_uInt32 &rnDone)
{
    rnDone = 0;

    if (!m_xManager.is())
        return store_E_InvalidAccess;
    if (!m_bWriteable)
        return store_E_AccessViolation;

    if (!pBuffer)
        return store_E_InvalidParameter;
    if (!nBytes)
        return store_E_None;

    // Acquire exclusive access.
    osl::MutexGuard aGuard (*m_xManager);

    // Write data.
    OStoreDirectoryPageObject aPage (*m_pNode);
    const sal_uInt8 *pData = (const sal_uInt8*)pBuffer;

    storeError eErrCode = store_E_None;
    while (nBytes > 0)
    {
        // Determine 'Offset' scope.
        inode::ChunkScope eScope = m_pNode->scope (nOffset);
        if (eScope == inode::SCOPE_INTERNAL)
        {
            // Write to inode page (internal scope).
            inode::ChunkDescriptor aDescr (
                nOffset, m_pNode->capacity());

            sal_uInt32 nLength = sal_uInt32(aDescr.m_nLength);
            nLength = SAL_MIN(nLength, nBytes);

            rtl_copyMemory (
                &m_pNode->m_pData[aDescr.m_nOffset],
                &pData[rnDone], nLength);

            // Mark inode dirty.
            aPage.touch();

            // Adjust counters.
            rnDone  += nLength;
            nOffset += nLength;
            nBytes  -= nLength;

            // Adjust data length.
            if (aPage.dataLength() < nOffset)
                aPage.dataLength (nOffset);
        }
        else
        {
            // Write to data page (external scope).
            if (!m_pData)
                m_pData = new(m_nPageSize) data(m_nPageSize);
            if (!m_pData)
                return store_E_OutOfMemory;
            OStoreDataPageObject aData (*m_pData);

            inode::ChunkDescriptor aDescr (
                nOffset - m_pNode->capacity(), m_pData->capacity());

            sal_uInt32 nLength = sal_uInt32(aDescr.m_nLength);
            if ((aDescr.m_nOffset > 0) || (nBytes < nLength))
            {
                // Unaligned. Need to load/create data page.
                eErrCode = aPage.get (
                    aDescr.m_nPage, m_pSingle, m_pDouble, m_pTriple,
                    aData, *m_xManager, NULL);
                if (eErrCode != store_E_None)
                {
                    if (eErrCode != store_E_NotExists)
                        return eErrCode;

                    rtl_zeroMemory (
                        &m_pData->m_pData[0],
                        m_pData->capacity());
                }
            }

            // Modify data page.
            nLength = SAL_MIN(nLength, nBytes);

            rtl_copyMemory (
                &m_pData->m_pData[aDescr.m_nOffset],
                &pData[rnDone], nLength);

            // Save data page.
            eErrCode = aPage.put (
                aDescr.m_nPage, m_pSingle, m_pDouble, m_pTriple,
                aData, *m_xManager, NULL);
            if (eErrCode != store_E_None)
                return eErrCode;

            // Adjust counters.
            rnDone  += nLength;
            nOffset += nLength;
            nBytes  -= nLength;

            // Adjust data length.
            if (aPage.dataLength() < nOffset)
                aPage.dataLength (nOffset);
        }
    }

    // Check for modified inode.
    if (aPage.dirty())
        return m_xManager->save (aPage);
    else
        return store_E_None;
}

/*
 * flush.
 */
storeError OStoreLockBytes::flush (void)
{
    if (!m_xManager.is())
        return store_E_InvalidAccess;

    return m_xManager->flush();
}

/*
 * setSize.
 */
storeError OStoreLockBytes::setSize (sal_uInt32 nSize)
{
    if (!m_xManager.is())
        return store_E_InvalidAccess;
    if (!m_bWriteable)
        return store_E_AccessViolation;

    // Acquire exclusive access.
    osl::MutexGuard aGuard (*m_xManager);

    // Determine current length.
    OStoreDirectoryPageObject aPage (*m_pNode);
    sal_uInt32 nDataLen = aPage.dataLength();

    if (nSize == nDataLen)
        return store_E_None;

    if (nSize < nDataLen)
    {
        // Truncate.
        storeError eErrCode = store_E_None;

        // Determine 'Size' scope.
        inode::ChunkScope eSizeScope = m_pNode->scope (nSize);
        if (eSizeScope == inode::SCOPE_INTERNAL)
        {
            // Internal 'Size' scope. Determine 'Data' scope.
            inode::ChunkScope eDataScope = m_pNode->scope (nDataLen);
            if (eDataScope == inode::SCOPE_EXTERNAL)
            {
                // External 'Data' scope.
                if (!m_pData)
                    m_pData = new(m_nPageSize) data(m_nPageSize);
                if (!m_pData)
                    return store_E_OutOfMemory;
                OStoreDataPageObject aData (*m_pData);

                // Truncate all external data pages.
                eErrCode = aPage.truncate (
                    0, m_pSingle, m_pDouble, m_pTriple,
                    aData, *m_xManager, NULL);
                if (eErrCode != store_E_None)
                    return eErrCode;
            }

            // Truncate internal data page.
            inode::ChunkDescriptor aDescr (nSize, m_pNode->capacity());
            rtl_zeroMemory (
                &m_pNode->m_pData[aDescr.m_nOffset],
                aDescr.m_nLength);
        }
        else
        {
            // External 'Size' scope.
            if (!m_pData)
                m_pData = new(m_nPageSize) data(m_nPageSize);
            if (!m_pData)
                return store_E_OutOfMemory;
            OStoreDataPageObject aData (*m_pData);

            // Truncate external data pages.
            inode::ChunkDescriptor aDescr (
                nSize - m_pNode->capacity(), m_pData->capacity());

            sal_uInt32 nPage = aDescr.m_nPage;
            if (aDescr.m_nOffset) nPage += 1;

            eErrCode = aPage.truncate (
                nPage, m_pSingle, m_pDouble, m_pTriple,
                aData, *m_xManager, NULL);
            if (eErrCode != store_E_None)
                return eErrCode;
        }
    }

    // Set (extended or truncated) size.
    aPage.dataLength (nSize);

    // Save modified inode.
    return m_xManager->save (aPage);
}

/*
 * stat.
 */
storeError OStoreLockBytes::stat (sal_uInt32 &rnSize)
{
    rnSize = 0;

    if (!m_xManager.is())
        return store_E_InvalidAccess;

    rnSize = m_pNode->m_aDataBlock.m_nDataLen;
    return store_E_None;
}

/*
 * lockRange.
 */
storeError OStoreLockBytes::lockRange (sal_uInt32, sal_uInt32)
{
    // (NYI).
    return store_E_None;
}

/*
 * unlockRange.
 */
storeError OStoreLockBytes::unlockRange (sal_uInt32, sal_uInt32)
{
    // (NYI).
    return store_E_None;
}

