/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: archive.cpp,v 1.1.2.1 2004/07/09 02:02:36 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "hxtypes.h"
#include "hxresult.h"

#include <stdio.h>
#include <io.h>
#include <sys/stat.h>

#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#else
#include <sys/types.h>
#endif

#include "archive.h"


Archiver::Archiver() : 
    m_fTarFile(NULL),
    m_pReadBuf(NULL)
{
}

Archiver::~Archiver()
{
    HX_VECTOR_DELETE(m_pReadBuf);
}

HX_RESULT
Archiver::Init(FILE* fTarFile)
{
    m_fTarFile = fTarFile;

    m_pReadBuf = new BYTE[READ_CHUNK_SIZE];
    if(!m_pReadBuf)
    {
        return HXR_OUTOFMEMORY;
    }

    return WriteArchiveHeader();
}

HX_RESULT
Archiver::AddFile(const char* szFileName)
{
    HX_RESULT res = HXR_OK;
    struct stat fileInfo;

    if(stat(szFileName, &fileInfo) != 0)
    {
        return HXR_FILE_NOT_FOUND;
    }

    // Write the header
    if(strcmp(szFileName, ".") != 0 && strcmp(szFileName, "..") != 0)
    {
        res = WriteFileHeader(szFileName, &fileInfo);
    }
    if(FAILED(res))
    {
        return res;
    }

    if(fileInfo.st_mode & _S_IFDIR)
    {
        res = RecurseDirectory(szFileName);
    }
    else
    {
        res = WriteFileData(szFileName, &fileInfo);
    }

    return res;
}

HX_RESULT
Archiver::WriteArchiveHeader()
{
    size_t unPos;

    // ID (4 bytes)
    // Major Version (1)
    // Minor Version (1)
    memcpy(m_pReadBuf, HXAR_ID, HXAR_ID_SIZE);
    unPos = HXAR_ID_SIZE;
    m_pReadBuf[unPos++] = HXAR_VER_MAJOR;
    m_pReadBuf[unPos++] = HXAR_VER_MINOR;

    if(fwrite((void*)m_pReadBuf, 1, HXAR_ARCH_DESC_SIZE, m_fTarFile) != 
        HXAR_ARCH_DESC_SIZE)
    {
        return HXR_WRITE_ERROR;
    }
    
    return HXR_OK;
}

HX_RESULT 
Archiver::WriteFileHeader(const char* szFileName, struct stat* pFileInfo)
{
    size_t unPos = 0;
    size_t unLen = strlen(szFileName) + 1;

    // ID (4 bytes)    
    // File Mode (2)
    // File Size (8)    
    // File Name + '\0' (variable)

    memcpy(m_pReadBuf, HXAR_FILE_ID, HXAR_FILE_ID_SIZE);
    unPos = HXAR_FILE_ID_SIZE;

    // mode
    m_pReadBuf[unPos++] = (BYTE)(pFileInfo->st_mode >> 8);
    m_pReadBuf[unPos++] = (BYTE)(pFileInfo->st_mode);

    // File size - upper 32 bits are 0
    memset(m_pReadBuf + unPos, 0, 4);
    unPos += 4;

    // If it's a directory, set size to 0
    if(pFileInfo->st_mode & _S_IFDIR)
    {
        memset(m_pReadBuf + unPos, 0, 4);
        unPos += 4;
    }
    // Otherwise, write file size
    else
    {        
        m_pReadBuf[unPos++] = (BYTE)(pFileInfo->st_size >> 24);
        m_pReadBuf[unPos++] = (BYTE)(pFileInfo->st_size >> 16);
        m_pReadBuf[unPos++] = (BYTE)(pFileInfo->st_size >> 8);
        m_pReadBuf[unPos++] = (BYTE)(pFileInfo->st_size);
    }

    if((fwrite((void*)m_pReadBuf, 1, unPos, m_fTarFile) != unPos) ||
        (fwrite((void*)szFileName, 1, unLen, m_fTarFile) != unLen))
    {
        return HXR_WRITE_ERROR;
    }

    return HXR_OK;
}

