/*  ZIP plugin for Tux Commander
 *   version 0.5.6, designed for ZipArchive v3.2.0
 *  Copyright (C) 2008 Tomas Bzatek <tbzatek@users.sourceforge.net>
 *   Check for updates on tuxcmd.sourceforge.net
 *
 *  Uses ZipArchive library
 *   Copyright (C) 2000 - 2007 Artpol Software - Tadeusz Dracz
 *   http://www.artpol-software.com/ZipArchive/
 * 


 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <dirent.h>
#include <fnmatch.h>
#include <unistd.h>
#include <glib.h>

#include "vfs_types.h"
#include "strutils.h"
#include "vfsutils.h"
#include "treepathutils.h"
#include "treepath_vfs.h"

#include "ZipArchive.h"
#include "ZipPlatform.h"
#include "ZipCallback.h"



#define VERSION "0.5.6"
#define BUILD_DATE "2009-10-25"
#define DEFAULT_BLOCK_SIZE 65536


using namespace std;
extern "C" {


/******************************************************************************************************/
/**  Utilities                                                                                        */
/**************                                                                        ****************/                  

TVFSResult get_vfs_errorcode(int m_iCause)
{
  switch (m_iCause) {
    case 13: return cVFS_WriteErr;                              //  Permission denied
    case CZipException::noError: return cVFS_WriteErr;          //  No error.
    case CZipException::genericError: return cVFS_WriteErr;     //  An unknown error.
    case CZipException::badZipFile: return cVFS_ReadErr;        //  Damaged or not a zip file.
    case CZipException::badCrc: return cVFS_ReadErr;            //  Crc is mismatched.
    case CZipException::noCallback: return cVFS_Failed;         //  There is no spanned archive callback object set.
    case CZipException::aborted: return cVFS_Failed;            //  The disk change callback method returned false.
    case CZipException::abortedAction: return cVFS_Failed;      //  The action callback method returned false.
    case CZipException::abortedSafely: return cVFS_Failed;      //  The action callback method returned false, but the data is not corrupted.
    case CZipException::nonRemovable: return cVFS_WriteErr;     //  The device selected for the spanned archive is not removable.
    case CZipException::tooManyVolumes: return cVFS_WriteErr;   //  The limit of the maximum volumes reached.
    case CZipException::tooManyFiles: return cVFS_ReadErr;      //  The limit of the maximum files in an archive reached.
    case CZipException::tooLongData: return cVFS_ReadErr;       //  The filename, the comment or the local or central extra field of the file added to the archive is too long.
    case CZipException::tooBigSize: return cVFS_ReadErr;        //  The file size is too large to be supported.
    case CZipException::badPassword: return cVFS_ReadErr;       //  An incorrect password set for the file being decrypted.
    case CZipException::dirWithSize: return cVFS_ReadErr;       //  The directory with a non-zero size found while testing.
    case CZipException::internalError: return cVFS_WriteErr;    //  An internal error.
    case CZipException::notRemoved: return cVFS_WriteErr;       //  Error while removing a file
    case CZipException::notRenamed: return cVFS_WriteErr;       //  Error while renaming a file (under Windows call GetLastError() to find out more).
    case CZipException::platfNotSupp: return cVFS_WriteErr;     //  Cannot create a file for the specified platform.
    case CZipException::cdirNotFound: return cVFS_ReadErr;      //  The central directory was not found in the archive (or you were trying to open not the last disk of a segmented archive).
//    case CZipException::cdir64NotFound: return cVFS_ReadErr;    //  The Zip64 central directory signature was not found in the archive where expected.
//    case CZipException::noBBZInZip64: return cVFS_ReadErr;      //  The number of bytes before a zip archive must be zero in the Zip64 format.
//    case CZipException::badAesAuthCode: return cVFS_ReadErr;    //  Mismatched authentication code in WinZip AEC decrypted data.
    case CZipException::noZip64: return cVFS_ReadErr;           //  The Zip64 format has not been enabled for the library, but is required to open the archive.
    case CZipException::noAES: return cVFS_ReadErr;             //  WinZip AES encryption has not been enabled for the library, but is required to decompress the archive.
    case CZipException::outOfBounds: return cVFS_ReadErr;       //  The collection is empty and the bounds do not exist.
//    case CZipException::mutexError: return cVFS_ReadErr;        //  Locking or unlocking resources access was unsuccessful.
    case CZipException::streamEnd: return cVFS_ReadErr;         //  Zlib library error.
    case CZipException::needDict: return cVFS_ReadErr;          //  Zlib library error.
    case CZipException::errNo: return cVFS_ReadErr;             //  Zlib library error.
    case CZipException::streamError: return cVFS_ReadErr;       //  Zlib library error.
    case CZipException::dataError: return cVFS_ReadErr;         //  Zlib library error.
    case CZipException::memError: return cVFS_ReadErr;          //  Zlib library or CZipMemFile error.
    case CZipException::bufError: return cVFS_ReadErr;          //  Zlib library error.
    case CZipException::versionError: return cVFS_ReadErr;      //  Zlib library error.
//    case CZipException::bzSequenceError: return cVFS_ReadErr;   //  Bzlib library error.
//    case CZipException::bzParamError: return cVFS_ReadErr;      //  Bzlib library error.
//    case CZipException::bzMemError: return cVFS_ReadErr;        //  Bzlib library error.
//    case CZipException::bzDataError: return cVFS_ReadErr;       //  Bzlib library error.
//    case CZipException::bzDataErrorMagic: return cVFS_ReadErr;  //  Bzlib library error.
//    case CZipException::bzIoError: return cVFS_ReadErr;         //  Bzlib library error.
//    case CZipException::bzUnexpectedEof: return cVFS_ReadErr;   //  Bzlib library error.
//    case CZipException::bzOutbuffFull: return cVFS_ReadErr;     //  Bzlib library error.
//    case CZipException::bzConfigError: return cVFS_ReadErr;     //  Bzlib library error.
//    case CZipException::bzInternalError: return cVFS_ReadErr;   //  Internal Bzlib library error.    	
    default: return cVFS_WriteErr;
  }    
  return cVFS_Failed;  //  Default error
}



