Simplified advanced detector file sys scanning code

svn-id: r33455
This commit is contained in:
Max Horn 2008-07-30 15:38:42 +00:00
parent 04c05d3ca0
commit fbe4f0dd48
5 changed files with 330 additions and 257 deletions

View File

@ -305,13 +305,14 @@ static void reportUnknown(const StringMap &filesMD5, const IntMap &filesSize) {
printf("\n");
}
static ADGameDescList detectGameFilebased(const FSList &fslist, const Common::ADParams &params, IntMap &allFiles);
static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams &params);
static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &params, Language language, Platform platform, const Common::String extra) {
StringSet filesList;
StringMap allFiles;
StringSet detectFiles;
StringMap filesMD5;
IntMap filesSize;
IntMap allFiles;
const ADGameFileDescription *fileDesc;
const ADGameDescription *g;
@ -319,16 +320,8 @@ static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &p
debug(3, "Starting detection");
// First we compose list of files which we need MD5s for
for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) {
g = (const ADGameDescription *)descPtr;
for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
filesList[String(fileDesc->fileName)] = true;
}
}
// Get the information of the existing files
// First we compose an efficient to query set of all files in fslist.
// Includes nifty stuff like removing trailing dots and ignoring case.
for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
if (file->isDirectory())
continue;
@ -339,23 +332,37 @@ static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &p
if (tstr.lastChar() == '.')
tstr.deleteLastChar();
allFiles[tstr] = true; // Record the presence of this file
allFiles[tstr] = file->getPath(); // Record the presence of this file
}
debug(3, "+ %s", tstr.c_str());
// Compute the set of files for which we need MD5s for. I.e. files which are
// included in some ADGameDescription *and* present in fslist.
for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) {
g = (const ADGameDescription *)descPtr;
if (!filesList.contains(tstr))
continue;
for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
String tstr = fileDesc->fileName;
if (allFiles.contains(tstr))
detectFiles[tstr] = true;
}
}
// Get the information for all detection files, if they exist
for (StringSet::const_iterator file = detectFiles.begin(); file != detectFiles.end(); ++file) {
String fname = file->_key;
debug(3, "+ %s", fname.c_str());
char md5str[32+1];
if (!md5_file_string(*file, md5str, params.md5Bytes))
if (!md5_file_string(allFiles[fname].c_str(), md5str, params.md5Bytes))
continue;
filesMD5[tstr] = md5str;
filesMD5[fname] = md5str;
debug(3, "> %s: %s", tstr.c_str(), md5str);
debug(3, "> %s: %s", fname.c_str(), md5str);
File testFile;
if (testFile.open(file->getPath())) {
filesSize[tstr] = (int32)testFile.size();
if (testFile.open(allFiles[fname])) {
filesSize[fname] = (int32)testFile.size();
testFile.close();
}
}
@ -443,32 +450,16 @@ static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &p
// Filename based fallback
if (params.fileBasedFallback != 0)
matched = detectGameFilebased(fslist, params, allFiles);
matched = detectGameFilebased(allFiles, params);
}
return matched;
}
static ADGameDescList detectGameFilebased(const FSList &fslist, const Common::ADParams &params, IntMap &allFiles) {
static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams &params) {
const ADFileBasedFallback *ptr;
const char* const* filenames;
// First we create list of files required for detection.
// The filenames can be different than the MD5 based match ones.
for (ptr = params.fileBasedFallback; ptr->desc; ++ptr) {
for (filenames = ptr->filenames; *filenames; ++filenames) {
String tstr = String(*filenames);
if (!allFiles.contains(tstr)) {
File testFile;
if (testFile.open(tstr) || testFile.open(tstr + ".")) {
allFiles[tstr] = true;
testFile.close();
}
}
}
}
// Then we perform the actual filename matching. If there are
// several matches, only the one with the maximum numbers of
// files is considered.

View File

@ -32,27 +32,10 @@
#include "common/config-manager.h"
#include "common/file.h"
#include "common/util.h"
#include "common/system.h"
DECLARE_SINGLETON(Common::ConfigManager);
#ifdef __PLAYSTATION2__
#include "backends/platform/ps2/systemps2.h"
#endif
#ifdef IPHONE
#include "backends/platform/iphone/osys_iphone.h"
#endif
#if defined(UNIX)
#ifdef MACOSX
#define DEFAULT_CONFIG_FILE "Library/Preferences/ScummVM Preferences"
#else
#define DEFAULT_CONFIG_FILE ".scummvmrc"
#endif
#else
#define DEFAULT_CONFIG_FILE "scummvm.ini"
#endif
#define MAXLINELEN 256
static bool isValidDomainName(const Common::String &domName) {
@ -85,77 +68,22 @@ ConfigManager::ConfigManager()
void ConfigManager::loadDefaultConfigFile() {
char configFile[MAXPATHLEN];
// GP2X is Linux based but Home dir can be read only so do not use it and put the config in the executable dir.
// On the iPhone, the home dir of the user when you launch the app from the Springboard, is /. Which we don't want.
#if defined(UNIX) && !defined(GP2X) && !defined(IPHONE)
const char *home = getenv("HOME");
if (home != NULL && strlen(home) < MAXPATHLEN)
snprintf(configFile, MAXPATHLEN, "%s/%s", home, DEFAULT_CONFIG_FILE);
else
strcpy(configFile, DEFAULT_CONFIG_FILE);
#else
#if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__)
OSVERSIONINFO win32OsVersion;
ZeroMemory(&win32OsVersion, sizeof(OSVERSIONINFO));
win32OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&win32OsVersion);
// Check for non-9X version of Windows.
if (win32OsVersion.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) {
// Use the Application Data directory of the user profile.
if (win32OsVersion.dwMajorVersion >= 5) {
if (!GetEnvironmentVariable("APPDATA", configFile, sizeof(configFile)))
error("Unable to access application data directory");
} else {
if (!GetEnvironmentVariable("USERPROFILE", configFile, sizeof(configFile)))
error("Unable to access user profile directory");
_appDomain.clear();
_gameDomains.clear();
_transientDomain.clear();
_domainSaveOrder.clear();
strcat(configFile, "\\Application Data");
CreateDirectory(configFile, NULL);
}
// Open the default config file
SeekableReadStream *stream = g_system->openConfigFileForReading();
_filename.clear(); // clear the filename to indicate that we are using the default config file
strcat(configFile, "\\ScummVM");
CreateDirectory(configFile, NULL);
strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
// ... load it ...
assert(stream);
loadStream(*stream);
// ... and close it again.
delete stream;
if (fopen(configFile, "r") == NULL) {
// Check windows directory
char oldConfigFile[MAXPATHLEN];
GetWindowsDirectory(oldConfigFile, MAXPATHLEN);
strcat(oldConfigFile, "\\" DEFAULT_CONFIG_FILE);
if (fopen(oldConfigFile, "r")) {
printf("The default location of the config file (scummvm.ini) in ScummVM has changed,\n");
printf("under Windows NT4/2000/XP/Vista. You may want to consider moving your config\n");
printf("file from the old default location:\n");
printf("%s\n", oldConfigFile);
printf("to the new default location:\n");
printf("%s\n\n", configFile);
strcpy(configFile, oldConfigFile);
}
}
} else {
// Check windows directory
GetWindowsDirectory(configFile, MAXPATHLEN);
strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
}
#elif defined(PALMOS_MODE)
strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE);
#elif defined(IPHONE)
strcpy(configFile, OSystem_IPHONE::getConfigPath());
#elif defined(__PLAYSTATION2__)
((OSystem_PS2*)g_system)->makeConfigPath(configFile);
#elif defined(__PSP__)
strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE);
#elif defined (__SYMBIAN32__)
strcpy(configFile, Symbian::GetExecutablePath());
strcat(configFile, DEFAULT_CONFIG_FILE);
#else
strcpy(configFile, DEFAULT_CONFIG_FILE);
#endif
#endif
loadConfigFile(configFile);
flushToDisk();
}
@ -163,160 +91,168 @@ void ConfigManager::loadConfigFile(const String &filename) {
_appDomain.clear();
_gameDomains.clear();
_transientDomain.clear();
_domainSaveOrder.clear();
_filename = filename;
_domainSaveOrder.clear();
loadFile(_filename);
printf("Using configuration file: %s\n", _filename.c_str());
}
void ConfigManager::loadFile(const String &filename) {
File cfg_file;
if (!cfg_file.open(filename)) {
printf("Creating configuration file: %s\n", filename.c_str());
} else {
String domain;
String comment;
int lineno = 0;
printf("Using configuration file: %s\n", _filename.c_str());
loadStream(cfg_file);
}
}
// TODO: Detect if a domain occurs multiple times (or likewise, if
// a key occurs multiple times inside one domain).
void ConfigManager::loadStream(SeekableReadStream &stream) {
while (!cfg_file.eof() && !cfg_file.ioFailed()) {
lineno++;
String domain;
String comment;
int lineno = 0;
// Read a line
String line;
while (line.lastChar() != '\n') {
char buf[MAXLINELEN];
if (!cfg_file.readLine_NEW(buf, MAXLINELEN))
break;
line += buf;
// TODO: Detect if a domain occurs multiple times (or likewise, if
// a key occurs multiple times inside one domain).
while (!stream.eos() && !stream.ioFailed()) {
lineno++;
// Read a line
String line;
while (line.lastChar() != '\n') {
char buf[MAXLINELEN];
if (!stream.readLine_NEW(buf, MAXLINELEN))
break;
line += buf;
}
if (line.size() == 0) {
// Do nothing
} else if (line[0] == '#') {
// Accumulate comments here. Once we encounter either the start
// of a new domain, or a key-value-pair, we associate the value
// of the 'comment' variable with that entity.
comment += line;
} else if (line[0] == '[') {
// It's a new domain which begins here.
const char *p = line.c_str() + 1;
// Get the domain name, and check whether it's valid (that
// is, verify that it only consists of alphanumerics,
// dashes and underscores).
while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
p++;
switch (*p) {
case '\0':
error("Config file buggy: missing ] in line %d", lineno);
break;
case ']':
domain = String(line.c_str() + 1, p - (line.c_str() + 1));
//domain = String(line.c_str() + 1, p); // TODO: Pending Common::String changes
break;
default:
error("Config file buggy: Invalid character '%c' occured in domain name in line %d", *p, lineno);
}
if (line.size() == 0) {
// Do nothing
} else if (line[0] == '#') {
// Accumulate comments here. Once we encounter either the start
// of a new domain, or a key-value-pair, we associate the value
// of the 'comment' variable with that entity.
comment += line;
} else if (line[0] == '[') {
// It's a new domain which begins here.
const char *p = line.c_str() + 1;
// Get the domain name, and check whether it's valid (that
// is, verify that it only consists of alphanumerics,
// dashes and underscores).
while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
p++;
switch (*p) {
case '\0':
error("Config file buggy: missing ] in line %d", lineno);
break;
case ']':
domain = String(line.c_str() + 1, p - (line.c_str() + 1));
//domain = String(line.c_str() + 1, p); // TODO: Pending Common::String changes
break;
default:
error("Config file buggy: Invalid character '%c' occured in domain name in line %d", *p, lineno);
}
// Store domain comment
if (domain == kApplicationDomain) {
_appDomain.setDomainComment(comment);
} else {
_gameDomains[domain].setDomainComment(comment);
}
comment.clear();
_domainSaveOrder.push_back(domain);
// Store domain comment
if (domain == kApplicationDomain) {
_appDomain.setDomainComment(comment);
} else {
// This line should be a line with a 'key=value' pair, or an empty one.
// Skip leading whitespaces
const char *t = line.c_str();
while (isspace(*t))
t++;
// Skip empty lines / lines with only whitespace
if (*t == 0)
continue;
// If no domain has been set, this config file is invalid!
if (domain.empty()) {
error("Config file buggy: Key/value pair found outside a domain in line %d", lineno);
}
// Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
const char *p = strchr(t, '=');
if (!p)
error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
// Trim spaces before the '=' to obtain the key
const char *p2 = p;
while (p2 > t && isspace(*(p2-1)))
p2--;
String key(t, p2 - t);
// Skip spaces after the '='
t = p + 1;
while (isspace(*t))
t++;
// Trim trailing spaces
p2 = t + strlen(t);
while (p2 > t && isspace(*(p2-1)))
p2--;
String value(t, p2 - t);
// Finally, store the key/value pair in the active domain
set(key, value, domain);
// Store comment
if (domain == kApplicationDomain) {
_appDomain.setKVComment(key, comment);
} else {
_gameDomains[domain].setKVComment(key, comment);
}
comment.clear();
_gameDomains[domain].setDomainComment(comment);
}
comment.clear();
_domainSaveOrder.push_back(domain);
} else {
// This line should be a line with a 'key=value' pair, or an empty one.
// Skip leading whitespaces
const char *t = line.c_str();
while (isspace(*t))
t++;
// Skip empty lines / lines with only whitespace
if (*t == 0)
continue;
// If no domain has been set, this config file is invalid!
if (domain.empty()) {
error("Config file buggy: Key/value pair found outside a domain in line %d", lineno);
}
// Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
const char *p = strchr(t, '=');
if (!p)
error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
// Extract the key/value pair
String key(t, p);
String value(p + 1);
// Trim of spaces
key.trim();
value.trim();
// Finally, store the key/value pair in the active domain
set(key, value, domain);
// Store comment
if (domain == kApplicationDomain) {
_appDomain.setKVComment(key, comment);
} else {
_gameDomains[domain].setKVComment(key, comment);
}
comment.clear();
}
}
}
void ConfigManager::flushToDisk() {
#ifndef __DC__
DumpFile cfg_file;
WriteStream *stream;
if (!cfg_file.open(_filename)) {
warning("Unable to write configuration file: %s", _filename.c_str());
if (_filename.empty()) {
// Write to the default config file
stream = g_system->openConfigFileForWriting();
if (!stream) // If writing to the config file is not possible, do nothing
return;
} else {
// First write the domains in _domainSaveOrder, in that order.
// Note: It's possible for _domainSaveOrder to list domains which
// are not present anymore.
StringList::const_iterator i;
for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) {
if (kApplicationDomain == *i) {
writeDomain(cfg_file, *i, _appDomain);
} else if (_gameDomains.contains(*i)) {
writeDomain(cfg_file, *i, _gameDomains[*i]);
}
DumpFile *dump = new DumpFile();
assert(dump);
if (!dump->open(_filename)) {
warning("Unable to write configuration file: %s", _filename.c_str());
delete dump;
return;
}
stream = dump;
}
DomainMap::const_iterator d;
// Now write the domains which haven't been written yet
if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end())
writeDomain(cfg_file, kApplicationDomain, _appDomain);
for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) {
if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end())
writeDomain(cfg_file, d->_key, d->_value);
// First write the domains in _domainSaveOrder, in that order.
// Note: It's possible for _domainSaveOrder to list domains which
// are not present anymore.
StringList::const_iterator i;
for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) {
if (kApplicationDomain == *i) {
writeDomain(*stream, *i, _appDomain);
} else if (_gameDomains.contains(*i)) {
writeDomain(*stream, *i, _gameDomains[*i]);
}
}
DomainMap::const_iterator d;
// Now write the domains which haven't been written yet
if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end())
writeDomain(*stream, kApplicationDomain, _appDomain);
for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) {
if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end())
writeDomain(*stream, d->_key, d->_value);
}
delete stream;
#endif // !__DC__
}