HX_RESULT
Archiver::WriteFileData(const char* szFileName, struct stat* pFileInfo)
{
    HX_RESULT res = HXR_OK;
    size_t unReadSize = READ_CHUNK_SIZE;
    size_t unRead = 0;
    size_t unWritten = 0;
    size_t unTotal;
    FILE* fInFile = fopen(szFileName, "rb");

    if(!fInFile)
    {
        return HXR_FILE_NOT_FOUND;
    }

    // Copy the input file contents to the tar file
    for(unTotal = 0; unTotal < (size_t)pFileInfo->st_size && SUCCEEDED(res);
        unTotal += unRead)
    {
        if(unTotal + unReadSize > (size_t)pFileInfo->st_size)
        {
            unReadSize = pFileInfo->st_size - unTotal;
        }

        // Read a chunk
        unRead = fread((void*)m_pReadBuf, 1, unReadSize, fInFile);
        if(unRead != unReadSize)
        {
            res = HXR_INVALID_FILE;
        }
        else
        {
            // Write a chunk
            unWritten = fwrite((void*)m_pReadBuf, 1, unRead, m_fTarFile);
            if(unWritten != unRead)
            {
                res = HXR_WRITE_ERROR;
            }
        }
    }

    if(fInFile)
    {
        fclose(fInFile);
    }

    return res;
}

Dearchiver::Dearchiver() : 
    m_szOutDir(NULL),
    m_State(STATE_CLOSED),
    m_pReadBuf(NULL),
    m_ulSaved(0),
    m_pSaveBuf(NULL),
    m_fFile(NULL),
    m_ulFileSize(0),
    m_ulMode(0),
    m_ulWritten(0)
{
}

Dearchiver::~Dearchiver()
{
    Close();

    HX_VECTOR_DELETE(m_pReadBuf);
    HX_VECTOR_DELETE(m_pSaveBuf);
}

HX_RESULT
Dearchiver::Init(const char* szOutDir)
{
    struct stat fileInfo;

    if(szOutDir)
    {
        if(stat(szOutDir, &fileInfo) == 0)
        {
            if(!(fileInfo.st_mode & _S_IFDIR))
            {
                return HXR_WRITE_ERROR;
            }
        }
        else if(mkdir(szOutDir) != 0)
        {
            return HXR_WRITE_ERROR;
        }
    }

    if(!m_pReadBuf)
    {
        m_pReadBuf = new BYTE[READ_CHUNK_SIZE];
        if(!m_pReadBuf)
        {
            return HXR_OUTOFMEMORY;
        }
    }

    if(!m_pSaveBuf)
    {
        m_pSaveBuf = new BYTE[DESC_BUF_SIZE];
        if(!m_pSaveBuf)
        {
            return HXR_OUTOFMEMORY;
        }
    }

    m_szOutDir = szOutDir;
    m_State = STATE_ARCH_HEADER;

    return HXR_OK;
}

HX_RESULT
Dearchiver::Extract(FILE* fTarFile)
{
    HX_RESULT res = HXR_OK;
    size_t unReadSize = READ_CHUNK_SIZE;
    size_t unRead = 0;
    UINT32 ulExtracted = 0;

    // untar the file
    while(!feof(fTarFile) && SUCCEEDED(res))
    {
        // Read a chunk
        unRead = fread((void*)m_pReadBuf, 1, unReadSize, fTarFile);
        if(ferror(fTarFile))
        {
            res = HXR_INVALID_FILE;
        }
        else
        {
            // Write a chunk
            res = Extract(m_pReadBuf, unRead, ulExtracted);
        }
    }

    return res;
}

HX_RESULT
Dearchiver::Extract(BYTE* pArchData, const UINT32 ulSize, UINT32& ulRead)
{
    HX_RESULT res = HXR_OK;
    UINT32 ulLocalRead = 0;
    ulRead = 0;

    while(ulRead < ulSize)
    {
        switch (m_State)
        {
            case(STATE_ARCH_HEADER):
            {
                res = ReadTarHeader(pArchData + ulRead, ulSize - ulRead, 
                    ulLocalRead);
            }
            break;
            case(STATE_FILE_HEADER):
            {
                res = ReadFileHeader(pArchData + ulRead, ulSize - ulRead, 
                    ulLocalRead);
            }
            break;
            case(STATE_FILE_NAME):
            {
                res = ReadFileName(pArchData + ulRead, ulSize - ulRead, 
                    ulLocalRead);
            }
            break;
            case(STATE_FILE_DATA):
            {
                res = ReadFileData(pArchData + ulRead, ulSize - ulRead, 
                    ulLocalRead);
            }
        }
        if(FAILED(res))
        {
            break;
        }        

        ulRead += ulLocalRead;
    }

    return res;
}

