Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F134465
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
76 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/util/file-system.cpp b/util/file-system.cpp
index 97d991ca..7ad04f90 100644
--- a/util/file-system.cpp
+++ b/util/file-system.cpp
@@ -1,1884 +1,1895 @@
#ifdef USE_ALLEGRO
#include <allegro.h>
/* FIXME: replace with <winalleg.h> */
#ifdef _WIN32
#define BITMAP dummyBITMAP
#include <windows.h>
#undef BITMAP
#endif
#endif
#include <algorithm>
#include "funcs.h"
#include "file-system.h"
#include "thread.h"
#include "system.h"
#include "utf.h"
// #include "globals.h"
#include <dirent.h>
#include <sstream>
#include <exception>
#include <string>
#include <fstream>
#include <ostream>
#include "token.h"
#include "zip/unzip.h"
#include "zip/ioapi.h"
#include "7z/7zFile.h"
#include "7z/7z.h"
#include "7z/7zAlloc.h"
#include "7z/7zCrc.h"
#ifndef USE_ALLEGRO
/* some sfl symbols conflict with allegro */
#include "sfl/sfl.h"
#include "sfl/sfldir.h"
#endif
#ifdef _WIN32
// #define _WIN32_IE 0x400
#include <shlobj.h>
#endif
using namespace std;
/* some filesystem access can only be done by one thread at a time. specifically, sfl
* has its own allocator that is meant to be used in a single-threaded manner.
* rather than try to add locks to sfl we just wrap all sfl calls with a lock.
*
* initialize() must be called to initialize this lock
*/
// Util::Thread::Lock lock;
namespace Path{
/* remove extra path separators (/) */
string sanitize(string path){
size_t double_slash = path.find("//");
while (double_slash != string::npos){
path.erase(double_slash, 1);
double_slash = path.find("//");
}
/* Remove /./ from paths because its redundant */
size_t useless = path.find("/./");
while (useless != string::npos){
path.erase(useless, 2);
useless = path.find("/./");
}
return path;
}
static string removeEndSlashes(string path){
size_t last = path.rfind("/");
while (path.size() > 0 && last == path.size() - 1){
path.erase(last, 1);
last = path.rfind("/");
}
return path;
}
static int invert(int c){
if (c == '\\'){
return '/';
}
return c;
}
std::string invertSlashes(string str){
transform(str.begin(), str.end(), str.begin(), invert);
return str;
}
const string & Path::path() const {
return mypath;
}
const string Path::getExtension() const {
size_t dot = mypath.rfind('.');
if (dot == string::npos){
return "";
} else {
return mypath.substr(dot + 1);
}
}
bool Path::isEmpty() const {
return mypath.empty();
}
Path::~Path(){
}
Path::Path(){
}
Path::Path(const std::string & path):
mypath(sanitize(invertSlashes(path))){
}
Path::Path(const Path & path):
mypath(sanitize(invertSlashes(path.path()))){
}
RelativePath::RelativePath(){
}
RelativePath::RelativePath(const std::string & path):
Path(path){
if (! path.empty() && path[0] == '/'){
ostringstream out;
out << "Relative path '" << path << "' cannot start with a /. Only absolute paths can start with /";
throw Storage::IllegalPath(__FILE__, __LINE__, out.str());
}
}
/* a/b/c/d -> b/c/d */
std::string stripFirstDir(const std::string & str){
if (str.find("/") != std::string::npos || str.find( "\\") != std::string::npos){
std::string temp = str;
size_t rem = temp.find("/");
if (rem != std::string::npos){
return str.substr(rem+1,str.size());
}
rem = temp.find("\\");
if( rem != std::string::npos ){
return str.substr(rem+1,str.size());
}
}
return str;
}
static vector<string> splitPath(string path){
vector<string> all;
if (path.size() > 0 && path[0] == '/'){
all.push_back("/");
}
size_t found = path.find('/');
while (found != string::npos){
if (found > 0){
all.push_back(path.substr(0, found));
}
path.erase(0, found + 1);
found = path.find('/');
}
if (path.size() != 0){
all.push_back(path);
}
return all;
}
static string joinPath(const vector<string> & paths){
ostringstream out;
bool first = true;
for (vector<string>::const_iterator it = paths.begin(); it != paths.end(); it++){
if (!first){
out << '/';
}
out << *it;
first = false;
}
return out.str();
}
/* a/b/c/d -> a/b/c
* a/b/c/d/ -> a/b/c
*/
static string dirname(string path){
vector<string> paths = splitPath(path);
if (paths.size() > 1){
paths.pop_back();
return joinPath(paths);
} else if (paths.size() == 1){
if (paths[0] == "/"){
return "/";
}
return ".";
} else {
return ".";
}
/*
while (path.size() > 0 && path[path.size() - 1] == '/'){
path.erase(path.size() - 1);
}
if (path.find("/") != string::npos ||
path.find("\\") != string::npos){
size_t rem = path.find_last_of("/");
if (rem != string::npos){
return path.substr(0, rem + 1);
}
rem = path.find_last_of("\\");
if (rem != string::npos){
return path.substr(0, rem + 1);
}
}
return "";
*/
}
RelativePath::RelativePath(const RelativePath & path):
Path(path){
}
RelativePath RelativePath::removeFirstDirectory() const {
return RelativePath(stripFirstDir(path()));
}
bool RelativePath::isFile() const {
vector<string> paths = splitPath(path());
return paths.size() == 1;
}
RelativePath RelativePath::firstDirectory() const {
vector<string> paths = splitPath(path());
if (paths.size() > 1){
return RelativePath(paths[0]);
}
return RelativePath();
}
RelativePath RelativePath::getDirectory() const {
return RelativePath(dirname(path()));
}
RelativePath RelativePath::getFilename() const {
return RelativePath(stripDir(path()));
}
bool RelativePath::operator<(const RelativePath & path) const {
return this->path() < path.path();
}
bool RelativePath::operator==(const RelativePath & path) const {
return this->path() == path.path();
}
bool RelativePath::operator!=(const RelativePath & path) const {
return !(*this == path);
}
RelativePath RelativePath::join(const RelativePath & him) const {
return RelativePath(path() + "/" + him.path());
}
RelativePath & RelativePath::operator=(const RelativePath & copy){
setPath(copy.path());
return *this;
}
AbsolutePath::AbsolutePath(){
}
AbsolutePath::AbsolutePath(const std::string & path):
Path(path){
}
AbsolutePath::AbsolutePath(const AbsolutePath & path):
Path(path){
}
AbsolutePath & AbsolutePath::operator=(const AbsolutePath & copy){
setPath(copy.path());
return *this;
}
AbsolutePath AbsolutePath::removeFirstDirectory() const {
return AbsolutePath(stripFirstDir(path()));
}
bool AbsolutePath::operator==(const AbsolutePath & path) const {
return removeEndSlashes(this->path()) == removeEndSlashes(path.path());
}
bool AbsolutePath::operator!=(const AbsolutePath & path) const {
return !(*this == path);
}
bool AbsolutePath::operator<(const AbsolutePath & path) const {
return this->path() < path.path();
}
string AbsolutePath::firstDirectory() const {
vector<string> paths = splitPath(path());
if (paths.size() > 1){
return paths[0];
}
return removeEndSlashes(path());
}
bool AbsolutePath::isFile() const {
vector<string> paths = splitPath(path());
return path().find("/") == string::npos;
// paths.size() == 1;
}
RelativePath AbsolutePath::remove(const AbsolutePath & what) const {
string real = path();
real.erase(0, what.path().size());
while (real.find("/") == 0){
real.erase(0, 1);
}
return RelativePath(real);
}
AbsolutePath AbsolutePath::getDirectory() const {
return AbsolutePath(dirname(path()));
}
AbsolutePath AbsolutePath::getFilename() const {
return AbsolutePath(stripDir(path()));
}
std::string AbsolutePath::getLastComponent() const {
if (getFilename().path() == ""){
return stripDir(removeEndSlashes(path()));
}
return getFilename().path();
}
AbsolutePath AbsolutePath::join(const RelativePath & path) const {
return AbsolutePath(this->path() + "/" + path.path());
}
InsensitivePath::InsensitivePath(const Path & what):
Path(what){
}
bool InsensitivePath::operator==(const Path & path) const {
return Util::upperCaseAll(this->path()) == Util::upperCaseAll(path.path());
}
std::string removeExtension(const std::string & str){
if (str.find(".") != std::string::npos){
return str.substr(0, str.find_last_of("."));
}
return str;
}
AbsolutePath replaceExtension(const Filesystem::AbsolutePath & input, const std::string & extension){
return AbsolutePath(removeExtension(input.path()) + "." + extension);
}
/* a/b/c/d -> d */
std::string stripDir(const std::string & str){
if (str.find("/") != std::string::npos || str.find("\\") != std::string::npos){
std::string temp = str;
size_t rem = temp.find_last_of( "/" );
if (rem != std::string::npos){
return str.substr(rem+1,str.size());
}
rem = temp.find_last_of( "\\" );
if( rem != std::string::npos ){
return str.substr(rem+1,str.size());
}
}
return str;
}
/* a/b/c/d -> a/b/c/ */
std::string stripFilename(const std::string & str){
std::string temp = str;
if( str.find( "/") != std::string::npos || str.find( "\\") != std::string::npos ){
size_t rem = temp.find_last_of( "/" );
if( rem != std::string::npos ){
return str.substr(0,rem+1);
}
rem = temp.find_last_of( "\\" );
if( rem != std::string::npos ){
return str.substr(0,rem+1);
}
}
return "";
}
}
namespace Storage{
Exception::Exception(const std::string & where, int line, const std::string & file):
Exc::Base(where, line),
reason(file){
}
Exception::Exception(const std::string & where, int line, const Exc::Base & nested, const std::string & file):
Exc::Base(where, line, nested),
reason(file){
}
Exception::Exception(const Exception & copy):
Exc::Base(copy),
reason(copy.reason){
}
Exception::~Exception() throw (){
}
const std::string Exception::getReason() const {
return reason;
}
NotFound::NotFound(const std::string & where, int line, const std::string & file):
Exception(where, line, file){
}
NotFound::NotFound(const std::string & where, int line, const Exc::Base & nested, const std::string & file):
Exception(where, line, nested, file){
}
NotFound::NotFound(const NotFound & copy):
Exception(copy){
}
NotFound::~NotFound() throw (){
}
IllegalPath::IllegalPath(const std::string & where, int line, const std::string & file):
Exception(where, line, file){
}
IllegalPath::IllegalPath(const std::string & where, int line, const Exc::Base & nested, const std::string & file):
Exception(where, line, nested, file){
}
IllegalPath::IllegalPath(const IllegalPath & copy):
Exception(copy){
}
IllegalPath::~IllegalPath() throw(){
}
System::System(){
}
System::~System(){
}
vector<Filesystem::AbsolutePath> System::getFiles(const Filesystem::AbsolutePath & dataPath, const Filesystem::RelativePath & find, bool caseInsensitive){
if (find.isFile()){
return getFiles(dataPath, find.path(), caseInsensitive);
}
/* split the path into its consituent parts
* a/b/c -> a/b and c
* search for a/b, then search for c in the results
*/
Filesystem::RelativePath directory = find.getDirectory();
Filesystem::RelativePath file = find.getFilename();
vector<Filesystem::AbsolutePath> more = getFiles(dataPath, directory, caseInsensitive);
vector<Filesystem::AbsolutePath> out;
for (vector<Filesystem::AbsolutePath>::iterator it = more.begin(); it != more.end(); it++){
Filesystem::AbsolutePath path = *it;
/* if its not a directory then we can't keep searching */
if (::System::isDirectory(path.path())){
vector<Filesystem::AbsolutePath> findMore = getFiles(path, file, caseInsensitive);
out.insert(out.end(), findMore.begin(), findMore.end());
}
}
return out;
}
Filesystem::AbsolutePath System::findContainer(const RelativePath & dataPath){
try{
return find(RelativePath(dataPath.path() + ".zip"));
} catch (const NotFound & fail){
}
try{
return find(RelativePath(dataPath.path() + ".7z"));
} catch (const NotFound & fail){
}
throw NotFound(dataPath.path(), __LINE__, __FILE__);
}
vector<Filesystem::AbsolutePath> System::getContainerFilesRecursive(const Filesystem::AbsolutePath & dataPath){
vector<Filesystem::AbsolutePath> out;
vector<Filesystem::AbsolutePath> zips = getFilesRecursive(dataPath, "*.zip");
out.insert(out.end(), zips.begin(), zips.end());
zips = getFilesRecursive(dataPath, "*.7z");
out.insert(out.end(), zips.begin(), zips.end());
return out;
}
vector<Path::AbsolutePath> System::getContainerFiles(const AbsolutePath & dataPath){
vector<Filesystem::AbsolutePath> out;
vector<Filesystem::AbsolutePath> zips = getFiles(dataPath, "*.zip");
out.insert(out.end(), zips.begin(), zips.end());
zips = getFiles(dataPath, "*.7z");
out.insert(out.end(), zips.begin(), zips.end());
return out;
}
vector<Filesystem::AbsolutePath> System::getContainerFiles(const RelativePath & path){
vector<Filesystem::AbsolutePath> out;
vector<Filesystem::AbsolutePath> zips = getFiles(path, Filesystem::RelativePath("*.zip"), false);
out.insert(out.end(), zips.begin(), zips.end());
zips = getFiles(path, Filesystem::RelativePath("*.7z"), false);
out.insert(out.end(), zips.begin(), zips.end());
return out;
}
static Util::ReferenceCount<System> self;
System & instance(){
if (self != NULL){
return *self;
}
self = new Filesystem(Util::getDataPath2());
return *self;
}
File::File(){
}
File::~File(){
}
class NormalFile: public File {
public:
NormalFile(const Path::AbsolutePath & path, Access mode = Read):
path(path){
ios_base::openmode iosMode = fstream::in;
switch (mode){
- case Read: iosMode = fstream::in; break;
+ case Read: iosMode = fstream::in; check(path); break;
case Write: iosMode = fstream::out; break;
- case ReadWrite: iosMode = fstream::in | fstream::out; break;
+ case ReadWrite: iosMode = fstream::in | fstream::out; check(path); break;
}
in.open(path.path().c_str(), iosMode | fstream::binary);
- in >> noskipws;
+ if (in.good()){
+ in >> noskipws;
+ }
}
Token * location(){
Token * head = new Token();
*head << "file";
*head << path.path();
return head;
}
+ /* Throws NotFound if path doesnt exist */
+ void check(const Path::AbsolutePath & path){
+ if (!instance().systemExists(path)){
+ ostringstream out;
+ out << "Could not find '" << path.path() << "'";
+ throw NotFound(__FILE__, __LINE__, out.str());
+ }
+ }
+
long getModificationTime(){
return ::System::getModificationTime(path.path());
}
bool canStream(){
return true;
}
long tell(){
return in.tellg();
}
void reset(){
in.clear();
}
off_t seek(off_t position, int whence){
switch (whence){
case SEEK_SET: in.seekg(position, ios::beg); break;
case SEEK_CUR: in.seekg(position, ios::cur); break;
case SEEK_END: in.seekg(position, ios::end); break;
}
return in.tellg();
}
bool eof(){
return in.eof();
}
int getSize(){
streampos here = in.tellg();
in.seekg(0, ios::end);
int length = in.tellg();
in.seekg(here, ios::beg);
return length;
}
bool good(){
return in.good();
}
File & operator>>(unsigned char & c){
in >> c;
return *this;
}
int readLine(char * output, int size){
in.read(output, size);
return in.gcount();
}
~NormalFile(){
in.close();
}
protected:
const Path::AbsolutePath path;
std::fstream in;
};
StringFile::StringFile(const std::string & start):
data(start),
stream(start){
stream >> noskipws;
}
Token * StringFile::location(){
Token * head = new Token();
*head << "<string file>";
return head;
}
long StringFile::getModificationTime(){
/* FIXME: maybe return INT_MAX or something? */
return 0;
}
void StringFile::reset(){
/* TODO or nothing..? */
}
int StringFile::readLine(char * output, int size){
stream.read(output, size);
return stream.gcount();
}
bool StringFile::canStream(){
return true;
}
int StringFile::getSize(){
return data.size();
}
long StringFile::tell(){
return stream.tellg();
}
off_t StringFile::seek(off_t position, int whence){
switch (whence){
case SEEK_SET: stream.seekg(position, ios::beg); break;
case SEEK_CUR: stream.seekg(position, ios::cur); break;
case SEEK_END: stream.seekg(position, ios::end); break;
}
return stream.tellg();
}
bool StringFile::eof(){
return stream.eof();
}
bool StringFile::good(){
return stream.good();
}
File & StringFile::operator>>(unsigned char & c){
stream >> c;
return *this;
}
StringFile::~StringFile(){
}
/* For 7z */
class LzmaContainer{
public:
LzmaContainer(const string & path, const Filesystem::AbsolutePath & start):
path(path),
start(start){
allocator.Alloc = SzAlloc;
allocator.Free = SzFree;
allocatorTemporary.Alloc = SzAllocTemp;
allocatorTemporary.Free = SzFreeTemp;
if (InFile_Open(&archiveStream.file, path.c_str())){
/* Error */
}
FileInStream_CreateVTable(&archiveStream);
LookToRead_CreateVTable(&lookStream, False);
lookStream.realStream = &archiveStream.s;
LookToRead_Init(&lookStream);
CrcGenerateTable();
SzArEx_Init(&database);
int ok = SzArEx_Open(&database, &lookStream.s, &allocator, &allocatorTemporary);
if (ok == SZ_OK){
/* Can read files */
UInt16 * name = NULL;
size_t tempSize = 0;
for (unsigned int index = 0; index < database.db.NumFiles; index++){
size_t offset = 0;
size_t outSizeProcessed = 0;
const CSzFileItem * file = database.db.Files + index;
size_t length;
if (file->IsDir){
continue;
}
length = SzArEx_GetFileNameUtf16(&database, index, NULL);
if (length > tempSize){
delete[] name;
name = new UInt16[length];
memset(name, 0, length);
tempSize = length;
}
SzArEx_GetFileNameUtf16(&database, index, name);
files[Utf::utf16_to_utf8(name)] = index;
}
delete[] name;
}
}
void readFile(const Path::AbsolutePath & path, unsigned char ** buffer, size_t * size){
Path::RelativePath find(path.remove(start));
*buffer = 0;
*size = 0;
if (files.find(find.path()) != files.end()){
int index = files[find.path()];
UInt32 block = 0;
size_t offset = 0;
size_t processed = 0;
int ok = SzArEx_Extract(&database, &lookStream.s, index,
&block, buffer, size, &offset, &processed,
&allocator, &allocatorTemporary);
if (ok != SZ_OK){
Global::debug(0) << "Could not read file from 7z archive: " << path.path() << endl;
return;
}
if (offset != 0){
memmove(*buffer, *buffer + offset, processed);
}
/* The original size is the size of the block buffer, but we only care
* about the size of the actual file.
*/
*size = processed;
}
}
vector<string> getFiles(){
vector<string> names;
for (map<string, int>::iterator it = files.begin(); it != files.end(); it++){
names.push_back(it->first);
}
return names;
}
string getPath() const {
return path;
}
string getMount() const {
return start.path();
}
long getModificationTime(const Path::AbsolutePath & path){
if (files.find(path.path()) != files.end()){
int index = files[path.path()];
const CSzFileItem * file = database.db.Files + index;
if (file->MTimeDefined){
CNtfsFileTime time = file->MTime;
/* Divide by 10 million here? */
return (time.Low | ((UInt64)time.High << 32)) / 10000000;
/*
unsigned year, mon, day, hour, min, sec;
UInt64 v64 = (time.Low | ((UInt64)time.High << 32)) / 10000000;
Byte ms[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
unsigned t;
UInt32 v;
sec = (unsigned)(v64 % 60); v64 /= 60;
min = (unsigned)(v64 % 60); v64 /= 60;
hour = (unsigned)(v64 % 24); v64 /= 24;
v = (UInt32)v64;
const int PERIOD_4 = (4 * 365 + 1);
const int PERIOD_100 = (PERIOD_4 * 25 - 1);
const int PERIOD_400 = (PERIOD_100 * 4 + 1);
year = (unsigned)(1601 + v / PERIOD_400 * 400);
v %= PERIOD_400;
t = v / PERIOD_100; if (t == 4) t = 3; year += t * 100; v -= t * PERIOD_100;
t = v / PERIOD_4; if (t == 25) t = 24; year += t * 4; v -= t * PERIOD_4;
t = v / 365; if (t == 4) t = 3; year += t; v -= t * 365;
if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)){
ms[1] = 29;
}
for (mon = 1; mon <= 12; mon++){
unsigned s = ms[mon - 1];
if (v < s)
break;
v -= s;
}
day = (unsigned)v + 1;
struct tm outTime;
memset(&outTime, 0, sizeof(outTime));
outTime.tm_sec = sec;
outTime.tm_min = min;
outTime.tm_hour = hour;
outTime.tm_mday = day;
outTime.tm_mon = mon;
/ * tm_year The number of years since 1900. * /
outTime.tm_year = year - 1900;
outTime.tm_isdst = -1;
return mktime(&outTime);
*/
}
}
return 0;
}
virtual ~LzmaContainer(){
SzArEx_Free(&database, &allocator);
File_Close(&archiveStream.file);
}
string path;
Path::AbsolutePath start;
CFileInStream archiveStream;
CLookToRead lookStream;
CSzArEx database;
ISzAlloc allocator;
ISzAlloc allocatorTemporary;
/* Map the filename to its index in the 7z database */
map<string, int> files;
};
/* overlays:
* add x/y.zip
* y.zip contains
* example.txt
* try to read x/example.txt, goto y.zip
* or y.zip could contain y/
*
* z.zip contains
* example.txt
*
* how can you prevent two zip files from providing the same files?
*/
class ZipContainer{
public:
ZipContainer(const string & path, const Filesystem::AbsolutePath & start):
path(path),
start(start),
locked(false){
zipFile = unzOpen(path.c_str());
if (zipFile == NULL){
throw Exception(__FILE__, __LINE__, "Could not open zip file");
}
if (unzGoToFirstFile(zipFile) != UNZ_OK){
throw Exception(__FILE__, __LINE__, "Could not get to first file");
}
do{
char filename[1024];
unz_file_info fileInfo;
unzGetCurrentFileInfo(zipFile, &fileInfo, filename, sizeof(filename), NULL, 0, NULL, 0);
files.push_back(string(filename));
} while (unzGoToNextFile(zipFile) != UNZ_END_OF_LIST_OF_FILE);
/*
zlib_filefunc64_32_def functions;
functions.open_file_func = __real_open;
functions.tell_file_func = __real_lseek;
functions.seek_file_func = __real_lseek;
*/
/*
unzFile zip = unzOpen(path.c_str());
unz_global_info info;
if (unzGetGlobalInfo(zip, &info) == UNZ_OK){
Global::debug(0) << "Entries: " << info.number_entry << std::endl;
}
if (unzGoToFirstFile(zip) != UNZ_OK){
throw Exception(__FILE__, __LINE__, "Could not get to first file");
}
do{
char filename[1024];
unz_file_info fileInfo;
unzGetCurrentFileInfo(zip, &fileInfo, filename, sizeof(filename), NULL, 0, NULL, 0);
Global::debug(0) << "Got file " << filename << std::endl;
Global::debug(0) << " Compressed " << fileInfo.compressed_size << " uncompressed " << fileInfo.uncompressed_size << std::endl;
} while (unzGoToNextFile(zip) != UNZ_END_OF_LIST_OF_FILE);
unzClose(zip);
*/
}
~ZipContainer(){
if (zipFile != NULL){
unzClose(zipFile);
}
}
string getPath() const {
return path;
}
string getMount() const {
return start.path();
}
long modificationTime(){
char filename[1024];
unz_file_info fileInfo;
unzGetCurrentFileInfo(zipFile, &fileInfo, filename, sizeof(filename), NULL, 0, NULL, 0);
struct tm outTime;
memset(&outTime, 0, sizeof(outTime));
outTime.tm_sec = fileInfo.tmu_date.tm_sec;
outTime.tm_min = fileInfo.tmu_date.tm_min;
outTime.tm_hour = fileInfo.tmu_date.tm_hour;
outTime.tm_mday = fileInfo.tmu_date.tm_mday;
outTime.tm_mon = fileInfo.tmu_date.tm_mon;
/* tm_year The number of years since 1900. */
outTime.tm_year = fileInfo.tmu_date.tm_year - 1900;
outTime.tm_isdst = -1;
return mktime(&outTime);
}
void findFile(const Path::AbsolutePath & file){
Path::RelativePath find(file.remove(start));
if (unzLocateFile(zipFile, find.path().c_str(), 2) != UNZ_OK){
Global::debug(0) << "Could not find " << find.path() << std::endl;
} else {
Global::debug(1) << "Found " << find.path() << " in zip file " << path << std::endl;
}
}
int currentFileSize(){
char filename[1024];
unz_file_info fileInfo;
unzGetCurrentFileInfo(zipFile, &fileInfo, filename, sizeof(filename), NULL, 0, NULL, 0);
return fileInfo.uncompressed_size;
}
int read(char * buffer, int size){
// Global::debug(0) << "offset before read " << unzGetOffset(zipFile) << " tell " << unztell(zipFile) << std::endl;
int got = unzReadCurrentFile(zipFile, buffer, size);
// Global::debug(0) << "offset after read " << unzGetOffset(zipFile) << " tell " << unztell(zipFile) << std::endl;
if (got <= 0){
throw Exception(__FILE__, __LINE__, "Could not read bytes from zip");
}
return got;
}
long tell(){
return unztell(zipFile);
}
void open(const Path::AbsolutePath & file){
if (locked){
std::ostringstream out;
char filename[1024];
unz_file_info fileInfo;
unzGetCurrentFileInfo(zipFile, &fileInfo, filename, sizeof(filename), NULL, 0, NULL, 0);
out << "Could not open zip file entry " << file.path() << " because a zip file is already open: " << filename;
throw Exception(__FILE__, __LINE__, out.str());
}
findFile(file);
if (unzOpenCurrentFile(zipFile) != UNZ_OK){
std::ostringstream out;
out << "Could not open zip file entry " << file.path();
throw Exception(__FILE__, __LINE__, out.str());
}
locked = true;
}
void close(){
unzCloseCurrentFile(zipFile);
locked = false;
}
vector<string> getFiles() const {
return files;
}
protected:
/* Path to the zip file itself */
const string path;
/* Where overlay starts */
const Path::AbsolutePath start;
unzFile zipFile;
vector<string> files;
/* Only one file can be opened at a time, so if another file is opened
* while locked=true then throw an error
*/
bool locked;
};
class ZipFile: public File {
public:
ZipFile(const Path::AbsolutePath & path, const Util::ReferenceCount<ZipContainer> & zip):
path(path),
zip(zip),
atEof(false),
position(0){
zip->open(path);
}
virtual ~ZipFile(){
zip->close();
}
long getModificationTime(){
return zip->modificationTime();
}
Token * location(){
Token * head = new Token();
*head << "container";
/* container zipfile mount-point file-inside-zip */
*head << zip->getPath();
*head << zip->getMount();
*head << path.path();
return head;
}
off_t seek(off_t where, int whence){
/* It seems that minizip is not capable of seeking in a specific file in a zip
* container so we have to re-open the file and read `position' bytes to
* emulate the seek behavior.
*/
switch (whence){
case SEEK_SET: {
if (where >= position){
/* Read bytes until we hit the offset */
position += skipBytes(where - position);
} else {
zip->close();
zip->open(path);
/* re-open file and read where bytes */
position = skipBytes(where);
}
break;
}
case SEEK_CUR: {
position += skipBytes(where);
break;
}
case SEEK_END: {
return seek(getSize() + where, SEEK_SET);
break;
}
}
if (position > getSize()){
position = getSize();
}
atEof = position == getSize();
return position;
}
bool eof(){
return atEof;
}
long tell(){
return zip->tell();
}
void reset(){
/* TODO or nothing..? */
}
bool canStream(){
return false;
}
bool good(){
/* FIXME */
return true;
}
File & operator>>(unsigned char & c){
readLine((char*) &c, 1);
return *this;
}
int getSize(){
return zip->currentFileSize();
}
int readLine(char * output, int size){
try{
int read = zip->read(output, size);
position += read;
return read;
} catch (const Exception & nomore){
atEof = true;
return 0;
}
}
protected:
/* skips `bytes'.
* returns number of bytes skipped (may be less than bytes)
*/
int skipBytes(int bytes){
char dummy[1024];
int total = 0;
while (bytes > 0){
/* Calls the zip file directly here so we don't mess up position */
int read = zip->read(dummy, bytes > (int) sizeof(dummy) ? (int) sizeof(dummy) : bytes);
total += read;
if (read == 0){
return total;
}
bytes -= read;
}
return total;
}
const Path::AbsolutePath path;
const Util::ReferenceCount<ZipContainer> zip;
bool atEof;
/* keep track of bytes read so we can seek easier */
int position;
};
Descriptor::Descriptor(){
}
Descriptor::~Descriptor(){
}
class ZipDescriptor: public Descriptor {
public:
ZipDescriptor(const Path::AbsolutePath & path, const Util::ReferenceCount<ZipContainer> & container):
path(path),
container(container){
}
Path::AbsolutePath path;
Util::ReferenceCount<ZipContainer> container;
using Descriptor::open;
virtual Util::ReferenceCount<File> open(File::Access mode){
return Util::ReferenceCount<File>(new ZipFile(path, container));
}
virtual ~ZipDescriptor(){
}
};
/* Extracts an entire file from a 7z archive into memory */
class LzmaFile: public File {
public:
LzmaFile(const Path::AbsolutePath & path, const Util::ReferenceCount<LzmaContainer> & container):
path(path),
container(container),
position(0),
memory(NULL),
size(0){
container->readFile(path, &memory, &size);
}
virtual ~LzmaFile(){
SzFree(NULL, memory);
}
bool eof(){
return position >= (int) size;
}
virtual bool good(){
return memory != NULL;
}
virtual int getSize(){
return size;
}
virtual bool canStream(){
return true;
}
virtual void reset(){
/* nothing */
}
virtual long tell(){
return position;
}
virtual Token * location(){
Token * head = new Token();
*head << "container";
/* container zipfile mount-point file-inside-zip */
*head << container->getPath();
*head << container->getMount();
*head << path.path();
return head;
}
virtual long getModificationTime(){
return container->getModificationTime(path);
}
virtual off_t seek(off_t position, int whence){
switch (whence){
case SEEK_SET: this->position = position; break;
case SEEK_CUR: this->position += position; break;
case SEEK_END: this->position = this->size + position; break;
}
if (this->position < 0){
this->position = 0;
}
if (this->position > (int) size){
this->position = size;
}
return this->position;
}
virtual File & operator>>(unsigned char & out){
if (this->position < (int) size){
out = memory[position];
position += 1;
}
return *this;
}
virtual int readLine(char * output, int size){
if (size > (int) (this->size - this->position)){
size = this->size - this->position;
}
memmove(output, memory + this->position, size);
this->position += size;
return size;
}
protected:
const Path::AbsolutePath path;
const Util::ReferenceCount<LzmaContainer> container;
/* keep track of bytes read so we can seek easier */
int position;
unsigned char * memory;
size_t size;
};
class LzmaDescriptor: public Descriptor {
public:
LzmaDescriptor(const Path::AbsolutePath & path, const Util::ReferenceCount<LzmaContainer> & container):
path(path),
container(container){
}
Path::AbsolutePath path;
Util::ReferenceCount<LzmaContainer> container;
using Descriptor::open;
virtual Util::ReferenceCount<File> open(File::Access mode){
return Util::ReferenceCount<File>(new LzmaFile(path, container));
}
virtual ~LzmaDescriptor(){
}
};
/* Keep this updated with all the supported container types */
bool isContainer(const Path::AbsolutePath & path){
return path.getExtension() == "zip" ||
path.getExtension() == "7z";
}
vector<std::string> containerTypes(){
vector<string> types;
types.push_back("zip");
types.push_back("7z");
return types;
}
bool System::isDirectory(const AbsolutePath & path){
return virtualDirectory.isDirectory(path) || systemIsDirectory(path);
}
bool System::exists(const AbsolutePath & path){
return virtualDirectory.exists(path) || systemExists(path);
}
void System::overlayFile(const AbsolutePath & where, Util::ReferenceCount<LzmaContainer> container){
virtualDirectory.addFile(where, Util::ReferenceCount<LzmaDescriptor>(new LzmaDescriptor(where, container)));
}
void System::overlayFile(const AbsolutePath & where, Util::ReferenceCount<ZipContainer> zip){
virtualDirectory.addFile(where, Util::ReferenceCount<ZipDescriptor>(new ZipDescriptor(where, zip)));
}
void System::unoverlayFile(const AbsolutePath & where){
virtualDirectory.removeFile(where);
}
vector<string> System::containerFileList(const AbsolutePath & container){
Util::ReferenceCount<ZipContainer> zip(new ZipContainer(container.path(), Filesystem::AbsolutePath()));
return zip->getFiles();
}
static bool isZipFile(const Filesystem::AbsolutePath & path){
return path.path().find(".zip") != string::npos;
}
static bool is7zFile(const Filesystem::AbsolutePath & path){
return path.path().find(".7z") != string::npos;
}
template <class Container>
static void addOverlayFiles(System & system, const Filesystem::AbsolutePath & where, const Util::ReferenceCount<Container> & container){
vector<string> files = container->getFiles();
for (vector<string>::const_iterator it = files.begin(); it != files.end(); it++){
string path = *it;
Global::debug(1) << "Add overlay to " << where.join(Filesystem::RelativePath(path)).path() << std::endl;
system.overlayFile(where.join(Filesystem::RelativePath(path)), container);
}
}
void System::addOverlay(const AbsolutePath & container, const AbsolutePath & where){
if (isZipFile(container)){
Global::debug(1) << "Opening zip file " << container.path() << std::endl;
addOverlayFiles(*this, where, Util::ReferenceCount<ZipContainer>(new ZipContainer(container.path(), where)));
} else if (is7zFile(container)){
addOverlayFiles(*this, where, Util::ReferenceCount<LzmaContainer>(new LzmaContainer(container.path(), where)));
}
}
void System::removeOverlay(const AbsolutePath & container, const AbsolutePath & where){
Util::ReferenceCount<ZipContainer> zip(new ZipContainer(container.path(), where));
vector<string> files = zip->getFiles();
for (vector<string>::const_iterator it = files.begin(); it != files.end(); it++){
string path = *it;
Global::debug(1) << "Remove overlay from " << where.join(Filesystem::RelativePath(path)).path() << std::endl;
unoverlayFile(where.join(Filesystem::RelativePath(path)));
}
}
Util::ReferenceCount<File> System::open(const AbsolutePath & path, File::Access mode){
Util::ReferenceCount<Descriptor> descriptor = virtualDirectory.lookup(path);
if (descriptor != NULL){
return descriptor->open(mode);
} else {
return Util::ReferenceCount<File>(new NormalFile(path, mode));
}
}
std::string readFile(const Path::AbsolutePath & path){
Util::ReferenceCount<Storage::File> file = Storage::instance().open(path);
if (file != NULL){
char * data = new char[file->getSize() + 1];
file->readLine(data, file->getSize());
data[file->getSize()] = 0;
std::string out(data);
delete[] data;
return out;
}
std::ostringstream out;
out << "Could not open file " << path.path();
throw Exception(__FILE__, __LINE__, out.str());
}
bool hasInstance(){
// return true;
return self != NULL;
}
System & setInstance(const Util::ReferenceCount<System> & what){
self = what;
return *self;
}
/* will read upto 'length' bytes unless a null byte is seen first */
string EndianReader::readStringX(int length){
ostringstream out;
uint8_t letter = readByte1();
while (letter != 0 && length > 0){
out << letter;
letter = readByte1();
length -= 1;
}
return out.str();
}
/* unconditionally reads 'length' bytes */
std::string EndianReader::readString2(int length){
ostringstream out;
vector<uint8_t> bytes = readBytes(length);
for (vector<uint8_t>::iterator it = bytes.begin(); it != bytes.end(); it++){
char byte = *it;
if (byte == 0){
break;
}
out << *it;
}
return out.str();
}
void EndianReader::seekEnd(streamoff where){
internal->seekEnd(where);
}
void EndianReader::seek(streampos where){
internal->seek(where);
}
int EndianReader::position(){
return internal->tell();
}
void EndianReader::readBytes(uint8_t * out, int length){
internal->read((char*) out, length);
}
vector<uint8_t> EndianReader::readBytes(int length){
vector<uint8_t> bytes;
for (int i = 0; i < length; i++){
uint8_t byte = 0;
internal->read((char*) &byte, 1);
if (internal->eof()){
throw Eof();
} else {
}
bytes.push_back(byte);
}
return bytes;
}
EndianReader::Internal::Internal(){
}
EndianReader::Internal::~Internal(){
}
bool EndianReader::StreamInternal::eof(){
return stream.eof();
}
int EndianReader::StreamInternal::read(char * data, int length){
stream.read(data, length);
return stream.gcount();
}
void EndianReader::StreamInternal::seekEnd(std::streamoff where){
stream.seekg(where, ios::end);
}
void EndianReader::StreamInternal::seek(std::streamoff where){
stream.seekg(where);
}
int EndianReader::StreamInternal::tell(){
return stream.tellg();
}
EndianReader::StreamInternal::~StreamInternal(){
}
bool EndianReader::FileInternal::eof(){
return file->eof();
}
void EndianReader::FileInternal::seekEnd(std::streamoff where){
file->seek(where, SEEK_END);
}
void EndianReader::FileInternal::seek(std::streamoff where){
file->seek(where, SEEK_SET);
}
int EndianReader::FileInternal::read(char * data, int length){
return file->readLine(data, length);
}
int EndianReader::FileInternal::tell(){
return file->tell();
}
EndianReader::FileInternal::~FileInternal(){
}
}
Filesystem::Filesystem(const AbsolutePath & path):
dataPath(path){
}
#ifdef _WIN32
Filesystem::AbsolutePath Filesystem::userDirectory(){
ostringstream str;
char path[MAX_PATH];
SHGetSpecialFolderPathA(0, path, CSIDL_APPDATA, false);
str << path << "/paintown/";
return Filesystem::AbsolutePath(str.str());
}
Filesystem::AbsolutePath Filesystem::configFile(){
ostringstream str;
char path[MAX_PATH];
SHGetSpecialFolderPathA(0, path, CSIDL_APPDATA, false);
str << path << "/paintown_configuration.txt";
return Filesystem::AbsolutePath(str.str());
}
#else
Filesystem::AbsolutePath Filesystem::configFile(){
ostringstream str;
/* what if HOME isn't set? */
str << getenv("HOME") << "/.paintownrc";
return Filesystem::AbsolutePath(str.str());
}
Filesystem::AbsolutePath Filesystem::userDirectory(){
ostringstream str;
char * home = getenv("HOME");
if (home == NULL){
str << "/tmp/paintown";
} else {
str << home << "/.paintown/";
}
return Filesystem::AbsolutePath(str.str());
}
#endif
Filesystem::AbsolutePath Filesystem::lookup(const RelativePath path){
vector<Filesystem::AbsolutePath> places;
#define push(x) try{ places.push_back(x); } catch (const Storage::IllegalPath & fail){ }
push(dataPath.join(path));
push(userDirectory().join(path));
push(Filesystem::AbsolutePath(path.path()));
#undef push
/* start error stuff early */
ostringstream out;
out << "Cannot find " << path.path() << ". I looked in ";
bool first = true;
for (vector<Filesystem::AbsolutePath>::iterator it = places.begin(); it != places.end(); it++){
const Filesystem::AbsolutePath & final = *it;
if (exists(final)){
return final;
}
if (!first){
out << ", ";
} else {
first = false;
}
out << "'" << final.path() << "'";
}
// out << "Cannot find " << path.path() << ". I looked in '" << dataPath.join(path).path() << "', '" << userDirectory().join(path).path() << "', and '" << path.path() << "'";
throw NotFound(__FILE__, __LINE__, out.str());
#if 0
/* first try the main data directory */
Filesystem::AbsolutePath final = dataPath.join(path);
if (::System::readable(final.path())){
return final;
}
/* then try the user directory, like ~/.paintown */
final = userDirectory().join(path);
if (::System::readable(final.path())){
return final;
}
/* then just look in the cwd */
if (::System::readable(path.path())){
return Filesystem::AbsolutePath(path.path());
}
ostringstream out;
out << "Cannot find " << path.path() << ". I looked in '" << dataPath.join(path).path() << "', '" << userDirectory().join(path).path() << "', and '" << path.path() << "'";
throw NotFound(__FILE__, __LINE__, out.str());
#endif
}
Filesystem::AbsolutePath Filesystem::lookupInsensitive(const Filesystem::AbsolutePath & directory, const Filesystem::RelativePath & path){
if (path.path() == ""){
throw NotFound(__FILE__, __LINE__, "Given empty path to lookup");
}
if (path.path() == "."){
return directory;
}
if (path.path() == ".."){
return directory.getDirectory();
}
if (path.isFile()){
vector<AbsolutePath> all = getFiles(directory, "*", true);
for (vector<AbsolutePath>::iterator it = all.begin(); it != all.end(); it++){
AbsolutePath & check = *it;
if (InsensitivePath(check.getFilename()) == path){
return check;
}
}
ostringstream out;
out << "Cannot find " << path.path() << " in " << directory.path();
throw NotFound(__FILE__, __LINE__, out.str());
} else {
return lookupInsensitive(lookupInsensitive(directory, path.firstDirectory()), path.removeFirstDirectory());
}
}
vector<Filesystem::AbsolutePath> Filesystem::findDirectoriesIn(const Filesystem::AbsolutePath & path){
vector<Filesystem::AbsolutePath> dirs = virtualDirectory.findDirectories(path, "*", false);
DIR * dir = opendir(path.path().c_str());
if (dir == NULL){
return dirs;
}
struct dirent * entry = readdir(dir);
while (entry != NULL){
if (string(entry->d_name) != "." && string(entry->d_name) != ".."){
string total = path.path() + "/" + entry->d_name;
if (::System::isDirectory(total)){
dirs.push_back(AbsolutePath(total));
}
}
entry = readdir(dir);
}
closedir(dir);
return dirs;
}
vector<Filesystem::AbsolutePath> Filesystem::findDirectories(const RelativePath & path){
typedef vector<AbsolutePath> Paths;
Paths dirs;
Paths main_dirs = findDirectoriesIn(dataPath.join(path));
Paths user_dirs = findDirectoriesIn(userDirectory().join(path));
Paths here_dirs = findDirectoriesIn(Filesystem::AbsolutePath(path.path()));
dirs.insert(dirs.end(), main_dirs.begin(), main_dirs.end());
dirs.insert(dirs.end(), user_dirs.begin(), user_dirs.end());
dirs.insert(dirs.end(), here_dirs.begin(), here_dirs.end());
return dirs;
}
/* a/b/c/ -> a/b/c */
static string removeTrailingSlash(string str){
while (str.size() > 0 && str[str.size() - 1] == '/'){
str = str.erase(str.size() - 1);
}
return str;
}
vector<Filesystem::AbsolutePath> Filesystem::getFiles(const AbsolutePath & dataPath, const string & find, bool caseInsensitive){
#ifdef USE_ALLEGRO
struct al_ffblk info;
vector<AbsolutePath> files;
if (al_findfirst((dataPath.path() + "/" + find).c_str(), &info, FA_ALL ) != 0){
return files;
}
files.push_back(AbsolutePath(dataPath.path() + "/" + string(info.name)));
while ( al_findnext( &info ) == 0 ){
files.push_back(AbsolutePath(dataPath.path() + "/" + string(info.name)));
}
al_findclose( &info );
return files;
#else
Util::Thread::ScopedLock scoped(lock);
vector<AbsolutePath> files;
vector<AbsolutePath> more = virtualDirectory.findFiles(dataPath, find, caseInsensitive);
files.insert(files.end(), more.begin(), more.end());
DIRST sflEntry;
// bool ok = open_dir(&sflEntry, removeTrailingSlash(dataPath.path()).c_str());
bool ok = open_dir(&sflEntry, dataPath.path().c_str());
if (!ok){
/* sfldir.c claims that you have to call close_dir even if
* open_dir fails but close_dir will do ASSERT(dir->dir_handle) which is sometimes
* NULL when open_dir fails so we first check if the dir_handle is non-NULL and
* then call close_dir.
*/
if (sflEntry._dir_handle != NULL){
close_dir(&sflEntry);
}
return files;
}
while (ok){
if (file_matches(sflEntry.file_name, find.c_str())){
files.push_back(AbsolutePath(dataPath.path() + "/" + string(sflEntry.file_name)));
}
ok = read_dir(&sflEntry);
}
close_dir(&sflEntry);
/*
for (map<AbsolutePath, Util::ReferenceCount<Storage::ZipContainer> >::iterator it = overlays.begin(); it != overlays.end(); it++){
AbsolutePath path = it->first;
if (it->second == NULL){
continue;
}
// Global::debug(0) << "Check " << path.path() << " (" << path.getDirectory().path() << ") vs directory " << dataPath.path() << " wildcard " << find << " to " << path.getFilename().path() << std::endl;
if (path.getDirectory() == dataPath &&
file_matches(path.getLastComponent().c_str(), find.c_str())){
// Global::debug(0) << "Found overlay " << path.path() << " in " << dataPath.path() << " for wildcard " << find << std::endl;
files.push_back(path);
}
}
*/
// Global::debug(0) << "Warning: Filesystem::getFiles() is not implemented yet for SDL" << endl;
return files;
#endif
}
std::vector<Filesystem::AbsolutePath> Filesystem::getFiles(const RelativePath & path, const RelativePath & find, bool caseInsensitive){
vector<AbsolutePath> directories;
directories.push_back(dataPath.join(path));
directories.push_back(userDirectory().join(path));
directories.push_back(Filesystem::AbsolutePath(path.path()));
vector<AbsolutePath> files;
for (vector<AbsolutePath>::iterator it = directories.begin(); it != directories.end(); it++){
Global::debug(0) << "Search for " << find.path() << " in " << it->path() << std::endl;
vector<AbsolutePath> found = getFiles(*it, find, caseInsensitive);
files.insert(files.end(), found.begin(), found.end());
}
return files;
}
template <class X>
static void append(vector<X> & destination, const vector<X> & source){
/*
for (typename vector<X>::const_iterator it = source.begin(); it != source.end(); it++){
destination.push_back(*it);
}
*/
copy(source.begin(), source.end(), back_insert_iterator<vector<X> >(destination));
}
vector<Filesystem::AbsolutePath> Filesystem::getAllDirectories(const AbsolutePath & path){
vector<AbsolutePath> all = findDirectoriesIn(path);
vector<AbsolutePath> final;
append(final, all);
for (vector<AbsolutePath>::iterator it = all.begin(); it != all.end(); it++){
vector<AbsolutePath> more = getAllDirectories(*it);
append(final, more);
}
return final;
}
vector<Filesystem::AbsolutePath> Filesystem::getFilesRecursive(const AbsolutePath & dataPath, const string & find, bool caseInsensitive){
if (!exists(dataPath)){
return vector<AbsolutePath>();
}
vector<AbsolutePath> directories = getAllDirectories(dataPath);
directories.push_back(dataPath);
vector<AbsolutePath> files;
for (vector<AbsolutePath>::iterator it = directories.begin(); it != directories.end(); it++){
vector<AbsolutePath> found = getFiles(*it, find, caseInsensitive);
append(files, found);
}
return files;
}
/*
std::string find(const std::string path){
if (path.length() == 0){
throw NotFound("No path given");
}
if (path[0] == '/'){
string str(path);
str.erase(0, 1);
string out = lookup(str);
if (System::isDirectory(out)){
return sanitize(out + "/");
}
return sanitize(out);
}
string out = lookup(path);
if (System::isDirectory(out)){
return sanitize(out + "/");
}
return sanitize(out);
}
*/
Filesystem::AbsolutePath Filesystem::find(const RelativePath & path){
if (path.isEmpty()){
throw NotFound(__FILE__, __LINE__, "No path given");
}
AbsolutePath out = lookup(path);
if (::System::isDirectory(out.path())){
return AbsolutePath(out.path() + "/");
}
return AbsolutePath(out.path());
}
Filesystem::AbsolutePath Filesystem::findInsensitive(const RelativePath & path){
try{
/* try sensitive lookup first */
return lookup(path);
} catch (const NotFound & fail){
}
/* get the base directory */
AbsolutePath directory = lookup(path.getDirectory());
return lookupInsensitive(directory, path.getFilename());
}
bool Filesystem::exists(const RelativePath & path){
try{
AbsolutePath absolute = find(path);
return true;
} catch (const NotFound & found){
return false;
}
}
bool Filesystem::systemIsDirectory(const AbsolutePath & path){
return ::System::isDirectory(path.path());
}
bool Filesystem::systemExists(const AbsolutePath & path){
return ::System::readable(path.path());
}
Filesystem::RelativePath Filesystem::cleanse(const AbsolutePath & path){
string str = path.path();
if (str.find(dataPath.path()) == 0){
str.erase(0, dataPath.path().length());
} else if (str.find(userDirectory().path()) == 0){
str.erase(0, userDirectory().path().length());
}
return RelativePath(str);
}
diff --git a/util/file-system.h b/util/file-system.h
index 4232069e..c2242bd4 100644
--- a/util/file-system.h
+++ b/util/file-system.h
@@ -1,605 +1,606 @@
#ifndef _paintown_file_system_h
#define _paintown_file_system_h
#include "exceptions/exception.h"
#include "pointer.h"
#include "thread.h"
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <fstream>
#include <stdint.h>
class Token;
struct stat;
/* path utilities */
namespace Path{
class Path{
public:
const std::string & path() const;
const std::string getExtension() const;
bool isEmpty() const;
virtual ~Path();
protected:
Path();
Path(const std::string & path);
Path(const Path & path);
virtual inline void setPath(const std::string & s){
mypath = s;
}
std::string mypath;
};
class InsensitivePath: public Path {
public:
InsensitivePath(const Path & what);
bool operator==(const Path & path) const;
};
/* relative path should not have the leading data directory on it, just
* the path within the paintown system.
*/
class RelativePath: public Path {
public:
explicit RelativePath();
explicit RelativePath(const std::string & path);
RelativePath(const RelativePath & path);
bool operator<(const RelativePath & path) const;
virtual RelativePath getDirectory() const;
virtual RelativePath getFilename() const;
RelativePath removeFirstDirectory() const;
RelativePath firstDirectory() const;
/* true if there are no directory parts to this path
* foo is a file
* bar/foo is not a file
*/
bool isFile() const;
/* a/ + b/ = a/b/ */
RelativePath join(const RelativePath & path) const;
RelativePath & operator=(const RelativePath & copy);
bool operator==(const RelativePath & path) const;
bool operator!=(const RelativePath & path) const;
};
/* absolute paths should have the entire filesystem path on it */
class AbsolutePath: public Path {
public:
explicit AbsolutePath();
explicit AbsolutePath(const std::string & path);
AbsolutePath(const AbsolutePath & path);
AbsolutePath & operator=(const AbsolutePath & copy);
bool operator<(const AbsolutePath & path) const;
bool operator==(const AbsolutePath & path) const;
bool operator!=(const AbsolutePath & path) const;
/* Remove a given path from the start of this path */
virtual RelativePath remove(const AbsolutePath & path) const;
virtual AbsolutePath getDirectory() const;
virtual AbsolutePath getFilename() const;
AbsolutePath removeFirstDirectory() const;
std::string firstDirectory() const;
bool isFile() const;
/* If the filename is empty then get the name of the directory.
* a/b -> b
* a/b/ -> b
*
* Otherwise (a/b/).getFilename() will be ""
*/
virtual std::string getLastComponent() const;
AbsolutePath join(const RelativePath & path) const;
};
std::string invertSlashes(std::string str);
std::string sanitize(std::string path);
/* remove extension. foo.txt -> foo */
std::string removeExtension(const std::string & str);
/* Replace the extension on `input' with `extension'. Don't put the . on the extension,
* so call it as replaceExtension(path, "foo")
* instead of replaceExtension(path, ".foo")
*/
AbsolutePath replaceExtension(const AbsolutePath & input, const std::string & extension);
/* basename, just get the filename and remove the directory part */
std::string stripDir(const std::string & str);
/* dirname, just get the directory and remove the filename part */
std::string stripFilename(const std::string & str);
}
namespace Storage{
/* sorry for the crappy abbreviation, but can't collide with the
* Exception class here
*/
namespace Exc = ::Exception;
class Exception: public Exc::Base {
public:
Exception(const std::string & where, int line, const std::string & file);
Exception(const std::string & where, int line, const Exc::Base & nested, const std::string & file);
Exception(const Exception & copy);
virtual ~Exception() throw ();
virtual void throwSelf() const {
throw *this;
}
protected:
virtual const std::string getReason() const;
virtual Exc::Base * copy() const {
return new Exception(*this);
}
private:
std::string reason;
};
class NotFound: public Exception {
public:
NotFound(const std::string & where, int line, const std::string & file);
NotFound(const std::string & where, int line, const Exc::Base & nested, const std::string & file);
virtual ~NotFound() throw();
NotFound(const NotFound & copy);
virtual void throwSelf() const {
throw *this;
}
protected:
virtual Exc::Base * copy() const {
return new NotFound(*this);
}
};
class IllegalPath: public Exception {
public:
IllegalPath(const std::string & where, int line, const std::string & file);
IllegalPath(const std::string & where, int line, const Exc::Base & nested, const std::string & file);
virtual ~IllegalPath() throw();
IllegalPath(const IllegalPath & copy);
virtual void throwSelf() const {
throw *this;
}
protected:
virtual Exc::Base * copy() const {
return new IllegalPath(*this);
}
};
class Eof: public std::exception {
public:
Eof(){
}
virtual ~Eof() throw (){
}
};
/* Abstraction for files. Should be used instead of FILE, ifstream, SDL_RWOps, anything else */
class File{
public:
enum Access{
Read,
Write,
ReadWrite
};
File();
virtual ~File();
/* Returns the number of bytes read */
virtual int readLine(char * output, int size) = 0;
/* Mostly for normal files, reset their error flags so
* we can seek after eof
*/
virtual void reset() = 0;
/* true if the underlying object can be streamed. generally
* this should be true if the object can be kept around while other files
* of the same type can be opened.
* zip files aren't streamable because only one zip entry can be
* open at a time so it must be closed as soon as possible.
*/
virtual bool canStream() = 0;
virtual long getModificationTime() = 0;
/* seek to an absolute position */
virtual off_t seek(off_t position, int whence) = 0;
virtual int getSize() = 0;
virtual long tell() = 0;
/* Returns a token that represents the path. For normal files it will just be
* a single string containing the path -- "data/x/y/z.txt"
* For zip files it will be (container "x/y/z.zip" "mount/point" "a.txt")
*/
virtual Token * location() = 0;
/* if the file is at eof and can't read anymore */
virtual bool eof() = 0;
/* if the file can still be read */
virtual bool good() = 0;
/* read one unsigned byte */
virtual File & operator>>(unsigned char &) = 0;
};
class EndianReader{
public:
EndianReader(const Util::ReferenceCount<Storage::File> & file){
internal = new FileInternal(file);
}
EndianReader(std::ifstream & stream){
internal = new StreamInternal(stream);
}
class Internal{
public:
Internal();
virtual bool eof() = 0;
virtual int read(char * data, int length) = 0;
virtual void seekEnd(std::streamoff where) = 0;
virtual void seek(std::streamoff where) = 0;
virtual int tell() = 0;
virtual ~Internal();
};
class StreamInternal: public Internal {
public:
StreamInternal(std::ifstream & stream):
stream(stream){
}
virtual bool eof();
virtual int read(char * data, int length);
virtual void seekEnd(std::streamoff where);
virtual void seek(std::streamoff where);
virtual int tell();
virtual ~StreamInternal();
/* Yes, use a reference here */
std::ifstream & stream;
};
class FileInternal: public Internal {
public:
FileInternal(const Util::ReferenceCount<Storage::File> & file):
file(file){
}
virtual bool eof();
virtual void seekEnd(std::streamoff where);
virtual void seek(std::streamoff where);
virtual int read(char * data, int length);
virtual int tell();
virtual ~FileInternal();
Util::ReferenceCount<Storage::File> file;
};
virtual ~EndianReader(){
}
virtual int8_t readByte1(){
return convert(readBytes(sizeof(int8_t)));
}
virtual int16_t readByte2(){
return convert(readBytes(sizeof(int16_t)));
}
virtual int32_t readByte4(){
return convert(readBytes(sizeof(int32_t)));
}
virtual std::string readStringX(int length);
virtual std::string readString2(int length);
virtual void readBytes(uint8_t * out, int length);
virtual void seekEnd(std::streamoff where);
virtual void seek(std::streampos where);
virtual int position();
protected:
virtual int32_t convert(const std::vector<uint8_t> & bytes) = 0;
std::vector<uint8_t> readBytes(int length);
Util::ReferenceCount<Internal> internal;
};
/* combines bytes b0 b1 b2 b3 as b0 + b1*2^8 + b2*2^16 + b3*2^24 */
class LittleEndianReader: public EndianReader {
public:
LittleEndianReader(const Util::ReferenceCount<Storage::File> & file):
EndianReader(file){
}
LittleEndianReader(std::ifstream & stream):
EndianReader(stream){
}
protected:
virtual int32_t convert(const std::vector<uint8_t> & bytes){
uint32_t out = 0;
for (std::vector<uint8_t>::const_reverse_iterator it = bytes.rbegin(); it != bytes.rend(); it++){
out = (out << 8) + *it;
}
return out;
}
};
/* combines bytes b0 b1 b2 b3 as b0*2^24 + b1*2^16 + b2*2^8 + b3 */
class BigEndianReader: public EndianReader {
public:
BigEndianReader(const Util::ReferenceCount<Storage::File> & file):
EndianReader(file){
}
BigEndianReader(std::ifstream & stream):
EndianReader(stream){
}
protected:
virtual int32_t convert(const std::vector<uint8_t> & bytes){
uint32_t out = 0;
for (std::vector<uint8_t>::const_iterator it = bytes.begin(); it != bytes.end(); it++){
out = (out << 8) + *it;
}
return out;
}
};
class LzmaContainer;
class ZipContainer;
class StringFile: public File {
public:
StringFile(const std::string & start);
virtual int readLine(char * output, int size);
virtual bool eof();
virtual bool good();
virtual int getSize();
virtual long getModificationTime();
virtual bool canStream();
virtual void reset();
virtual long tell();
virtual Token * location();
virtual off_t seek(off_t position, int whence);
virtual File & operator>>(unsigned char &);
virtual ~StringFile();
protected:
std::string data;
std::istringstream stream;
};
/* Contains information about an abstract File. */
class Descriptor{
public:
Descriptor();
virtual Util::ReferenceCount<File> open(File::Access mode) = 0;
virtual ~Descriptor();
};
class Traverser;
class Directory{
public:
Directory();
virtual ~Directory();
/* Finds any path in the given directory that matches find, files and directories */
std::vector<Path::AbsolutePath> findFiles(const Path::AbsolutePath & dataPath, const std::string & find, bool caseInsensitive);
/* Only finds directories */
std::vector<Path::AbsolutePath> findDirectories(const Path::AbsolutePath & dataPath, const std::string & find, bool caseInsensitive);
void addFile(const Path::AbsolutePath & path, const Util::ReferenceCount<Descriptor> & file);
void removeFile(const Path::AbsolutePath & path);
/* FIXME: maybe filenames() should just return files? */
/* Files + directories */
std::vector<std::string> filenames() const;
/* Just directories */
std::vector<std::string> directoryNames() const;
/* Finds a file with the given path. Will not find directories.
* Might return NULL if the path can't be found
*/
Util::ReferenceCount<Descriptor> lookup(const Path::AbsolutePath & path);
/* true if the path (either file or directory) exists */
bool exists(const Path::AbsolutePath & path);
/* true if path is a directory */
bool isDirectory(const Path::AbsolutePath & path);
protected:
void doTraverse(const Path::AbsolutePath & path, Traverser & traverser);
void traverse(const Path::AbsolutePath & path, Traverser & traverser);
// Util::ReferenceCount<File> doLookup(const Path::AbsolutePath & path);
std::map<std::string, Util::ReferenceCount<Directory> > directories;
std::map<std::string, Util::ReferenceCount<Descriptor> > files;
Util::Thread::LockObject lock;
};
class System{
public:
System();
virtual ~System();
typedef Path::AbsolutePath AbsolutePath;
typedef Path::RelativePath RelativePath;
virtual AbsolutePath find(const RelativePath & path) = 0;
virtual RelativePath cleanse(const AbsolutePath & path) = 0;
virtual bool exists(const RelativePath & path) = 0;
virtual bool exists(const AbsolutePath & path);
virtual bool isDirectory(const AbsolutePath & path);
virtual std::vector<AbsolutePath> getFilesRecursive(const AbsolutePath & dataPath, const std::string & find, bool caseInsensitive = false) = 0;
/* Finds all supported container files. Currently
* .zip
*/
virtual std::vector<AbsolutePath> getContainerFilesRecursive(const AbsolutePath & dataPath);
/* Gets container files only in the specified directory */
virtual std::vector<AbsolutePath> getContainerFiles(const AbsolutePath & dataPath);
/* Gets container files in <user>/path ./path and <data>/path */
virtual std::vector<AbsolutePath> getContainerFiles(const RelativePath & path);
/* Given a path with no extension, find a container file that is <name>.zip or
* <name>.7z or whatever exists.
* So if dataPath is "mugen/foo" this function will look for
* mugen/foo.zip
* mugen/foo.7z
* mugen/foo.rar
* ...
*
* and return the first one it finds
*/
virtual AbsolutePath findContainer(const RelativePath & dataPath);
/* search for a pattern of a single file within a directory */
virtual std::vector<AbsolutePath> getFiles(const AbsolutePath & dataPath, const std::string & find, bool caseInsensitive = false) = 0;
/* Container should be a path to a zip file */
virtual void addOverlay(const AbsolutePath & container, const AbsolutePath & where);
virtual void removeOverlay(const AbsolutePath & container, const AbsolutePath & where);
virtual std::vector<std::string> containerFileList(const AbsolutePath & container);
/* search for some path which may contain wildcards in a directory */
virtual std::vector<AbsolutePath> getFiles(const AbsolutePath & dataPath, const RelativePath & find, bool caseInsensitive);
/* Gets all the files in all directories matched by the relative path */
virtual std::vector<AbsolutePath> getFiles(const RelativePath & dataPath, const RelativePath & find, bool caseInsensitive) = 0;
virtual AbsolutePath configFile() = 0;
virtual AbsolutePath userDirectory() = 0;
virtual std::vector<AbsolutePath> findDirectories(const RelativePath & path) = 0;
virtual AbsolutePath findInsensitive(const RelativePath & path) = 0;
virtual AbsolutePath lookupInsensitive(const AbsolutePath & directory, const RelativePath & path) = 0;
virtual Util::ReferenceCount<File> open(const AbsolutePath & path, File::Access mode = File::Read);
/* Should be protected but needs to be public so we can use it in template methods
* in the implementation file.
*/
public:
virtual void overlayFile(const AbsolutePath & where, Util::ReferenceCount<ZipContainer> zip);
virtual void overlayFile(const AbsolutePath & where, Util::ReferenceCount<LzmaContainer> container);
- protected:
+
+ /* package: */
virtual bool systemExists(const AbsolutePath & path) = 0;
virtual bool systemIsDirectory(const AbsolutePath & path) = 0;
virtual void unoverlayFile(const AbsolutePath & where);
// std::map<AbsolutePath, Util::ReferenceCount<ZipContainer> > overlays;
Storage::Directory virtualDirectory;
};
System & instance();
bool hasInstance();
System & setInstance(const Util::ReferenceCount<System> & what);
bool isContainer(const Path::AbsolutePath & path);
std::string readFile(const Path::AbsolutePath & path);
std::vector<std::string> containerTypes();
}
/*
* class Filesystem
* class NetworkStorage
* class ZipStorage
*/
class Filesystem: public Storage::System {
public:
Filesystem(const Path::AbsolutePath & dataPath);
typedef Path::AbsolutePath AbsolutePath;
typedef Path::RelativePath RelativePath;
typedef Path::InsensitivePath InsensitivePath;
typedef Storage::Exception Exception;
typedef Storage::NotFound NotFound;
/* given a relative path like sounds/arrow.png, prepend the proper
* data path to it to give data/sounds/arrow.png
*/
AbsolutePath find(const RelativePath & path);
/* like `find' but ignores case */
AbsolutePath findInsensitive(const RelativePath & path);
/* findInsensitive but starts in the given absolute directory path */
AbsolutePath lookupInsensitive(const AbsolutePath & directory, const RelativePath & path);
// void initialize();
/* whether the file exists at all */
using System::exists;
bool exists(const RelativePath & path);
/* remove the data path from a string
* data/sounds/arrow.png -> sounds/arrow.png
*/
RelativePath cleanse(const AbsolutePath & path);
/* returns all the directories starting with the given path.
* will look in the main data directory, the user directory, and
* the current working directory.
*/
std::vector<AbsolutePath> findDirectories(const RelativePath & path);
/* user specific directory to hold persistent data */
AbsolutePath userDirectory();
/* user specific path to store the configuration file */
AbsolutePath configFile();
using Storage::System::getFiles;
/* search a directory for some files matching pattern `find' */
virtual std::vector<AbsolutePath> getFiles(const AbsolutePath & dataPath, const std::string & find, bool caseInsensitive = false);
virtual std::vector<AbsolutePath> getFiles(const RelativePath & dataPath, const RelativePath & find, bool caseInsensitive);
/* same as getFiles but search directories recursively */
std::vector<AbsolutePath> getFilesRecursive(const AbsolutePath & dataPath, const std::string & find, bool caseInsensitive = false);
protected:
virtual bool systemExists(const AbsolutePath & path);
virtual bool systemIsDirectory(const AbsolutePath & path);
AbsolutePath lookup(const RelativePath path);
std::vector<AbsolutePath> findDirectoriesIn(const AbsolutePath & path);
std::vector<AbsolutePath> getAllDirectories(const AbsolutePath & path);
protected:
Util::Thread::LockObject lock;
AbsolutePath dataPath;
};
#endif
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Jun 17, 9:32 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
70216
Default Alt Text
(76 KB)
Attached To
Mode
R75 R-Tech1
Attached
Detach File
Event Timeline