/******************************************************************************************************/
/**  Auxiliary classes                                                                                */
/**************                                                                        ****************/                  

struct ZIP_API CVFSZipActionCallback;

struct TVFSGlobs {
  TVFSLogFunc log_func;
  char *curr_dir;
  char *archive_path;

  gboolean need_password;

  CZipArchive *zip;
  CVFSZipActionCallback *extract_callback;

  bool archive_opened;
  unsigned long block_size;
  bool archive_modified;

  struct PathTree *files;
  struct VfsFilelistData *vfs_filelist;

  TVFSAskQuestionCallback callback_ask_question;
  TVFSAskPasswordCallback callback_ask_password;
  TVFSProgressCallback callback_progress;
  void *callback_data;
};

//  Define the progress class and the class methods
struct ZIP_API CVFSZipActionCallback : public CZipActionCallback
{
        CVFSZipActionCallback()
        {
                m_uTotalToProcess = 0;
                m_uProcessed = 0;
                globs = NULL;
        }

        struct TVFSGlobs *globs;

        virtual bool Callback(ZIP_SIZE_TYPE uProgress)
        {
          fprintf(stderr, "(II) Callback called, position = %lu; m_uTotalToProcess = %lu; m_uProcessed = %lu\n", 
                          uProgress, m_uTotalToProcess, m_uProcessed);
          bool ret = true;
          try {
            if (globs && globs->callback_progress)
              ret = globs->callback_progress (m_uProcessed, m_uTotalToProcess, globs->callback_data);
          }
          catch (...) {
            fprintf(stderr, "(EE) extract_callback: Fatal error occured when calling pCallBackProgress\n");
          }
          return ret;
        }
};




/***********************************************************************************************************************
*  Internal tree functions
********/

