309 lines
9.1 KiB
C++
309 lines
9.1 KiB
C++
// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
|
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
|
|
|
#ifndef _FILESYSTEM_H
|
|
#define _FILESYSTEM_H
|
|
|
|
#include "ByteRange.h"
|
|
#include "DateTime.h"
|
|
#include "RefCounted.h"
|
|
#include "StringRange.h"
|
|
#include <deque>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
/*
|
|
* Functionality:
|
|
* - Overlay multiple file sources (directories and archives)
|
|
* in a single virtual file system.
|
|
* - Enumerate entries from that combined virtual file system.
|
|
* - Read a file from the virtual file system.
|
|
* - Determine the path of the application's installed files.
|
|
* - Determine a sensible path for application configuration files.
|
|
*/
|
|
|
|
namespace FileSystem {
|
|
|
|
class FileSource;
|
|
class FileData;
|
|
class FileSourceFS;
|
|
class FileSourceUnion;
|
|
|
|
void Init();
|
|
void Uninit();
|
|
|
|
extern FileSourceUnion gameDataFiles;
|
|
extern FileSourceFS userFiles;
|
|
|
|
std::string GetUserDir();
|
|
std::string GetDataDir();
|
|
|
|
/// Makes a string safe for use as a file name
|
|
/// warning: this mapping is non-injective, that is,
|
|
/// multiple input names may produce the same output
|
|
std::string SanitiseFileName(const std::string &a);
|
|
|
|
/// Create path <a>/<b>, coping with 'a' or 'b' being empty,
|
|
/// 'b' being an absolute path, or 'a' not having a trailing separator
|
|
std::string JoinPath(const std::string &a, const std::string &b);
|
|
|
|
/// Create path <base>/<path> ensuring that the result points
|
|
/// to a path at or below <base>
|
|
/// throws an exception (std::invalid_argument) if the path tries to escape
|
|
/// <base> must not be empty
|
|
std::string JoinPathBelow(const std::string &base, const std::string &path);
|
|
|
|
/// Given a path in the form <base>/<relpath>, return <relpath>.
|
|
/// If <base> is not the exact base of <path>, return <path> unchanged
|
|
std::string GetRelativePath(const std::string &base, const std::string &path);
|
|
|
|
/// Collapse redundant path separators, and '.' and '..' components
|
|
/// NB: this does not interpret symlinks, so the result may refer to
|
|
/// an entirely different file than the input
|
|
/// throws std::invalid_argument if the input path resolves to a 'negative' path
|
|
/// (e.g., "a/../.." resolves to a negative path)
|
|
std::string NormalisePath(const std::string &path);
|
|
|
|
enum class CopyMode {
|
|
OVERWRITE, // overwrite all files in target with files from source
|
|
ONLY_MISSING_IN_TARGET // only copy files that aren't present in target
|
|
};
|
|
|
|
/// Copy the contents of a directory from sourceFS into a directory in targetFS, according to copymode.
|
|
/// Returns false if sourceDir or targetDir are invalid
|
|
bool CopyDir(FileSource &sourceFS, std::string sourceDir, FileSourceFS &targetFS, std::string targetDir, CopyMode copymode = CopyMode::OVERWRITE);
|
|
|
|
class FileInfo {
|
|
friend class FileSource;
|
|
|
|
public:
|
|
FileInfo() :
|
|
m_source(0),
|
|
m_dirLen(0),
|
|
m_type(FT_NON_EXISTENT) {}
|
|
|
|
enum FileType {
|
|
// note: order here affects sort-order of FileInfo
|
|
FT_DIR,
|
|
FT_FILE,
|
|
FT_NON_EXISTENT,
|
|
FT_SPECIAL
|
|
};
|
|
|
|
enum FileType GetType() const { return m_type; }
|
|
bool Exists() const { return (m_type != FT_NON_EXISTENT); }
|
|
bool IsDir() const { return (m_type == FT_DIR); }
|
|
bool IsFile() const { return (m_type == FT_FILE); }
|
|
bool IsSpecial() const { return (m_type == FT_SPECIAL); }
|
|
|
|
// modification time specified in *local* time (not UTC)
|
|
// (specified in local time because we want it to be easy to display)
|
|
Time::DateTime GetModificationTime() const { return m_modTime; }
|
|
|
|
const std::string &GetPath() const { return m_path; }
|
|
std::string GetName() const { return m_path.substr(m_dirLen); }
|
|
std::string GetDir() const { return m_path.substr(0, m_dirLen); }
|
|
std::string GetAbsoluteDir() const;
|
|
std::string GetAbsolutePath() const;
|
|
|
|
const FileSource &GetSource() const { return *m_source; }
|
|
|
|
RefCountedPtr<FileData> Read() const;
|
|
|
|
friend bool operator==(const FileInfo &a, const FileInfo &b)
|
|
{
|
|
return (a.m_source == b.m_source && a.m_type == b.m_type && a.m_path == b.m_path);
|
|
}
|
|
friend bool operator!=(const FileInfo &a, const FileInfo &b)
|
|
{
|
|
return (a.m_source != b.m_source || a.m_type != b.m_type || a.m_path != b.m_path);
|
|
}
|
|
friend bool operator<(const FileInfo &a, const FileInfo &b)
|
|
{
|
|
int c = a.m_path.compare(b.m_path);
|
|
if (c != 0) {
|
|
return (c < 0);
|
|
}
|
|
if (a.m_type != b.m_type) {
|
|
return (a.m_type < b.m_type);
|
|
}
|
|
return (a.m_source < b.m_source);
|
|
}
|
|
friend bool operator<=(const FileInfo &a, const FileInfo &b)
|
|
{
|
|
int c = a.m_path.compare(b.m_path);
|
|
if (c != 0) {
|
|
return (c < 0);
|
|
}
|
|
if (a.m_type != b.m_type) {
|
|
return (a.m_type < b.m_type);
|
|
}
|
|
return (a.m_source <= b.m_source);
|
|
}
|
|
friend bool operator>(const FileInfo &a, const FileInfo &b) { return (b < a); }
|
|
friend bool operator>=(const FileInfo &a, const FileInfo &b) { return (b <= a); }
|
|
|
|
private:
|
|
// use FileSource::MakeFileInfo to create your FileInfos
|
|
FileInfo(FileSource *source, const std::string &path, FileType type, Time::DateTime modTime);
|
|
|
|
FileSource *m_source;
|
|
std::string m_path;
|
|
Time::DateTime m_modTime;
|
|
int m_dirLen;
|
|
FileType m_type;
|
|
};
|
|
|
|
class FileData : public RefCounted {
|
|
public:
|
|
virtual ~FileData() {}
|
|
|
|
const FileInfo &GetInfo() const { return m_info; }
|
|
size_t GetSize() const { return m_size; }
|
|
const char *GetData() const
|
|
{
|
|
assert(m_info.IsFile());
|
|
return m_data;
|
|
}
|
|
StringRange AsStringRange() const { return StringRange(m_data, m_size); }
|
|
ByteRange AsByteRange() const { return ByteRange(m_data, m_size); }
|
|
|
|
protected:
|
|
FileData(const FileInfo &info, size_t size, char *data) :
|
|
m_info(info),
|
|
m_data(data),
|
|
m_size(size) {}
|
|
FileData(const FileInfo &info) :
|
|
m_info(info),
|
|
m_data(0),
|
|
m_size(0) {}
|
|
|
|
FileInfo m_info;
|
|
char *m_data;
|
|
size_t m_size;
|
|
};
|
|
|
|
class FileDataMalloc : public FileData {
|
|
public:
|
|
FileDataMalloc(const FileInfo &info, size_t size) :
|
|
FileData(info, size, static_cast<char *>(std::malloc(size))) {}
|
|
FileDataMalloc(const FileInfo &info, size_t size, char *data) :
|
|
FileData(info, size, data) {}
|
|
virtual ~FileDataMalloc() { std::free(m_data); }
|
|
};
|
|
|
|
class FileSource {
|
|
public:
|
|
explicit FileSource(const std::string &root, bool trusted = false) :
|
|
m_root(root),
|
|
m_trusted(trusted) {}
|
|
virtual ~FileSource() {}
|
|
|
|
const std::string &GetRoot() const { return m_root; }
|
|
|
|
virtual FileInfo Lookup(const std::string &path) = 0;
|
|
virtual RefCountedPtr<FileData> ReadFile(const std::string &path) = 0;
|
|
virtual bool ReadDirectory(const std::string &path, std::vector<FileInfo> &output) = 0;
|
|
|
|
bool IsTrusted() const { return m_trusted; }
|
|
|
|
protected:
|
|
FileInfo MakeFileInfo(const std::string &path, FileInfo::FileType entryType, Time::DateTime modTime);
|
|
FileInfo MakeFileInfo(const std::string &path, FileInfo::FileType entryType);
|
|
|
|
private:
|
|
std::string m_root;
|
|
bool m_trusted;
|
|
};
|
|
|
|
class FileSourceFS : public FileSource {
|
|
public:
|
|
explicit FileSourceFS(const std::string &root, bool trusted = false);
|
|
~FileSourceFS();
|
|
|
|
virtual FileInfo Lookup(const std::string &path);
|
|
virtual RefCountedPtr<FileData> ReadFile(const std::string &path);
|
|
virtual bool ReadDirectory(const std::string &path, std::vector<FileInfo> &output);
|
|
|
|
bool MakeDirectory(const std::string &path);
|
|
|
|
enum WriteFlags {
|
|
WRITE_TEXT = 1
|
|
};
|
|
|
|
// similar to fopen(path, "rb")
|
|
FILE *OpenReadStream(const std::string &path);
|
|
// similar to fopen(path, "wb")
|
|
FILE *OpenWriteStream(const std::string &path, int flags = 0);
|
|
};
|
|
|
|
class FileSourceUnion : public FileSource {
|
|
public:
|
|
FileSourceUnion();
|
|
~FileSourceUnion();
|
|
|
|
// add and remove sources
|
|
// note: order is important. The array of sources works like a PATH array:
|
|
// that is, earlier sources take priority over later sources
|
|
void PrependSource(FileSource *fs);
|
|
void AppendSource(FileSource *fs);
|
|
void RemoveSource(FileSource *fs);
|
|
|
|
virtual FileInfo Lookup(const std::string &path);
|
|
std::vector<FileInfo> LookupAll(const std::string &path);
|
|
virtual RefCountedPtr<FileData> ReadFile(const std::string &path);
|
|
virtual bool ReadDirectory(const std::string &path, std::vector<FileInfo> &output);
|
|
|
|
private:
|
|
std::vector<FileSource *> m_sources;
|
|
};
|
|
|
|
class FileEnumerator {
|
|
public:
|
|
enum Flags {
|
|
IncludeDirs = 1,
|
|
IncludeSpecials = 2,
|
|
ExcludeFiles = 4,
|
|
Recurse = 8
|
|
};
|
|
|
|
explicit FileEnumerator(FileSource &fs, int flags = 0);
|
|
explicit FileEnumerator(FileSource &fs, const std::string &path, int flags = 0);
|
|
~FileEnumerator();
|
|
|
|
void AddSearchRoot(const std::string &path);
|
|
|
|
bool Finished() const { return m_queue.empty(); }
|
|
void Next();
|
|
const FileInfo &Current() const { return m_queue.front(); }
|
|
|
|
private:
|
|
void ExpandDirQueue();
|
|
void QueueDirectoryContents(const FileInfo &info);
|
|
|
|
FileSource *m_source;
|
|
std::deque<FileInfo> m_queue;
|
|
std::deque<FileInfo> m_dirQueue;
|
|
int m_flags;
|
|
};
|
|
|
|
} // namespace FileSystem
|
|
|
|
inline std::string FileSystem::FileInfo::GetAbsoluteDir() const
|
|
{
|
|
return JoinPath(m_source->GetRoot(), GetDir());
|
|
}
|
|
|
|
inline std::string FileSystem::FileInfo::GetAbsolutePath() const
|
|
{
|
|
return JoinPath(m_source->GetRoot(), GetPath());
|
|
}
|
|
|
|
inline RefCountedPtr<FileSystem::FileData> FileSystem::FileInfo::Read() const
|
|
{
|
|
return m_source->ReadFile(m_path);
|
|
}
|
|
|
|
#endif
|