201 lines
6.2 KiB
C++
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;
|
|
}
|