void build_global_filelist(struct TVFSGlobs *globs)
{
  int iCount = globs->zip->GetCount();
  //  Ensure the filelist is freed
  if (globs->files) filelist_tree_free(globs->files); 
	  
  globs->files = filelist_tree_new();
  vfs_filelist_set_files(globs->vfs_filelist, globs->files);

  //  list files in archive
  for (int i = 0; i < iCount; i++) {
    CZipFileHeader *fh = globs->zip->GetFileInfo(i);
    if (fh != NULL)  
      printf("  No: %i, '%s', IsDir: %i, Size: %lu, SystemAttr = 0x%lX, OriginalAttr = 0x%lX, encrypted = %d\n", 
    	     i, (LPCTSTR)fh->GetFileName(), fh->IsDirectory(), fh->m_uUncomprSize, fh->GetSystemAttr(), fh->GetOriginalAttributes(), fh->IsEncrypted());
  }    
  printf("\n\n");
    
  for (int i = 0; i < iCount; i++) {
    CZipFileHeader *fh = globs->zip->GetFileInfo(i);
    if (fh != NULL) { 
	  //  Create a TVFSItem entry and fill all info
	  struct TVFSItem *item = (struct TVFSItem*)malloc(sizeof(struct TVFSItem));
	  memset(item, 0, sizeof(struct TVFSItem));

	  item->iSize = (int64_t)fh->m_uUncomprSize;
          item->iPackedSize = (int64_t)fh->m_uComprSize;
	  if (fh->IsDirectory())
		  item->ItemType = vDirectory;
	  	  else item->ItemType = vRegular;
	  item->iMode = fh->GetSystemAttr();
	  item->iUID = geteuid();
	  item->iGID = getegid();
	  item->m_time = (__time_t)fh->GetTime();
	  item->c_time = item->m_time;
	  item->a_time = item->m_time;
	  
	  if (fh->IsEncrypted())  globs->need_password = TRUE;
	  
	  char *s;
	  
	  s = g_filename_display_name ((LPCTSTR)fh->GetFileName());
	  
	  //  Add item to the global list and continue with next file	  
	  filelist_tree_add_item(globs->files, s, s, item, i + 1);
	  g_free (s);
	  printf("\n");
    }
  }

  if (globs->need_password) printf("Password present.\n");

  printf("\n\n\n\nPrinting the contents of the global filelist:\n\n");
  filelist_tree_print(globs->files);
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//  Basic initialization functions

struct TVFSGlobs *
VFSNew (TVFSLogFunc log_func)
{
  struct TVFSGlobs * globs;

  globs = (struct TVFSGlobs *) malloc (sizeof (struct TVFSGlobs));
  memset (globs, 0, sizeof (struct TVFSGlobs));

  globs->archive_opened = false;
  globs->block_size = DEFAULT_BLOCK_SIZE;
  globs->archive_modified = false;
  globs->need_password = FALSE;

  globs->callback_data = NULL;
  globs->callback_ask_question = NULL;
  globs->callback_ask_password = NULL;
  globs->callback_progress = NULL;

  globs->log_func = log_func;
  if (globs->log_func != NULL) globs->log_func("zip plugin: VFSInit");

  return globs;
}

void
VFSSetCallbacks (struct TVFSGlobs *globs,
                 TVFSAskQuestionCallback ask_question_callback,
                 TVFSAskPasswordCallback ask_password_callback,
                 TVFSProgressCallback progress_func,
                 void *data)
{
  globs->callback_ask_question = ask_question_callback;
  globs->callback_ask_password = ask_password_callback;
  globs->callback_progress = progress_func;
  globs->callback_data = data;
}

void
VFSFree (struct TVFSGlobs *globs)
{
  if (globs->log_func != NULL) globs->log_func("zip plugin: VFSDestroy");
  free (globs);
}

int VFSVersion()
{
  return cVFSVersion;
}

struct TVFSInfo *
VFSGetInfo()
{
  struct TVFSInfo *module_info = (TVFSInfo*) g_malloc0 (sizeof (struct TVFSInfo));

  module_info->ID = g_strdup ("zip_plugin");
  module_info->Name = g_strdup ("ZIP plugin");
  module_info->About = g_strdup_printf ("version %s, build date: %s\nusing ZipArchive library v%s\n", VERSION, BUILD_DATE, CZipArchive::m_gszVersion);
  module_info->Copyright = g_strdup_printf ("Plugin Copyright (C) 2004-2009 Tomáš Bžatek\n%s", CZipArchive::m_gszCopyright);

  return module_info;
}

char *
VFSGetArchiveExts()
{
  return g_strdup ("zip");
}


/**************************************************************************************************************************************/
/**************************************************************************************************************************************/

TVFSResult VFSOpenArchive(struct TVFSGlobs *globs, char *sName)
{
  //  Initialize the objects
  globs->files = NULL;
  globs->vfs_filelist = vfs_filelist_new(NULL);
  
  globs->curr_dir = NULL;
  globs->zip = new CZipArchive;

  try
  {
    fprintf(stderr, "(--) VFSOpenArchive: trying to open the file...\n");

    try {
      if (! globs->zip->Open(sName, CZipArchive::zipOpen, 0)) {
          printf("(EE) VFSOpenArchive: error opening zip archive\n");
          return cVFS_Failed;
      }
    }
    catch (...) {
      printf("(!!) VFSOpenArchive: error opening readwrite zip, trying readonly...\n");
      try {
        // try to open in read only mode (required if there's no write access on the media)
        if (! globs->zip->Open(sName, CZipArchive::zipOpenReadOnly, 0)) {
            printf("(EE) VFSOpenArchive: error opening readonly zip archive\n");
            return cVFS_Failed;
        }
      }
      catch (...) {
        printf("(EE) VFSOpenArchive: error opening readonly zip\n");
        return cVFS_Failed;
      }
    }
    int iCount = globs->zip->GetCount(false);
    printf("(II) VFSOpenArchive: %i records found, %i files.\n", iCount, globs->zip->GetCount(true));
    if (iCount < 1) return cVFS_Failed;

    //  Build the global file list
    build_global_filelist(globs);

    //  Set the progress callback
    globs->extract_callback = new CVFSZipActionCallback;
    globs->extract_callback->globs = globs;
    globs->zip->SetCallback(globs->extract_callback, CZipActionCallback::cbExtract);
    globs->zip->SetCallback(globs->extract_callback, CZipActionCallback::cbAdd);

    //  Set automatic flushing of changes to disk
    globs->zip->SetAutoFlush(true);
  }
  catch (CZipException e) {
    printf ("(EE) VFSOpenArchive: Error while processing archive %s\n%s\n", (LPCTSTR) sName, (LPCTSTR)e.GetErrorDescription());
    if (e.m_szFileName.IsEmpty()) printf("\n");
    else printf("(EE) VFSOpenArchive: Filename in error object: %s\n\n", (LPCTSTR)e.m_szFileName);
    globs->zip->Close(true);
    return cVFS_Failed;
  }
  catch (...) {
    printf ("(EE) VFSOpenArchive: Unknown error while processing archive %s\n\n", (LPCTSTR) sName);
    globs->zip->Close(true);
    return cVFS_Failed;
  }

  globs->archive_path = strdup(sName);
  globs->archive_modified = false;
  return cVFS_OK;
}


TVFSResult VFSClose(struct TVFSGlobs *globs)
{
  if (globs) {
	  //  Closing the archive...
	  fprintf(stderr, "(II) VFSClose: Closing the archive...\n");
	  try {
		if (globs->archive_modified) globs->zip->Flush();
	    globs->zip->Close(CZipArchive::afNoException, false);
	    //*** In case of inconsistency, try using afWriteDir value
	    //    (Use when an exception was thrown. The Close method writes the 
	    //     central directory structure to the archive, so that the archive should be usable.)
	  }
	  catch (CZipException e) {
	    fprintf(stderr, "(EE) VFSClose: Error while closing archive: %s\n", (LPCTSTR)e.GetErrorDescription());
	    return cVFS_Failed;
	  }


	  //  Freeing the ZIP objects...
	  fprintf(stderr, "(II) VFSClose: Freeing ZipArchive objects...\n");
	  try {
	    delete globs->extract_callback;
	    delete globs->zip;
	  }
	  catch (...)  {
	    fprintf(stderr, "(EE) VFSClose: Error freeing ZipArchive objects\n");
	    return cVFS_Failed;
	  }

	  //  Freeing the filelist
	  fprintf(stderr, "(II) VFSClose: Freeing filelist...\n");
	  if (globs->vfs_filelist) vfs_filelist_free(globs->vfs_filelist);
	  if (globs->files) filelist_tree_free(globs->files);

	  //  Free the rest...
	  free(globs->archive_path);
  }
  return cVFS_OK;
}

char *
VFSGetPath (struct TVFSGlobs *globs)
{
  return g_strdup (globs->curr_dir);
}

u_int64_t VFSGetFileSystemFree(struct TVFSGlobs *globs, char *APath)
{
  return 0;
}

u_int64_t VFSGetFileSystemSize(struct TVFSGlobs *globs, char *APath)
{
  return globs->zip->GetOccupiedSpace();
}




/******************************************************************************************************/

TVFSResult VFSChangeDir(struct TVFSGlobs *globs, char *NewPath)
{
  if (NewPath == NULL) {
    printf("(EE) VFSChangeDir: NewPath is NULL!\n");
    return cVFS_Failed;
  }
  
  globs->curr_dir = vfs_filelist_change_dir(globs->vfs_filelist, NewPath);
  if (globs->curr_dir) return cVFS_OK;
  else return cVFS_Failed;
}

/*
int VFSSetPassword(struct TVFSGlobs *globs, char *pass)
{
  printf ("(II) VFSSetPassword: Going to set the password...\n");
  try {
    globs->zip->SetPassword(pass);
  }
  catch (...)
  {
    printf ("(EE) VFSSetPassword: Changing password failed. Maybe closed archive ?\n");
    return cVFS_Failed;
  }
  return cVFS_OK;
}
*/

int VFSGetPasswordRequired(struct TVFSGlobs *globs)
{
  if (globs)  return globs->need_password;
  return FALSE;
}

void
VFSResetPassword(struct TVFSGlobs *globs)
{
  if (globs)
    globs->zip->SetPassword(NULL);
}


/******************************************************************************************************/

TVFSResult VFSListFirst(struct TVFSGlobs *globs, char *sDir, struct TVFSItem *Item)
{
  if (sDir == NULL) {
    printf("(EE) VFSListFirst: sDir is NULL!\n");
    return cVFS_Failed;
  }
  printf ("(--) VFSListFirst: Going to list all items in '%s'\n", sDir);
  
  return vfs_filelist_list_first(globs->vfs_filelist, sDir, Item);	
}

TVFSResult VFSListNext(struct TVFSGlobs *globs, char *sDir, struct TVFSItem *Item)
{
  return vfs_filelist_list_next(globs->vfs_filelist, sDir, Item);	
}

TVFSResult VFSListClose(struct TVFSGlobs *globs)
{
  return vfs_filelist_list_close(globs->vfs_filelist);	
}


/******************************************************************************************************/
long VFSFileExists(struct TVFSGlobs *globs, const char *FileName, const long Use_lstat)
{
  if (! globs) return FALSE; 
  return vfs_filelist_file_exists(globs->vfs_filelist, FileName, Use_lstat);	
}

TVFSResult VFSFileInfo(struct TVFSGlobs *globs, char *AFileName, struct TVFSItem *Item)
{
  printf("(--) VFSFileInfo: requested info for object '%s'\n", AFileName);
  if (!globs) return cVFS_Failed;
  return vfs_filelist_file_info(globs->vfs_filelist, AFileName, Item);
}

/******************************************************************************************************/
/**  Recursive tree size counting                                                                     */
/**************                                                                        ****************/                  

u_int64_t VFSGetDirSize(struct TVFSGlobs *globs, char *APath)
{
  if (! globs) return 0;
  return vfs_filelist_get_dir_size(globs->vfs_filelist, APath);
}

void VFSBreakGetDirSize(struct TVFSGlobs *globs)
{
  printf("(WW) VFSBreakGetDirSize: calling break\n");
  if (globs) vfs_filelist_get_dir_size_break(globs->vfs_filelist);
}


/******************************************************************************************************/
/**  Methods modifying the archive                                                                    */
/**************                                                                        ****************/                  

TVFSResult VFSMkDir(struct TVFSGlobs *globs, const char *sDirName)
{
  if ((sDirName == NULL) || (strlen(sDirName) < 1)) {
    printf("(EE) VFSMkDir: The value of 'sDirName' is NULL or empty\n");
	return cVFS_Failed;
  }
  if ((strlen(sDirName) < 1) || (strcmp(sDirName, "/") == 0)) {
    printf("(EE) VFSMkDir: Invalid value '%s' (duplicate root entry?)\n", sDirName);
    return cVFS_Failed;
  }
  printf ("(II) VFSMkDir: Going to create new directory '%s'...\n", sDirName);

  try {
	try {  
      CZipFileHeader header;
//      globs->zip->SetFileHeaderAttr(header, 0x41ED0010);  //  alternatively use ZipPlatform::GetDefaultAttributes();
      globs->zip->SetFileHeaderAttr(header, 0x41ED);  //  alternatively use ZipPlatform::GetDefaultAttributes();
      char *s = exclude_leading_path_sep(sDirName);
      header.SetFileName(s);
      free(s);
      header.SetTime(time(NULL));
      bool bRet = globs->zip->OpenNewFile(header, 0, NULL);
      globs->zip->CloseNewFile();
      if (! bRet) {
        printf("(EE) VFSMkDir: Error creating new directory '%s'\n", sDirName);
        return cVFS_Failed;
      } 
      globs->archive_modified = true;	
      build_global_filelist(globs);
      return cVFS_OK;
	}
    catch (CZipException e) {
      globs->zip->CloseNewFile(true);
      fprintf(stderr, "(EE) VFSMkDir: Error creating new directory '%s': [%d] %s, archive closed = %d.\n", 
                      sDirName, e.m_iCause, (LPCTSTR)e.GetErrorDescription(), globs->zip->IsClosed());
      return get_vfs_errorcode(e.m_iCause);
    }
  }
  catch (...)
  {
    printf("(EE) VFSMkDir: Error creating new directory '%s'\n", sDirName);
    return cVFS_Failed;
  }
}

TVFSResult VFSRemove(struct TVFSGlobs *globs, const char *APath)
{
  printf("(II) VFSRemove: Going to remove the file '%s'...\n", APath);

  char *AFile = exclude_trailing_path_sep(APath);
  unsigned long int file_no = filelist_find_index_by_path(globs->files, AFile) - 1;
  free(AFile);

  if (file_no < 0) {
    printf("(EE) VFSRemove: can't find the file specified: '%s'\n", APath);
    return cVFS_Failed;
  }

  try {
    try {
	  if (! globs->zip->RemoveFile(file_no)) {
        printf("(EE) VFSRemove: Delete file '%s' failed.\n", APath);
        return cVFS_Failed;
      }
      build_global_filelist(globs);
      globs->archive_modified = true;
      printf("(II) VFSRemove OK.\n");
      
      //  Test for the sparse ZIP central directory
      char *AFile1 = exclude_trailing_path_sep(APath);
      char *AFile2 = g_path_get_dirname(AFile1);
      char *AFile3 = exclude_trailing_path_sep(AFile2);
      if ((strlen(AFile3) > 0) && (strcmp(AFile3, "/") != 0)) {
        printf("(II) VFSRemove: AFile1: '%s', AFile2: '%s', AFile3: '%s'\n", AFile1, AFile2, AFile3);
        file_no = filelist_find_index_by_path(globs->files, AFile2) - 1;
        printf("(II) VFSRemove: deleted: '%s', parent: '%s', file_no = %ld\n", APath, AFile3, file_no);
        if (file_no < 0) {
          printf("(WW) VFSRemove: sparse ZIP archive detected, adding empty directory: '%s'\n", AFile3);
          VFSMkDir(globs, AFile3);
        }
      }  
      free(AFile1);   free(AFile2);   free(AFile3);
      
      return cVFS_OK;
    }
    catch (CZipException e) {
      fprintf(stderr, "(EE) VFSRemove: Delete file '%s' failed: [%d] %s, archive closed = %d.\n", 
                       APath, e.m_iCause, (LPCTSTR)e.GetErrorDescription(), globs->zip->IsClosed());
      return get_vfs_errorcode(e.m_iCause);
    }
  }
  catch (...)
  {
    printf("(EE) VFSRemove: Delete file '%s' failed.\n", APath);
    return cVFS_Failed;
  }
}

TVFSResult VFSRename(struct TVFSGlobs *globs, const char *sSrcName, const char *sDstName)
{
  printf ("(II) VFSRename: Going to rename/move the file '%s' to '%s'...\n", sSrcName, sDstName);

  char *AFile = exclude_trailing_path_sep(sSrcName);
  char *ADestFile = exclude_trailing_path_sep(sDstName);
  unsigned long int file_no = filelist_find_index_by_path(globs->files, AFile) - 1;
  free(AFile);

  if (file_no < 0) {
    printf("(EE) VFSRename: can't find the file specified: '%s'\n", sSrcName);
    return cVFS_Failed;
  }

  try {
    try {
      if (! globs->zip->RenameFile(file_no, ADestFile)) {
        printf ("(EE) VFSRename: Rename/move file '%s' failed.\n", sSrcName);
        return cVFS_Failed;
      }
      free(ADestFile);
      build_global_filelist(globs);
      globs->archive_modified = true;	
      return cVFS_OK;
    }
    catch (CZipException e) {
      fprintf(stderr, "(EE) VFSRename: Rename/move file '%s' failed: [%d] %s, archive closed = %d.\n",
                      sSrcName, e.m_iCause, (LPCTSTR)e.GetErrorDescription(), globs->zip->IsClosed());
      return get_vfs_errorcode(e.m_iCause);
    }
  }
  catch (...)
  {
    printf ("(EE) VFSRename: Rename/move file failed.\n");
    return cVFS_Failed;
  }
}

TVFSResult VFSMakeSymLink(struct TVFSGlobs *globs, const char *NewFileName, const char *PointTo)
{
  fprintf(stderr, "(EE) VFSMakeSymLink: Symbolic links not supported in ZIP archives.\n");
  return cVFS_Not_Supported;
}

TVFSResult VFSChmod(struct TVFSGlobs *globs, const char *FileName, const uint Mode)
{
  printf("(II) VFSChmod: Going to change permissions of the file '%s'...\n", FileName);

  char *AFile = exclude_trailing_path_sep(FileName);
  unsigned long int file_no = filelist_find_index_by_path(globs->files, AFile) - 1;
  free(AFile);

  if (file_no < 0) {
    printf("(EE) VFSChmod: can't find the file specified: '%s'\n", FileName);
    return cVFS_Failed;
  }

  try {
    try {
      //  Set system compatibility first
      if (! globs->zip->SetSystemCompatibility(ZipCompatibility::zcUnix)) {
          printf("(EE) VFSChmod: Unable to set system compatibility\n");
      }
    	
      //  Change the header data
      globs->zip->ReadLocalHeader(file_no);
      CZipFileHeader *header = globs->zip->GetFileInfo(file_no);
      if (! header) {
        printf("(EE) VFSChmod: Permissions modification of the file '%s' failed: NULL returned by GetFileInfo()\n", FileName);
        return cVFS_Failed;
      }
      //  We need to change only 0xF000FFFF mask
      //  The 0xF_______ bits represents file/directory type, the 0x____FFFF represents ZIP attributes and 0x_FFF____ represents unix permissions

//      printf("(II) VFSChmod: Current permissions: %lX, stripped: %lX, setting to: %X, modified: %lX\n", 
//              header->GetSystemAttr(), header->GetSystemAttr() & 0xF000FFFF, Mode & 0xFFF, (header->GetSystemAttr() & 0xF000FFFF) + ((Mode & 0xFFF) << 16));
//      globs->zip->SetFileHeaderAttr(*header, (header->GetSystemAttr() & 0xF000FFFF) + ((Mode & 0xFFF) << 16));

      printf("(II) VFSChmod: Current permissions: 0x%lX, stripped: 0x%lX, setting to: 0x%X, modified: 0x%lX\n", 
              header->GetSystemAttr(), header->GetSystemAttr() & 0xFFFFF000, 
              Mode & 0xFFF, (header->GetSystemAttr() & 0xFFFFF000) + (Mode & 0xFFF));
      globs->zip->SetFileHeaderAttr(*header, (header->GetSystemAttr() & 0xFFFFF000) + (Mode & 0xFFF));

      // write the local header information
      globs->zip->OverwriteLocalHeader(file_no);      

/*      
      //  Re-encrypt the file
      if (header->IsEncrypted()) {
    	  printf("(II) VFSChmod: Re-encrypting the file...\n");
    	  if (! globs->zip->EncryptFile(file_no)) 
    		  printf("(EE) VFSChmod: Unable to encrypt the file\n");
      }
*/
      globs->zip->RemoveCentralDirectoryFromArchive();
	  globs->zip->Flush();

      
      printf("(II) VFSChmod OK.\n");
      build_global_filelist(globs); 
      globs->archive_modified = true;
      return cVFS_OK;
    }
    catch (CZipException e) {
      globs->zip->CloseNewFile(true);
      fprintf(stderr, "(EE) VFSChmod: permissions modification of the file '%s' failed: [%d] %s, archive closed = %d.\n", 
                       FileName, e.m_iCause, (LPCTSTR)e.GetErrorDescription(), globs->zip->IsClosed());
      return get_vfs_errorcode(e.m_iCause);
    }
  }
  catch (...)
  {
    printf("(EE) VFSChmod: permissions modification of the file '%s' failed.\n", FileName);
    return cVFS_Failed;
  }
}

TVFSResult VFSChown(struct TVFSGlobs *globs, const char *FileName, const uint UID, const uint GID)
{
  fprintf(stderr, "(EE) VFSChown: Owner changing is not supported in ZIP archives.\n");
  return cVFS_Not_Supported;
}

TVFSResult VFSChangeTimes(struct TVFSGlobs *globs, char *APath, long mtime, long atime)
{
  printf ("(II) VFSChangeTimes: Going to change date/times of the file '%s'...\n", APath);

  char *AFile = exclude_trailing_path_sep(APath);
  unsigned long int file_no = filelist_find_index_by_path(globs->files, AFile) - 1;
  free(AFile);

  if (file_no < 0) {
    printf("(EE) VFSChangeTimes: can't find the file specified: '%s'\n", APath);
    return cVFS_Failed;
  }

  try {
    try {
      // read the local header information
      globs->zip->ReadLocalHeader(file_no);
      CZipFileHeader *header = globs->zip->GetFileInfo(file_no);
      if (! header) {
        printf("(EE) VFSChangeTimes: DateTime modification of the file '%s' failed: NULL returned by GetFileInfo()\n", APath);
        return cVFS_Failed;
      }
      //  Change the header data
      header->SetTime(mtime);
      
/*      //  Re-encrypt the file
      if (header->IsEncrypted()) {
    	  printf("(II) VFSChangeTimes: Re-encrypting the file...\n");
    	  if (! globs->zip->EncryptFile(file_no)) 
    		  printf("(EE) VFSChangeTimes: Unable to encrypt the file\n");
      }
*/
      // write the local header information
      globs->zip->OverwriteLocalHeader(file_no);      
      globs->zip->RemoveCentralDirectoryFromArchive();
     
      printf("(II) VFSChangeTimes OK.\n");
      build_global_filelist(globs); 
      globs->archive_modified = true;
      return cVFS_OK;
    }
    catch (CZipException e) {
      globs->zip->CloseNewFile(true);
      fprintf(stderr, "(EE) VFSChangeTimes: DateTime modification of the file '%s' failed: [%d] %s, archive closed = %d.\n", 
                       APath, e.m_iCause, (LPCTSTR)e.GetErrorDescription(), globs->zip->IsClosed());
      return get_vfs_errorcode(e.m_iCause);
    }
  }
  catch (...)
  {
    printf("(EE) VFSChangeTimes: DateTime modification of the file '%s' failed.\n", APath);
    return cVFS_Failed;
  }
}



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////

TVFSFileDes VFSOpenFile(struct TVFSGlobs *globs, const char *APath, int Mode, int *Error)
{
  *Error = cVFS_Not_Supported;
  return (TVFSFileDes)0;
}

TVFSResult VFSCloseFile(struct TVFSGlobs *globs, TVFSFileDes FileDescriptor)
{
  return cVFS_Not_Supported;
}

u_int64_t VFSFileSeek(struct TVFSGlobs *globs, TVFSFileDes FileDescriptor, u_int64_t AbsoluteOffset, int *Error)
{
  *Error = cVFS_Not_Supported;
  return 0;
}

int VFSReadFile(struct TVFSGlobs *globs, TVFSFileDes FileDescriptor, void *Buffer, int ABlockSize, int *Error)
{
  *Error = cVFS_Not_Supported;
  return 0;
}

int VFSWriteFile(struct TVFSGlobs *globs, TVFSFileDes FileDescriptor, void *Buffer, int BytesCount, int *Error)
{
  *Error = cVFS_Not_Supported;
  return 0;
}

void VFSSetBlockSize(struct TVFSGlobs *globs, int Value)
{
  globs->block_size = Value;
}

int VFSIsOnSameFS(struct TVFSGlobs *globs, const char *Path1, const char *Path2)
{
  printf("(II) VFSIsOnSameFS: Not supported in ZIP archives.\n");
  return true;
}

int VFSTwoSameFiles(struct TVFSGlobs *globs, const char *Path1, const char *Path2)
{
  printf("(II) VFSTwoSameFiles: Not supported in ZIP archives, comparing by paths.\n");
  return compare_two_same_files(Path1, Path2);
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////


//  Known issues: - crashes when no space left on NFS mounts, probably unhandled exception in further ZipArchive code (repro: Gentoo, Ubuntu)
TVFSResult VFSCopyToLocal(struct TVFSGlobs *globs, const char *sSrcName, const char *sDstName, int Append)
{
  gboolean try_again;
  
  if ((sSrcName == NULL)  || (sDstName == NULL) || (strlen(sSrcName) < 1) || (strlen(sDstName) < 1)) {
    printf("(EE) VFSCopyToLocal: The value of 'sSrcName' or 'sDstName' is NULL or empty\n");
    return cVFS_Failed;
  }
  
  printf("(II) VFSCopyToLocal: copying file '%s' out to '%s'\n", sSrcName, sDstName);

  unsigned long int file_no = filelist_find_index_by_path(globs->files, sSrcName) - 1;
  if (file_no < 0) {
    printf("(EE) VFSCopyToLocal: can't find source file '%s'\n", sSrcName);
    return cVFS_ReadErr;
  }

  char *s = exclude_trailing_path_sep(sDstName);
  char *dest_path = extract_file_path(s);
  char *dest_filename = extract_file_name(s);
  free(s);

  //  Perform extract
  try {
    do {
      try {
        try_again = FALSE; 
        if (! globs->zip->ExtractFile(file_no, dest_path, false, dest_filename, globs->block_size)) {
          globs->zip->CloseFile(NULL, true);
          fprintf(stderr, "(EE) VFSCopyToLocal: Error while copying out, archive closed = %d.\n", globs->zip->IsClosed());
          return cVFS_WriteErr;
        } 
        fprintf(stderr, "(II) VFSCopyToLocal: copy OK, archive closed = %d.\n", globs->zip->IsClosed());
      }
      catch (CZipException e) {
        globs->zip->CloseFile(NULL, true);
        fprintf(stderr, "(EE) VFSCopyToLocal: Error while copying out: [%d] %s, archive closed = %d.\n", 
                            e.m_iCause, (LPCTSTR)e.GetErrorDescription(), globs->zip->IsClosed());
        switch (e.m_iCause) {
          case CZipException::badPassword:
            if (globs->callback_ask_password) {
              char *passwd = NULL;
              int res = globs->callback_ask_password ("The archive is encrypted and requires password",
                                                      NULL, NULL, NULL, (TVFSAskPasswordFlags)(VFS_ASK_PASSWORD_NEED_PASSWORD | VFS_ASK_PASSWORD_ARCHIVE_MODE),
                                                      NULL, &passwd, NULL, NULL, NULL,
                                                      globs->callback_data);
              if (res && passwd) {
                fprintf(stderr, "  (II) VFSCopyToLocal: setting password to '%s'\n", passwd);
                globs->zip->SetPassword(passwd);
                try_again = TRUE;
                break;
              } else
                return cVFS_Cancelled;  
            }
          default:
            return get_vfs_errorcode(e.m_iCause);
        }
      }
    } while (try_again);
  }
  catch (...)
  {
    fprintf(stderr, "(EE) VFSCopyToLocal: Fatal error while copying out..., archive closed = %d.\n", globs->zip->IsClosed());
    return cVFS_WriteErr;
  }  

  free(dest_path);
  free(dest_filename);
  return cVFS_OK;
}

//  Known issues: - archive corruption when no space left on device
//				  - encrypted files are unreadable after copy in
TVFSResult VFSCopyFromLocal(struct TVFSGlobs *globs, const char *sSrcName, const char *sDstName, int Append)
{
  gboolean try_again;

  if ((sSrcName == NULL)  || (sDstName == NULL) || (strlen(sSrcName) < 1) || (strlen(sDstName) < 1)) {
    printf("(EE) VFSCopyFromLocal: The value of 'sSrcName' or 'sDstName' is NULL or empty\n");
    return cVFS_Failed;
  }

  printf("(II) VFSCopyFromLocal: copying file '%s' in to '%s'\n", sSrcName, sDstName);

  try {
    do {
      try {
        try_again = FALSE; 
        char *s = exclude_leading_path_sep(sDstName);
        if (! globs->zip->AddNewFile(sSrcName, s, -1, CZipArchive::zipsmSafeSmart, globs->block_size)) {
          globs->zip->CloseNewFile(true);
          globs->zip->CloseFile(NULL, true);
          build_global_filelist(globs);
          fprintf(stderr, "(EE) VFSCopyFromLocal: Error while copying in, archive closed = %d.\n", globs->zip->IsClosed());
          return cVFS_WriteErr;
        }

        globs->zip->Flush();
        printf("(II) VFSCopyFromLocal: copy OK, archive closed = %d.\n", globs->zip->IsClosed());
        build_global_filelist(globs);
        globs->archive_modified = true;

  /*      
        //  Encrypt the file if archive contains any encrypted files
        if (globs->need_password) {
            unsigned long int file_no = filelist_find_index_by_path(globs->files, s) - 1;
            if (file_no < 0) {
                    printf("(EE) VFSCopyFromLocal: unable to find index for newly written file '%s'\n", sSrcName);
                    return cVFS_WriteErr;
            }
            printf("(II) VFSCopyFromLocal: Encrypting the newly written file...\n");
            if (! globs->zip->EncryptFile(file_no)) 
                    printf("(EE) VFSCopyFromLocal: Unable to encrypt the newly written file\n");
        }  
    */
        
        free(s);
      }
      catch (CZipException e) {
        globs->zip->CloseNewFile(true);
        globs->zip->CloseFile(NULL, true);
        build_global_filelist(globs);
        fprintf(stderr, "(EE) VFSCopyFromLocal: Error while copying in: [%d] %s, archive closed = %d.\n", 
                        e.m_iCause, (LPCTSTR)e.GetErrorDescription(), globs->zip->IsClosed());
        switch (e.m_iCause) {
          case CZipException::badPassword:
            if (globs->callback_ask_password) {
              char *passwd = NULL;
              int res = globs->callback_ask_password ("The archive is encrypted and requires password",
                                                      NULL, NULL, NULL, (TVFSAskPasswordFlags)(VFS_ASK_PASSWORD_NEED_PASSWORD | VFS_ASK_PASSWORD_ARCHIVE_MODE),
                                                      NULL, &passwd, NULL, NULL, NULL,
                                                      globs->callback_data);
              if (res && passwd) {
                fprintf(stderr, "  (II) VFSCopyFromLocal: setting password to '%s'\n", passwd);
                globs->zip->SetPassword(passwd);
                try_again = TRUE;
                break;
              } else
                return cVFS_Cancelled;  
            }
          default:
            return get_vfs_errorcode(e.m_iCause);
        }
      }
    } while (try_again);
  }
  catch (...)
  {
    fprintf(stderr, "(EE) VFSCopyFromLocal: Fatal error while copying in..., archive closed = %d.\n", globs->zip->IsClosed());
    return cVFS_WriteErr;
  }

  return cVFS_OK;
}


///////////////////////////////
//  end of extern "C"
}




/***
* Todo:
* 
* - UTF-8, FName/FDisplayName and absolute/relative paths revision needed!
*    (check http://www.artpol-software.com/ZipArchive/KB/0610051525.aspx )
* - implement an "opened" flag - is it really needed? + add tests to all functions
* - readonly checking IsReadOnly() + add tests to all functions modifying the archive
* - after VFS API update implement archive testing, compression level setting, retrieving compression info for each file and archive, readonly flags, begin-action, end-action (no refresh, faster operations) ...
* - moving files from one directory to another within the archive
*
*/



