ultimatepp/bazaar/plugin/gdal/port/cpl_vsil_abstract_archive.cpp
cxl 23ff1e7e82 .gdal moved to bazaar
git-svn-id: svn://ultimatepp.org/upp/trunk@9273 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2015-12-07 13:36:24 +00:00

620 lines
22 KiB
C++

/******************************************************************************
* $Id: cpl_vsil_abstract_archive.cpp 27745 2014-09-27 16:38:57Z goatbar $
*
* Project: CPL - Common Portability Library
* Purpose: Implement VSI large file api for archive files.
* Author: Even Rouault, even.rouault at mines-paris.org
*
******************************************************************************
* Copyright (c) 2010-2014, Even Rouault <even dot rouault at mines-paris dot org>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
#include "cpl_vsi_virtual.h"
#include "cpl_string.h"
#include "cpl_multiproc.h"
#include <map>
#include <set>
#define ENABLE_DEBUG 0
CPL_CVSID("$Id: cpl_vsil_abstract_archive.cpp 27745 2014-09-27 16:38:57Z goatbar $");
/************************************************************************/
/* ~VSIArchiveEntryFileOffset() */
/************************************************************************/
VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
{
}
/************************************************************************/
/* ~VSIArchiveReader() */
/************************************************************************/
VSIArchiveReader::~VSIArchiveReader()
{
}
/************************************************************************/
/* VSIArchiveFilesystemHandler() */
/************************************************************************/
VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
{
hMutex = NULL;
}
/************************************************************************/
/* ~VSIArchiveFilesystemHandler() */
/************************************************************************/
VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
{
std::map<CPLString,VSIArchiveContent*>::const_iterator iter;
for( iter = oFileList.begin(); iter != oFileList.end(); ++iter )
{
VSIArchiveContent* content = iter->second;
int i;
for(i=0;i<content->nEntries;i++)
{
delete content->entries[i].file_pos;
CPLFree(content->entries[i].fileName);
}
CPLFree(content->entries);
delete content;
}
if( hMutex != NULL )
CPLDestroyMutex( hMutex );
hMutex = NULL;
}
/************************************************************************/
/* GetContentOfArchive() */
/************************************************************************/
const VSIArchiveContent* VSIArchiveFilesystemHandler::GetContentOfArchive
(const char* archiveFilename, VSIArchiveReader* poReader)
{
CPLMutexHolder oHolder( &hMutex );
if (oFileList.find(archiveFilename) != oFileList.end() )
{
return oFileList[archiveFilename];
}
int bMustClose = (poReader == NULL);
if (poReader == NULL)
{
poReader = CreateReader(archiveFilename);
if (!poReader)
return NULL;
}
if (poReader->GotoFirstFile() == FALSE)
{
if (bMustClose)
delete(poReader);
return NULL;
}
VSIArchiveContent* content = new VSIArchiveContent;
content->nEntries = 0;
content->entries = NULL;
oFileList[archiveFilename] = content;
std::set<CPLString> oSet;
do
{
CPLString osFileName = poReader->GetFileName();
const char* fileName = osFileName.c_str();
/* Remove ./ pattern at the beginning of a filename */
if (fileName[0] == '.' && fileName[1] == '/')
{
fileName += 2;
if (fileName[0] == '\0')
continue;
}
char* pszStrippedFileName = CPLStrdup(fileName);
char* pszIter;
for(pszIter = pszStrippedFileName;*pszIter;pszIter++)
{
if (*pszIter == '\\')
*pszIter = '/';
}
int bIsDir = strlen(fileName) > 0 &&
fileName[strlen(fileName)-1] == '/';
if (bIsDir)
{
/* Remove trailing slash */
pszStrippedFileName[strlen(fileName)-1] = 0;
}
if (oSet.find(pszStrippedFileName) == oSet.end())
{
oSet.insert(pszStrippedFileName);
/* Add intermediate directory structure */
for(pszIter = pszStrippedFileName;*pszIter;pszIter++)
{
if (*pszIter == '/')
{
char* pszStrippedFileName2 = CPLStrdup(pszStrippedFileName);
pszStrippedFileName2[pszIter - pszStrippedFileName] = 0;
if (oSet.find(pszStrippedFileName2) == oSet.end())
{
oSet.insert(pszStrippedFileName2);
content->entries = (VSIArchiveEntry*)CPLRealloc(content->entries,
sizeof(VSIArchiveEntry) * (content->nEntries + 1));
content->entries[content->nEntries].fileName = pszStrippedFileName2;
content->entries[content->nEntries].nModifiedTime = poReader->GetModifiedTime();
content->entries[content->nEntries].uncompressed_size = 0;
content->entries[content->nEntries].bIsDir = TRUE;
content->entries[content->nEntries].file_pos = NULL;
if (ENABLE_DEBUG)
CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes", content->nEntries+1,
content->entries[content->nEntries].fileName,
content->entries[content->nEntries].uncompressed_size);
content->nEntries++;
}
else
{
CPLFree(pszStrippedFileName2);
}
}
}
content->entries = (VSIArchiveEntry*)CPLRealloc(content->entries,
sizeof(VSIArchiveEntry) * (content->nEntries + 1));
content->entries[content->nEntries].fileName = pszStrippedFileName;
content->entries[content->nEntries].nModifiedTime = poReader->GetModifiedTime();
content->entries[content->nEntries].uncompressed_size = poReader->GetFileSize();
content->entries[content->nEntries].bIsDir = bIsDir;
content->entries[content->nEntries].file_pos = poReader->GetFileOffset();
if (ENABLE_DEBUG)
CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes", content->nEntries+1,
content->entries[content->nEntries].fileName,
content->entries[content->nEntries].uncompressed_size);
content->nEntries++;
}
else
{
CPLFree(pszStrippedFileName);
}
} while(poReader->GotoNextFile());
if (bMustClose)
delete(poReader);
return content;
}
/************************************************************************/
/* FindFileInArchive() */
/************************************************************************/
int VSIArchiveFilesystemHandler::FindFileInArchive(const char* archiveFilename,
const char* fileInArchiveName,
const VSIArchiveEntry** archiveEntry)
{
if (fileInArchiveName == NULL)
return FALSE;
const VSIArchiveContent* content = GetContentOfArchive(archiveFilename);
if (content)
{
int i;
for(i=0;i<content->nEntries;i++)
{
if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
{
if (archiveEntry)
*archiveEntry = &content->entries[i];
return TRUE;
}
}
}
return FALSE;
}
/************************************************************************/
/* SplitFilename() */
/************************************************************************/
char* VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
CPLString &osFileInArchive,
int bCheckMainFileExists)
{
int i = 0;
if (strcmp(pszFilename, GetPrefix()) == 0)
return NULL;
/* Allow natural chaining of VSI drivers without requiring double slash */
CPLString osDoubleVsi(GetPrefix());
osDoubleVsi += "/vsi";
if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
pszFilename += strlen(GetPrefix());
else
pszFilename += strlen(GetPrefix()) + 1;
while(pszFilename[i])
{
std::vector<CPLString> oExtensions = GetExtensions();
std::vector<CPLString>::const_iterator iter;
int nToSkip = 0;
for( iter = oExtensions.begin(); iter != oExtensions.end(); ++iter )
{
const CPLString& osExtension = *iter;
if (EQUALN(pszFilename + i, osExtension.c_str(), strlen(osExtension.c_str())))
{
nToSkip = strlen(osExtension.c_str());
break;
}
}
if (nToSkip != 0)
{
VSIStatBufL statBuf;
char* archiveFilename = CPLStrdup(pszFilename);
int bArchiveFileExists = FALSE;
if (archiveFilename[i + nToSkip] == '/' ||
archiveFilename[i + nToSkip] == '\\')
{
archiveFilename[i + nToSkip] = 0;
}
if (!bCheckMainFileExists)
{
bArchiveFileExists = TRUE;
}
else
{
CPLMutexHolder oHolder( &hMutex );
if (oFileList.find(archiveFilename) != oFileList.end() )
{
bArchiveFileExists = TRUE;
}
}
if (!bArchiveFileExists)
{
VSIFilesystemHandler *poFSHandler =
VSIFileManager::GetHandler( archiveFilename );
if (poFSHandler->Stat(archiveFilename, &statBuf,
VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
!VSI_ISDIR(statBuf.st_mode))
{
bArchiveFileExists = TRUE;
}
}
if (bArchiveFileExists)
{
if (pszFilename[i + nToSkip] == '/' ||
pszFilename[i + nToSkip] == '\\')
{
char* pszArchiveInFileName = CPLStrdup(pszFilename + i + nToSkip + 1);
/* Replace a/../b by b and foo/a/../b by foo/b */
while(TRUE)
{
char* pszPrevDir = strstr(pszArchiveInFileName, "/../");
if (pszPrevDir == NULL || pszPrevDir == pszArchiveInFileName)
break;
char* pszPrevSlash = pszPrevDir - 1;
while(pszPrevSlash != pszArchiveInFileName &&
*pszPrevSlash != '/')
pszPrevSlash --;
if (pszPrevSlash == pszArchiveInFileName)
memmove(pszArchiveInFileName, pszPrevDir + nToSkip, strlen(pszPrevDir + nToSkip) + 1);
else
memmove(pszPrevSlash + 1, pszPrevDir + nToSkip, strlen(pszPrevDir + nToSkip) + 1);
}
osFileInArchive = pszArchiveInFileName;
CPLFree(pszArchiveInFileName);
}
else
osFileInArchive = "";
/* Remove trailing slash */
if (osFileInArchive.size())
{
char lastC = osFileInArchive[strlen(osFileInArchive) - 1];
if (lastC == '\\' || lastC == '/')
osFileInArchive.resize(strlen(osFileInArchive) - 1);
}
return archiveFilename;
}
CPLFree(archiveFilename);
}
i++;
}
return NULL;
}
/************************************************************************/
/* OpenArchiveFile() */
/************************************************************************/
VSIArchiveReader* VSIArchiveFilesystemHandler::OpenArchiveFile(const char* archiveFilename,
const char* fileInArchiveName)
{
VSIArchiveReader* poReader = CreateReader(archiveFilename);
if (poReader == NULL)
{
return NULL;
}
if (fileInArchiveName == NULL || strlen(fileInArchiveName) == 0)
{
if (poReader->GotoFirstFile() == FALSE)
{
delete(poReader);
return NULL;
}
/* Skip optionnal leading subdir */
CPLString osFileName = poReader->GetFileName();
const char* fileName = osFileName.c_str();
if (fileName[strlen(fileName)-1] == '/' || fileName[strlen(fileName)-1] == '\\')
{
if (poReader->GotoNextFile() == FALSE)
{
delete(poReader);
return NULL;
}
}
if (poReader->GotoNextFile())
{
CPLString msg;
msg.Printf("Support only 1 file in archive file %s when no explicit in-archive filename is specified",
archiveFilename);
const VSIArchiveContent* content = GetContentOfArchive(archiveFilename, poReader);
if (content)
{
int i;
msg += "\nYou could try one of the following :\n";
for(i=0;i<content->nEntries;i++)
{
msg += CPLString().Printf(" %s/%s/%s\n", GetPrefix(), archiveFilename, content->entries[i].fileName);
}
}
CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
delete(poReader);
return NULL;
}
}
else
{
const VSIArchiveEntry* archiveEntry = NULL;
if (FindFileInArchive(archiveFilename, fileInArchiveName, &archiveEntry) == FALSE ||
archiveEntry->bIsDir)
{
delete(poReader);
return NULL;
}
if (!poReader->GotoFileOffset(archiveEntry->file_pos))
{
delete poReader;
return NULL;
}
}
return poReader;
}
/************************************************************************/
/* Stat() */
/************************************************************************/
int VSIArchiveFilesystemHandler::Stat( const char *pszFilename,
VSIStatBufL *pStatBuf,
CPL_UNUSED int nFlags )
{
int ret = -1;
CPLString osFileInArchive;
memset(pStatBuf, 0, sizeof(VSIStatBufL));
char* archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
if (archiveFilename == NULL)
return -1;
if (strlen(osFileInArchive) != 0)
{
if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Looking for %s %s\n",
archiveFilename, osFileInArchive.c_str());
const VSIArchiveEntry* archiveEntry = NULL;
if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
{
/* Patching st_size with uncompressed file size */
pStatBuf->st_size = archiveEntry->uncompressed_size;
pStatBuf->st_mtime = (time_t)archiveEntry->nModifiedTime;
if (archiveEntry->bIsDir)
pStatBuf->st_mode = S_IFDIR;
else
pStatBuf->st_mode = S_IFREG;
ret = 0;
}
}
else
{
VSIArchiveReader* poReader = CreateReader(archiveFilename);
CPLFree(archiveFilename);
archiveFilename = NULL;
if (poReader != NULL && poReader->GotoFirstFile())
{
/* Skip optionnal leading subdir */
CPLString osFileName = poReader->GetFileName();
const char* fileName = osFileName.c_str();
if (fileName[strlen(fileName)-1] == '/' || fileName[strlen(fileName)-1] == '\\')
{
if (poReader->GotoNextFile() == FALSE)
{
delete(poReader);
return -1;
}
}
if (poReader->GotoNextFile())
{
/* Several files in archive --> treat as dir */
pStatBuf->st_size = 0;
pStatBuf->st_mode = S_IFDIR;
}
else
{
/* Patching st_size with uncompressed file size */
pStatBuf->st_size = poReader->GetFileSize();
pStatBuf->st_mtime = (time_t)poReader->GetModifiedTime();
pStatBuf->st_mode = S_IFREG;
}
ret = 0;
}
delete(poReader);
}
CPLFree(archiveFilename);
return ret;
}
/************************************************************************/
/* Unlink() */
/************************************************************************/
int VSIArchiveFilesystemHandler::Unlink( CPL_UNUSED const char *pszFilename )
{
return -1;
}
/************************************************************************/
/* Rename() */
/************************************************************************/
int VSIArchiveFilesystemHandler::Rename( CPL_UNUSED const char *oldpath,
CPL_UNUSED const char *newpath )
{
return -1;
}
/************************************************************************/
/* Mkdir() */
/************************************************************************/
int VSIArchiveFilesystemHandler::Mkdir( CPL_UNUSED const char *pszDirname,
CPL_UNUSED long nMode )
{
return -1;
}
/************************************************************************/
/* Rmdir() */
/************************************************************************/
int VSIArchiveFilesystemHandler::Rmdir( CPL_UNUSED const char *pszDirname )
{
return -1;
}
/************************************************************************/
/* ReadDir() */
/************************************************************************/
char** VSIArchiveFilesystemHandler::ReadDir( const char *pszDirname )
{
CPLString osInArchiveSubDir;
char* archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
if (archiveFilename == NULL)
return NULL;
int lenInArchiveSubDir = strlen(osInArchiveSubDir);
char **papszDir = NULL;
const VSIArchiveContent* content = GetContentOfArchive(archiveFilename);
if (!content)
{
CPLFree(archiveFilename);
return NULL;
}
if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Read dir %s", pszDirname);
int i;
for(i=0;i<content->nEntries;i++)
{
const char* fileName = content->entries[i].fileName;
/* Only list entries at the same level of inArchiveSubDir */
if (lenInArchiveSubDir != 0 &&
strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
(fileName[lenInArchiveSubDir] == '/' || fileName[lenInArchiveSubDir] == '\\') &&
fileName[lenInArchiveSubDir + 1] != 0)
{
const char* slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
if (slash == NULL)
slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
if (slash == NULL || slash[1] == 0)
{
char* tmpFileName = CPLStrdup(fileName);
if (slash != NULL)
{
tmpFileName[strlen(tmpFileName)-1] = 0;
}
if (ENABLE_DEBUG)
CPLDebug("VSIArchive", "Add %s as in directory %s\n",
tmpFileName + lenInArchiveSubDir + 1, pszDirname);
papszDir = CSLAddString(papszDir, tmpFileName + lenInArchiveSubDir + 1);
CPLFree(tmpFileName);
}
}
else if (lenInArchiveSubDir == 0 &&
strchr(fileName, '/') == NULL && strchr(fileName, '\\') == NULL)
{
/* Only list toplevel files and directories */
if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Add %s as in directory %s\n", fileName, pszDirname);
papszDir = CSLAddString(papszDir, fileName);
}
}
CPLFree(archiveFilename);
return papszDir;
}