/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla Communicator client code, released
 * March 31, 1998.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Samir Gehani <sgehani@netscape.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. 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 MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsZipExtractor.h"

#define STANDALONE 1
#include "zipstub.h"
#include "zipfile.h"

nsZipExtractor::nsZipExtractor(char *aSrc, char *aDest) :
    mSrc(NULL),
    mDest(NULL)
{
    if (aSrc)
        mSrc = strdup(aSrc);
    if (aDest)
        mDest = strdup(aDest);
}

nsZipExtractor::~nsZipExtractor()
{
    XI_IF_FREE(mSrc);
    XI_IF_FREE(mDest);
}

int
nsZipExtractor::Extract(nsComponent *aXPIEngine, int aTotal)
{
    DUMP("Extract");

    char apath[MAXPATHLEN]; /* archive path */
    char bindir[512];
    char zpath[MAXPATHLEN]; /* path of file in zip archive */
    char epath[MAXPATHLEN]; /* path of file after being extracted */
    char *leaf = NULL, *lslash = NULL;
    struct stat dummy;
    int i, bFoundAll = FALSE, err = OK;
    PRInt32 zerr = ZIP_OK;
    PRUint32 len;
    void *hZip = NULL, *hFind = NULL;

    if (!aXPIEngine || !(aXPIEngine->GetArchive()))
        return E_PARAM;

    len=snprintf(apath,sizeof(apath),"%s/%s", mSrc, aXPIEngine->GetArchive());
    if ( len >= sizeof(apath) )
        return E_PARAM; /* this should be something like E_PATH_TOO_LONG */

    if (-1 == stat(apath, &dummy))
        return E_NO_DOWNLOAD;

    /* initialize archive etc.
     */
    zerr = ZIP_OpenArchive(apath, &hZip);
    if (zerr != ZIP_OK) return E_EXTRACTION;
    hFind = ZIP_FindInit(hZip, (const char *) NULL);
    if (!hFind)
    {
        err = E_EXTRACTION;
        goto au_revoir;
    }

    /* extract files
     */
    i = 0;
    while (!bFoundAll)
    {
        memset(zpath, 0, MAXPATHLEN);
        zerr = ZIP_FindNext(hFind, zpath, MAXPATHLEN);
        if (zerr == ZIP_ERR_FNF)
        {
            bFoundAll = true;
            break;
        }
        if (zerr != ZIP_OK)
        {
            err = E_EXTRACTION;
            goto au_revoir;
        }

        /* directory encountered: ignore entry
         */
        lslash = strrchr(zpath, '/');
        if (lslash == (zpath + strlen(zpath) - 1))
            continue;

        if (!lslash)
            leaf = zpath;
        else
            leaf = lslash + 1;
        if (!leaf)
            continue;

        /* update UI
         */
        if (gCtx->opt->mMode != nsXIOptions::MODE_SILENT)
            nsInstallDlg::MajorProgressCB(leaf, i,
                aTotal, nsInstallDlg::ACT_EXTRACT);

        len=snprintf(epath,sizeof(epath),"%s/%s", mDest, zpath);
        if ( len >= sizeof(apath) )
        {
            err = E_PARAM; /* this should be something like E_PATH_TOO_LONG */
            goto au_revoir;
        }
        err = DirCreateRecursive(epath);
        if (err != OK) goto au_revoir;

        zerr = ZIP_ExtractFile(hZip, zpath, epath);
        if (zerr != ZIP_OK)
        {
            err = E_EXTRACTION;
            goto au_revoir;
        }

        i++;
    }

    len=snprintf(bindir,sizeof(bindir),"%s/%s", mDest, TMP_EXTRACT_SUBDIR);
    if ( len >= sizeof(bindir) )
    {
        err = E_PARAM; /* this should be something like E_PATH_TOO_LONG */
        goto au_revoir;
    }

    if (-1 == stat(bindir, &dummy))
        err = E_EXTRACTION;

au_revoir:
    /* close archive etc.
     */
    if (hFind)
        ZIP_FindFree(hFind);
    if (hZip)
        ZIP_CloseArchive(&hZip);
    return err;
}

int
nsZipExtractor::DirCreateRecursive(char *aPath)
{
    PRUint32 len;
    int err = OK;
    char *slash = NULL;
    char currdir[MAXPATHLEN];
    struct stat dummy;

    if (!aPath || !mDest)
        return E_PARAM;

    slash = aPath + strlen(mDest);
    if (*slash != '/')
        return E_INVALID_PTR;

    while (slash)
    {
        len = slash - aPath;
        if (len >= sizeof(currdir)) return E_PARAM; // should not happen, just being defensive

        snprintf(currdir,len+1,"%s",aPath);

        if (-1 == stat(currdir, &dummy))
        {
            if (-1 == mkdir(currdir, 0755))
                return E_MKDIR_FAIL;
        }

        slash = strchr(slash+1, '/');
    }

    return err;
}