HX_RESULT
Dearchiver::ReadTarHeader(BYTE* pArchData, const UINT32 ulSize, UINT32& ulRead)
{
    HX_RESULT res = HXR_OK;
    BOOL bNeedMoreData = FALSE;
    UINT32 ulPos = 0;
    BYTE* pBuf = pArchData;
    UINT32 ulCopySize = 0;

    // We have a partial header, save what we've got
    if(ulSize + m_ulSaved < HXAR_ARCH_DESC_SIZE)
    {
        bNeedMoreData = TRUE;
        ulCopySize = ulSize;
    }
    // We have a partial header stored, copy the rest of it
    else if (m_ulSaved)
    {
        ulCopySize = HXAR_ARCH_DESC_SIZE - m_ulSaved;
    }

    // copy
    if(ulCopySize)
    {
        // Should never happen!
        if(m_ulSaved + ulCopySize > DESC_BUF_SIZE)
        {
            return HXR_FAIL;
        }
        pBuf = m_pSaveBuf;

        memcpy((void*)(m_pSaveBuf + m_ulSaved), pArchData, ulCopySize);
        m_ulSaved += ulCopySize;
    }

    // Read the tar descriptor
    if(!bNeedMoreData)
    {
        if(strncmp((const char*)pBuf, HXAR_ID, HXAR_ID_SIZE) == 0)
        {
            ulPos = HXAR_ID_SIZE;
            if(pBuf[ulPos] > HXAR_VER_MAJOR || 
                (pBuf[ulPos] == HXAR_VER_MAJOR && 
                pBuf[ulPos + 1] > HXAR_VER_MINOR))
            {
                res = HXR_INVALID_VERSION;
            }
            ulPos += 2;
            m_State = STATE_FILE_HEADER;
        }
        else
        {
            res = HXR_INVALID_FILE;
        }

        // Clear the save buffer
        m_ulSaved = 0;
    }

    if(SUCCEEDED(res))
    {
        ulRead = ulCopySize ? ulCopySize : ulPos;
    }

    return res;
}

HX_RESULT
Dearchiver::ReadFileHeader(BYTE* pArchData, const UINT32 ulSize,
                            UINT32& ulRead)
{
    HX_RESULT res = HXR_OK;
    BOOL bNeedMoreData = FALSE;
    UINT32 ulPos = 0;
    BYTE* pBuf = pArchData;
    UINT32 ulCopySize = 0;

    // We have a partial header, save what we've got
    if(ulSize + m_ulSaved < HXAR_FILE_DESC_SIZE)
    {
        bNeedMoreData = TRUE;
        ulCopySize = ulSize;
    }
    // We have a partial header stored, copy the rest of it
    else if (m_ulSaved)
    {
        ulCopySize = HXAR_FILE_DESC_SIZE - m_ulSaved;
    }

    // copy
    if(ulCopySize)
    {
        // Should never happen!
        if(m_ulSaved + ulCopySize > DESC_BUF_SIZE)
        {
            return HXR_FAIL;
        }
        pBuf = m_pSaveBuf;

        memcpy((void*)(m_pSaveBuf + m_ulSaved), pArchData, ulCopySize);
        m_ulSaved += ulCopySize;
    }

    // Read the file descriptor
    if(!bNeedMoreData)
    {        
        if(strncmp((const char*)pBuf, HXAR_FILE_ID, HXAR_FILE_ID_SIZE) == 0)
        {
            ulPos = HXAR_FILE_ID_SIZE;
            m_ulMode = (pBuf[ulPos] << 8) + pBuf[ulPos+1];
            ulPos += 6;
            m_ulFileSize = (pBuf[ulPos] << 24) + (pBuf[ulPos+1] << 16) + 
                            (pBuf[ulPos+2] << 8) + pBuf[ulPos+3];
            ulPos += 4;

            m_State = STATE_FILE_NAME;
        }
        else
        {
            res = HXR_INVALID_FILE;
        }

        // Clear the save buffer
        m_ulSaved = 0;
    }

    if(SUCCEEDED(res))
    {
        ulRead = ulCopySize ? ulCopySize : ulPos;
    }

    return res;
}

