1
0
forked from 0ad/0ad
0ad/source/lib/file/vfs/vfs_populate.cpp
2012-01-14 18:46:20 +00:00

201 lines
6.2 KiB
C++

/* Copyright (c) 2012 Wildfire Games
*
* 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.
*/
/*
* populate VFS directories with files
*/
#include "precompiled.h"
#include "lib/file/vfs/vfs_populate.h"
#include "lib/file/archive/archive_zip.h"
#include "lib/file/vfs/vfs_tree.h"
#include "lib/file/vfs/vfs_lookup.h"
#include "lib/file/vfs/vfs.h" // error codes
#define ENABLE_ARCHIVE_STATS 0
#if ENABLE_ARCHIVE_STATS
static std::vector<const VfsFile*> s_looseFiles;
static size_t s_numArchivedFiles;
#endif
struct CompareFileInfoByName
{
bool operator()(const FileInfo& a, const FileInfo& b)
{
return a.Name() < b.Name();
}
};
// helper class that allows breaking up the logic into sub-functions without
// always having to pass directory/realDirectory as parameters.
class PopulateHelper
{
NONCOPYABLE(PopulateHelper);
public:
PopulateHelper(VfsDirectory* directory, const PRealDirectory& realDirectory)
: m_directory(directory), m_realDirectory(realDirectory)
{
}
Status AddEntries() const
{
#if ENABLE_ARCHIVE_STATS
s_looseFiles.reserve(10000);
#endif
FileInfos files; files.reserve(500);
DirectoryNames subdirectoryNames; subdirectoryNames.reserve(50);
RETURN_STATUS_IF_ERR(GetDirectoryEntries(m_realDirectory->Path(), &files, &subdirectoryNames));
// to support .DELETED files inside archives safely, we need to load
// archives and loose files in a deterministic order in case they add
// and then delete the same file (or vice versa, depending on loading
// order). GetDirectoryEntries has undefined order so sort its output
std::sort(files.begin(), files.end(), CompareFileInfoByName());
std::sort(subdirectoryNames.begin(), subdirectoryNames.end());
RETURN_STATUS_IF_ERR(AddFiles(files));
AddSubdirectories(subdirectoryNames);
return INFO::OK;
}
private:
void AddFile(const FileInfo& fileInfo) const
{
const VfsPath name = fileInfo.Name();
if(name.Extension() == L".DELETED")
{
m_directory->RemoveFile(name.Basename());
if(!(m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED))
return;
}
const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), m_realDirectory->Priority(), m_realDirectory);
const VfsFile* pfile = m_directory->AddFile(file);
#if ENABLE_ARCHIVE_STATS
// notify archive builder that this file could be archived but
// currently isn't; if there are too many of these, archive will
// be rebuilt.
// note: check if archivable to exclude stuff like screenshots
// from counting towards the threshold.
if(m_realDirectory->Flags() & VFS_MOUNT_ARCHIVABLE)
s_looseFiles.push_back(pfile);
#else
UNUSED2(pfile);
#endif
}
static void AddArchiveFile(const VfsPath& pathname, const FileInfo& fileInfo, PIArchiveFile archiveFile, uintptr_t cbData)
{
PopulateHelper* this_ = (PopulateHelper*)cbData;
// (we have to create missing subdirectoryNames because archivers
// don't always place directory entries before their files)
const size_t flags = VFS_LOOKUP_ADD|VFS_LOOKUP_SKIP_POPULATE;
VfsDirectory* directory;
WARN_IF_ERR(vfs_Lookup(pathname, this_->m_directory, directory, 0, flags));
const VfsPath name = fileInfo.Name();
if(name.Extension() == L".DELETED")
{
directory->RemoveFile(name.Basename());
if(!(this_->m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED))
return;
}
const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), this_->m_realDirectory->Priority(), archiveFile);
directory->AddFile(file);
#if ENABLE_ARCHIVE_STATS
s_numArchivedFiles++;
#endif
}
Status AddFiles(const FileInfos& files) const
{
const OsPath path(m_realDirectory->Path());
for(size_t i = 0; i < files.size(); i++)
{
const OsPath pathname = path / files[i].Name();
if(pathname.Extension() == L".zip")
{
PIArchiveReader archiveReader = CreateArchiveReader_Zip(pathname);
// archiveReader == nullptr if file could not be opened (e.g. because
// archive is currently open in another program)
if(archiveReader)
RETURN_STATUS_IF_ERR(archiveReader->ReadEntries(AddArchiveFile, (uintptr_t)this));
}
else // regular (non-archive) file
AddFile(files[i]);
}
return INFO::OK;
}
void AddSubdirectories(const DirectoryNames& subdirectoryNames) const
{
for(size_t i = 0; i < subdirectoryNames.size(); i++)
{
// skip version control directories - this avoids cluttering the
// VFS with hundreds of irrelevant files.
if(subdirectoryNames[i] == L".svn")
continue;
VfsDirectory* subdirectory = m_directory->AddSubdirectory(subdirectoryNames[i]);
PRealDirectory realDirectory = CreateRealSubdirectory(m_realDirectory, subdirectoryNames[i]);
vfs_Attach(subdirectory, realDirectory);
}
}
VfsDirectory* const m_directory;
PRealDirectory m_realDirectory;
};
Status vfs_Populate(VfsDirectory* directory)
{
if(!directory->ShouldPopulate())
return INFO::OK;
const PRealDirectory& realDirectory = directory->AssociatedDirectory();
if(realDirectory->Flags() & VFS_MOUNT_WATCH)
realDirectory->Watch();
PopulateHelper helper(directory, realDirectory);
RETURN_STATUS_IF_ERR(helper.AddEntries());
return INFO::OK;
}
Status vfs_Attach(VfsDirectory* directory, const PRealDirectory& realDirectory)
{
RETURN_STATUS_IF_ERR(vfs_Populate(directory));
directory->SetAssociatedDirectory(realDirectory);
return INFO::OK;
}