View File

@ -36,7 +36,7 @@
namespace Common {
class WriteStream;
class SeekableReadStream;
/**
* The (singleton) configuration manager, used to query & set configuration
@ -156,7 +156,7 @@ private:
friend class Singleton<SingletonBaseType>;
ConfigManager();
void loadFile(const String &filename);
void loadStream(SeekableReadStream &stream);
void writeDomain(WriteStream &stream, const String &name, const Domain &domain);
Domain _transientDomain;

View File

@ -121,3 +121,132 @@ void OSystem::clearScreen() {
memset(screen->pixels, 0, screen->h * screen->pitch);
unlockScreen();
}
/*
FIXME: The config file loading code below needs to be cleaned up.
Port specific variants should be pushed into the respective ports.
Ideally, the default OSystem::openConfigFileForReading/Writing methods
should be removed completely.
*/
#include "common/file.h"
#ifdef __PLAYSTATION2__
#include "backends/platform/ps2/systemps2.h"
#endif
#ifdef IPHONE
#include "backends/platform/iphone/osys_iphone.h"
#endif
#if defined(UNIX)
#ifdef MACOSX
#define DEFAULT_CONFIG_FILE "Library/Preferences/ScummVM Preferences"
#else
#define DEFAULT_CONFIG_FILE ".scummvmrc"
#endif
#else
#define DEFAULT_CONFIG_FILE "scummvm.ini"
#endif
static Common::String getDefaultConfigFileName() {
char configFile[MAXPATHLEN];
#if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__)
OSVERSIONINFO win32OsVersion;
ZeroMemory(&win32OsVersion, sizeof(OSVERSIONINFO));
win32OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&win32OsVersion);
// Check for non-9X version of Windows.
if (win32OsVersion.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) {
// Use the Application Data directory of the user profile.
if (win32OsVersion.dwMajorVersion >= 5) {
if (!GetEnvironmentVariable("APPDATA", configFile, sizeof(configFile)))
error("Unable to access application data directory");
} else {
if (!GetEnvironmentVariable("USERPROFILE", configFile, sizeof(configFile)))
error("Unable to access user profile directory");
strcat(configFile, "\\Application Data");
CreateDirectory(configFile, NULL);
}
strcat(configFile, "\\ScummVM");
CreateDirectory(configFile, NULL);
strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
if (fopen(configFile, "r") == NULL) {
// Check windows directory
char oldConfigFile[MAXPATHLEN];
GetWindowsDirectory(oldConfigFile, MAXPATHLEN);
strcat(oldConfigFile, "\\" DEFAULT_CONFIG_FILE);
if (fopen(oldConfigFile, "r")) {
printf("The default location of the config file (scummvm.ini) in ScummVM has changed,\n");
printf("under Windows NT4/2000/XP/Vista. You may want to consider moving your config\n");
printf("file from the old default location:\n");
printf("%s\n", oldConfigFile);
printf("to the new default location:\n");
printf("%s\n\n", configFile);
strcpy(configFile, oldConfigFile);
}
}
} else {
// Check windows directory
GetWindowsDirectory(configFile, MAXPATHLEN);
strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
}
#elif defined(PALMOS_MODE)
strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE);
#elif defined(IPHONE)
strcpy(configFile, OSystem_IPHONE::getConfigPath());
#elif defined(__PLAYSTATION2__)
((OSystem_PS2*)g_system)->makeConfigPath(configFile);
#elif defined(__PSP__)
strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE);
#elif defined (__SYMBIAN32__)
strcpy(configFile, Symbian::GetExecutablePath());
strcat(configFile, DEFAULT_CONFIG_FILE);
#elif defined(UNIX) && !defined(GP2X) && !defined(IPHONE)
// On UNIX type systems, by default we store the config file inside
// to the HOME directory of the user.
//
// GP2X is Linux based but Home dir can be read only so do not use
// it and put the config in the executable dir.
//
// On the iPhone, the home dir of the user when you launch the app
// from the Springboard, is /. Which we don't want.
const char *home = getenv("HOME");
if (home != NULL && strlen(home) < MAXPATHLEN)
snprintf(configFile, MAXPATHLEN, "%s/%s", home, DEFAULT_CONFIG_FILE);
else
strcpy(configFile, DEFAULT_CONFIG_FILE);
#else
strcpy(configFile, DEFAULT_CONFIG_FILE);
#endif
return configFile;
}
Common::SeekableReadStream *OSystem::openConfigFileForReading() {
Common::File *confFile = new Common::File();
assert(confFile);
confFile->open(getDefaultConfigFileName());
return confFile;
}
Common::WriteStream *OSystem::openConfigFileForWriting() {
#ifdef __DC__
return 0;
#else
Common::DumpFile *confFile = new Common::DumpFile();
assert(confFile);
confFile->open(getDefaultConfigFileName());
return confFile;
#endif
}

View File

@ -44,6 +44,8 @@ namespace Common {
class EventManager;
class SaveFileManager;
class TimerManager;
class SeekableReadStream;
class WriteStream;
}
class FilesystemFactory;
@ -900,10 +902,25 @@ public:
/**
* Returns the FilesystemFactory object, depending on the current architecture.
*
* @return FilesystemFactory* The specific factory for the current architecture.
* @return the FSNode factory for the current architecture
*/
virtual FilesystemFactory *getFilesystemFactory() = 0;
/**
* Open the default config file for reading, by returning a suitable
* ReadStream instance. It is the callers responsiblity to delete
* the stream after use.
*/
virtual Common::SeekableReadStream *openConfigFileForReading();
/**
* Open the default config file for writing, by returning a suitable
* WriteStream instance. It is the callers responsiblity to delete
* the stream after use.
*
* May return 0 to indicate that writing to config file is not possible.
*/
virtual Common::WriteStream *openConfigFileForWriting();
/**
* Return String which is used for backend-specific addition to theme