HX_RESULT
Dearchiver::ReadFileName(BYTE* pArchData, const UINT32 ulSize, UINT32& ulRead)
{
    HX_RESULT res = HXR_OK;
    BOOL bNeedMoreData = FALSE;
    //UINT32 ulPos = 0;
    BYTE* pBuf = pArchData;    
    UINT32 ulCopySize = 0;
    
    // Find the null terminator signaling end of file name
    BYTE* pEnd = (BYTE*)memchr((void*)pArchData, '\0', ulSize);

    // We have a partial header, save what we've got
    if(!pEnd)
    {
        bNeedMoreData = TRUE;
        ulCopySize = ulSize;
    }

    // We have a partial header stored, copy the rest of it
    else if(m_ulSaved)
    {
        ulCopySize = pEnd - pArchData + 1;
    }

    // copy
    if(ulCopySize)
    {
        // File name is too long
        if(m_ulSaved + ulCopySize > DESC_BUF_SIZE)
        {
            return HXR_INVALID_FILE;
        }

        pBuf = m_pSaveBuf;

        memcpy((void*)(m_pSaveBuf + m_ulSaved), pArchData, ulCopySize);
        m_ulSaved += ulCopySize;
    }
    
    if(!bNeedMoreData)
    {
        res = OpenOutFile((const char*)pBuf);

        // Clear the save buffer
        m_ulSaved = 0;
    }

    if(SUCCEEDED(res))
    {
        ulRead = ulCopySize ? ulCopySize : pEnd - pArchData + 1;
    }

    return res;
}

HX_RESULT
Dearchiver::ReadFileData(BYTE* pArchData, const UINT32 ulSize, UINT32& ulRead)
{
    if(!m_fFile)
    {
        return HXR_FAIL;
    }

    HX_RESULT res = HXR_OK;
    UINT32 ulWriteSize = ulSize;

    ulRead = 0;

    if(m_ulWritten + ulSize > m_ulFileSize)
    {
        ulWriteSize = m_ulFileSize - m_ulWritten;
    }

    // Write the block
    if(fwrite((void*)pArchData, 1, ulWriteSize, m_fFile) != ulWriteSize)
    {
        res = HXR_WRITE_ERROR;
    }

    if(SUCCEEDED(res))
    {
        ulRead = ulWriteSize;
        m_ulWritten += ulWriteSize;
        if(m_ulWritten >= m_ulFileSize)
        {
            m_State = STATE_FILE_HEADER;
            CloseOutFile();
        }
    }

    return res;
}

HX_RESULT
Dearchiver::OpenOutFile(const char* szFileName)
{
    const char* pPath;
    char szFullPath[MAX_PATH + 1];
    struct stat fileInfo;

    // Add the output directory for the full file name
    if(m_szOutDir)
    {
        snprintf(szFullPath, MAX_PATH, "%s%c%s", m_szOutDir, 
            OS_SEPARATOR_CHAR, szFileName);
        pPath = szFullPath;
    }
    else
    {
        pPath = szFileName;
    }

    // Create the directory
    if(m_ulMode & _S_IFDIR)
    {
        if(stat(pPath, &fileInfo) == 0)
        {
            if(!(fileInfo.st_mode & _S_IFDIR))
            {
                return HXR_WRITE_ERROR;
            }
        }
        else if(mkdir(pPath) != 0)
        {
            return HXR_WRITE_ERROR;
        }

        m_State = STATE_FILE_HEADER;

        m_fFile = NULL;            
    }

    // Open the file
    else
    {
        m_fFile = fopen(pPath, "wb");
        if(!m_fFile)
        {
            return HXR_WRITE_ERROR;
        }
        else
        {
            m_State = STATE_FILE_DATA;
        }
    }

    return HXR_OK;
}

void
Dearchiver::CloseOutFile()
{
    m_ulFileSize = 0;
    m_ulWritten = 0;
    m_ulSaved = 0;
    m_State = STATE_FILE_HEADER;

    if(m_fFile)
    {
        fclose(m_fFile);
    }
}

void
Dearchiver::Close()
{
    CloseOutFile();
    m_State = STATE_CLOSED;
    m_szOutDir = NULL;
}
