Merge pull request #267 from KentuckyCompass/case_insensitive2

Case insensitive2
This commit is contained in:
Henrik Rydgård 2012-12-27 06:06:00 -08:00
commit bafb63ca72
2 changed files with 298 additions and 23 deletions

View File

@ -18,6 +18,7 @@
#ifdef _WIN32
#include <windows.h>
#else
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#endif
@ -25,8 +26,103 @@
#include "FileUtil.h"
#include "DirectoryFileSystem.h"
// TODO: Simulate case insensitivity on Unix.
// NOTE: MacOSX is already case insensitive.
#if HOST_IS_CASE_SENSITIVE
static bool FixFilenameCase(const std::string &path, std::string &filename)
{
// Are we lucky?
if (File::Exists(path + filename))
return true;
size_t filenameSize = filename.size();
for (size_t i = 0; i < filenameSize; i++)
{
filename[i] = tolower(filename[i]);
}
//TODO: lookup filename in cache for "path"
struct dirent_large { struct dirent entry; char padding[FILENAME_MAX+1]; } diren;
struct dirent_large;
struct dirent *result = NULL;
DIR *dirp = opendir(path.c_str());
if (!dirp)
return false;
bool retValue = false;
while (!readdir_r(dirp, (dirent*) &diren, &result) && result)
{
if (result->d_namlen != filenameSize)
continue;
size_t i;
for (i = 0; i < filenameSize; i++)
{
if (filename[i] != tolower(result->d_name[i]))
break;
}
if (i < filenameSize)
continue;
filename = result->d_name;
retValue = true;
}
closedir(dirp);
return retValue;
}
bool DirectoryFileSystem::FixPathCase(std::string &path, FixPathCaseBehavior behavior)
{
size_t len = path.size();
if (len == 0)
return true;
if (path[len - 1] == '/')
{
len--;
if (len == 0)
return true;
}
std::string fullPath;
fullPath.reserve(basePath.size() + len + 1);
fullPath.append(basePath);
size_t start = 0;
while (start < len)
{
size_t i = path.find('/', start);
if (i == std::string::npos)
i = len;
if (i > start)
{
std::string component = path.substr(start, i - start);
if (FixFilenameCase(fullPath, component) == false)
return (behavior == FPC_FILE_MUST_EXIST || (behavior == FPC_PATH_MUST_EXIST && i >= len));
path.replace(start, i - start, component);
fullPath.append(component);
fullPath.append(1, '/');
}
start = i + 1;
}
return true;
}
#endif
DirectoryFileSystem::DirectoryFileSystem(IHandleAllocator *_hAlloc, std::string _basePath) : basePath(_basePath) {
File::CreateFullPath(basePath);
@ -60,11 +156,37 @@ std::string DirectoryFileSystem::GetLocalPath(std::string localpath) {
}
bool DirectoryFileSystem::MkDir(const std::string &dirname) {
#if HOST_IS_CASE_SENSITIVE
// Must fix case BEFORE attempting, because MkDir would create
// duplicate (different case) directories
std::string fixedCase = dirname;
if ( ! FixPathCase(fixedCase, FPC_PARTIAL_ALLOWED) )
return false;
return File::CreateFullPath(GetLocalPath(fixedCase));
#else
return File::CreateFullPath(GetLocalPath(dirname));
#endif
}
bool DirectoryFileSystem::RmDir(const std::string &dirname) {
std::string fullName = GetLocalPath(dirname);
#if HOST_IS_CASE_SENSITIVE
// Maybe we're lucky?
if (File::DeleteDirRecursively(fullName))
return true;
// Nope, fix case and try again
fullName = dirname;
if ( ! FixPathCase(fullName, FPC_FILE_MUST_EXIST) )
return false; // or go on and attempt (for a better error code than just false?)
fullName = GetLocalPath(fullName);
#endif
/*#ifdef _WIN32
return RemoveDirectory(fullName.c_str()) == TRUE;
#else
@ -74,38 +196,97 @@ bool DirectoryFileSystem::RmDir(const std::string &dirname) {
}
bool DirectoryFileSystem::RenameFile(const std::string &from, const std::string &to) {
std::string fullFrom = GetLocalPath(from);
std::string fullTo = to;
// TO filename may not include path. Intention is that it uses FROM's path
if (to.find("/") != std::string::npos) {
int offset = from.find_last_of("/");
if (offset >= 0) {
size_t offset = from.find_last_of("/");
if (offset != std::string::npos) {
fullTo = from.substr(0, offset + 1) + to;
}
}
fullTo = GetLocalPath(fullTo);
#ifdef _WIN32
return MoveFile(fullFrom.c_str(), fullTo.c_str()) == TRUE;
#else
return 0 == rename(fullFrom.c_str(), fullTo.c_str());
std::string fullFrom = GetLocalPath(from);
#if HOST_IS_CASE_SENSITIVE
// In case TO should overwrite a file with different case
if ( ! FixPathCase(fullTo, FPC_PATH_MUST_EXIST) )
return false; // or go on and attempt (for a better error code than just false?)
#endif
fullTo = GetLocalPath(fullTo);
const char * fullToC = fullTo.c_str();
#ifdef _WIN32
bool retValue = (MoveFile(fullFrom.c_str(), fullToC) == TRUE);
#else
bool retValue = (0 == rename(fullFrom.c_str(), fullToC));
#endif
#if HOST_IS_CASE_SENSITIVE
if (! retValue)
{
// May have failed due to case sensitivity on FROM, so try again
fullFrom = from;
if ( ! FixPathCase(fullFrom, FPC_FILE_MUST_EXIST) )
return false; // or go on and attempt (for a better error code than just false?)
fullFrom = GetLocalPath(fullFrom);
#ifdef _WIN32
retValue = (MoveFile(fullFrom.c_str(), fullToC) == TRUE);
#else
retValue = (0 == rename(fullFrom.c_str(), fullToC));
#endif
}
#endif
return retValue;
}
bool DirectoryFileSystem::DeleteFile(const std::string &filename) {
std::string fullName = GetLocalPath(filename);
#ifdef _WIN32
return ::DeleteFile(fullName.c_str()) == TRUE;
bool retValue = (::DeleteFile(fullName.c_str()) == TRUE);
#else
return 0 == unlink(fullName.c_str());
bool retValue = (0 == unlink(fullName.c_str()));
#endif
#if HOST_IS_CASE_SENSITIVE
if (! retValue)
{
// May have failed due to case sensitivity, so try again
fullName = filename;
if ( ! FixPathCase(fullName, FPC_FILE_MUST_EXIST) )
return false; // or go on and attempt (for a better error code than just false?)
fullName = GetLocalPath(fullName);
#ifdef _WIN32
retValue = (::DeleteFile(fullName.c_str()) == TRUE);
#else
retValue = (0 == unlink(fullName.c_str()));
#endif
}
#endif
return retValue;
}
u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access) {
#if HOST_IS_CASE_SENSITIVE
if (access & (FILEACCESS_APPEND|FILEACCESS_CREATE|FILEACCESS_WRITE))
{
DEBUG_LOG(HLE, "Checking case for path %s", filename.c_str());
if ( ! FixPathCase(filename, FPC_PATH_MUST_EXIST) )
return 0; // or go on and attempt (for a better error code than just 0?)
}
// else we try fopen first (in case we're lucky) before simulating case insensitivity
#endif
std::string fullName = GetLocalPath(filename);
INFO_LOG(HLE,"Actually opening %s (%s)", fullName.c_str(), filename.c_str());
const char *fullNameC = fullName.c_str();
INFO_LOG(HLE,"Actually opening %s (%s)", fullNameC, filename.c_str());
OpenFileEntry entry;
//TODO: tests, should append seek to end of file? seeking in a file opened for append?
#ifdef _WIN32
// Convert parameters to Windows permissions and access
DWORD desired = 0;
@ -125,20 +306,74 @@ u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access) {
openmode = OPEN_EXISTING;
}
//Let's do it!
entry.hFile = CreateFile(fullName.c_str(), desired, sharemode, 0, openmode, 0, 0);
entry.hFile = CreateFile(fullNameC, desired, sharemode, 0, openmode, 0, 0);
bool success = entry.hFile != INVALID_HANDLE_VALUE;
#else
entry.hFile = fopen(fullName.c_str(), access & FILEACCESS_WRITE ? "wb" : "rb");
bool success = entry.hFile != 0;
// Convert flags in access parameter to fopen access mode
const char *mode = NULL;
if (access & FILEACCESS_APPEND) {
if (access & FILEACCESS_READ)
mode = "ab+"; // append+read, create if needed
else
mode = "ab"; // append only, create if needed
} else if (access & FILEACCESS_WRITE) {
if (access & FILEACCESS_READ) {
// FILEACCESS_CREATE is ignored for read only, write only, and append
// because C++ standard fopen's nonexistant file creation can only be
// customized for files opened read+write
if (access & FILEACCESS_CREATE)
mode = "wb+"; // read+write, create if needed
else
mode = "rb+"; // read+write, but don't create
} else {
mode = "wb"; // write only, create if needed
}
} else { // neither write nor append, so default to read only
mode = "rb"; // read only, don't create
}
entry.hFile = fopen(fullNameC, mode);
bool success = entry.hFile != 0;
#endif
#if HOST_IS_CASE_SENSITIVE
if (!success &&
!(access & FILEACCESS_APPEND) &&
!(access & FILEACCESS_CREATE) &&
!(access & FILEACCESS_WRITE))
{
if ( ! FixPathCase(filename, FPC_PATH_MUST_EXIST) )
return 0; // or go on and attempt (for a better error code than just 0?)
fullName = GetLocalPath(filename);
fullNameC = fullName.c_str();
DEBUG_LOG(HLE, "Case may have been incorrect, second try opening %s (%s)", fullNameC, filename.c_str());
// And try again with the correct case this time
#ifdef _WIN32
entry.hFile = CreateFile(fullNameC, desired, sharemode, 0, openmode, 0, 0);
success = entry.hFile != INVALID_HANDLE_VALUE;
#else
entry.hFile = fopen(fullNameC, mode);
success = entry.hFile != 0;
#endif
}
#endif
if (!success) {
#ifdef _WIN32
ERROR_LOG(HLE, "DirectoryFileSystem::OpenFile: FAILED, %i - access = %i", GetLastError(), (int)access);
#else
ERROR_LOG(HLE, "DirectoryFileSystem::OpenFile: FAILED, access = %i", (int)access);
#endif
//wwwwaaaaahh!!
return 0;
} else {
#ifdef _WIN32
if (access & FILEACCESS_APPEND)
SetFilePointer(entry.hFile, 0, NULL, FILE_END);
#endif
u32 newHandle = hAlloc->GetNewHandle();
entries[newHandle] = entry;
@ -209,18 +444,18 @@ size_t DirectoryFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {
#ifdef _WIN32
DWORD moveMethod = 0;
switch (type) {
case FILEMOVE_BEGIN: moveMethod = FILE_BEGIN; break;
case FILEMOVE_CURRENT: moveMethod = FILE_CURRENT; break;
case FILEMOVE_END: moveMethod = FILE_END; break;
case FILEMOVE_BEGIN: moveMethod = FILE_BEGIN; break;
case FILEMOVE_CURRENT: moveMethod = FILE_CURRENT; break;
case FILEMOVE_END: moveMethod = FILE_END; break;
}
DWORD newPos = SetFilePointer((*iter).second.hFile, (LONG)position, 0, moveMethod);
return newPos;
#else
int moveMethod = 0;
switch (type) {
case FILEMOVE_BEGIN: moveMethod = SEEK_SET; break;
case FILEMOVE_CURRENT: moveMethod = SEEK_CUR; break;
case FILEMOVE_END: moveMethod = SEEK_END; break;
case FILEMOVE_BEGIN: moveMethod = SEEK_SET; break;
case FILEMOVE_CURRENT: moveMethod = SEEK_CUR; break;
case FILEMOVE_END: moveMethod = SEEK_END; break;
}
fseek(iter->second.hFile, position, moveMethod);
return ftell(iter->second.hFile);
@ -237,8 +472,17 @@ PSPFileInfo DirectoryFileSystem::GetFileInfo(std::string filename) {
x.name = filename;
std::string fullName = GetLocalPath(filename);
if (!File::Exists(fullName)) {
if (! File::Exists(fullName)) {
#if HOST_IS_CASE_SENSITIVE
if (! FixPathCase(filename, FPC_FILE_MUST_EXIST))
return x;
fullName = GetLocalPath(filename);
if (! File::Exists(fullName))
return x;
#else
return x;
#endif
}
x.type = File::IsDirectory(fullName) ? FILETYPE_NORMAL : FILETYPE_DIRECTORY;
x.exists = true;
@ -290,6 +534,8 @@ std::vector<PSPFileInfo> DirectoryFileSystem::GetDirListing(std::string path) {
if (!retval)
break;
}
#else
ERROR_LOG(HLE, "GetDirListing not implemented on non-Windows");
#endif
return myVector;
}

View File

@ -28,6 +28,26 @@
typedef void * HANDLE;
#endif
#if defined(__APPLE__)
#if TARGET_OS_IPHONE
#define HOST_IS_CASE_SENSITIVE true
#elif TARGET_IPHONE_SIMULATOR
#define HOST_IS_CASE_SENSITIVE false
#else
// Mac OSX case sensitivity defaults off, but is user configurable (when
// creating a filesytem), so assume the worst:
#define HOST_IS_CASE_SENSITIVE true
#endif
#elif defined(_WIN32) || defined(__SYMBIAN32__)
#define HOST_IS_CASE_SENSITIVE false
#else // Android, Linux, BSD (and the rest?)
#define HOST_IS_CASE_SENSITIVE true
#endif
class DirectoryFileSystem : public IFileSystem {
public:
DirectoryFileSystem(IHandleAllocator *_hAlloc, std::string _basePath);
@ -63,4 +83,13 @@ private:
// In case of Windows: Translate slashes, etc.
std::string GetLocalPath(std::string localpath);
#if HOST_IS_CASE_SENSITIVE
typedef enum {
FPC_FILE_MUST_EXIST, // all path components must exist (rmdir, move from)
FPC_PATH_MUST_EXIST, // all except the last one must exist - still tries to fix last one (fopen, move to)
FPC_PARTIAL_ALLOWED, // don't care how many exist (mkdir recursive)
} FixPathCaseBehavior;
bool FixPathCase(std::string &path, FixPathCaseBehavior behavior);
#endif
};