[Scummvm-cvs-logs] SF.net SVN: scummvm:[39864] residual/trunk
aquadran at users.sourceforge.net
aquadran at users.sourceforge.net
Sun Apr 5 16:48:55 CEST 2009
Revision: 39864
http://scummvm.svn.sourceforge.net/scummvm/?rev=39864&view=rev
Author: aquadran
Date: 2009-04-05 14:48:54 +0000 (Sun, 05 Apr 2009)
Log Message:
-----------
update rest scummvm common code with residual, and update engine code to that changes
Modified Paths:
--------------
residual/trunk/common/config-file.cpp
residual/trunk/common/config-file.h
residual/trunk/common/config-manager.cpp
residual/trunk/common/config-manager.h
residual/trunk/common/error.h
residual/trunk/common/file.cpp
residual/trunk/common/file.h
residual/trunk/common/fs.cpp
residual/trunk/common/fs.h
residual/trunk/common/savefile.h
residual/trunk/common/stream.cpp
residual/trunk/common/stream.h
residual/trunk/dists/msvc9/residual.vcproj
residual/trunk/engine/backend/events/default/default-events.cpp
residual/trunk/engine/backend/events/default/default-events.h
residual/trunk/engine/backend/fs/abstract-fs.h
residual/trunk/engine/backend/fs/amigaos4/amigaos4-fs-factory.cpp
residual/trunk/engine/backend/fs/amigaos4/amigaos4-fs-factory.h
residual/trunk/engine/backend/fs/amigaos4/amigaos4-fs.cpp
residual/trunk/engine/backend/fs/fs-factory.h
residual/trunk/engine/backend/fs/posix/posix-fs-factory.cpp
residual/trunk/engine/backend/fs/posix/posix-fs-factory.h
residual/trunk/engine/backend/fs/posix/posix-fs.cpp
residual/trunk/engine/backend/fs/windows/windows-fs-factory.cpp
residual/trunk/engine/backend/fs/windows/windows-fs-factory.h
residual/trunk/engine/backend/fs/windows/windows-fs.cpp
residual/trunk/engine/backend/platform/driver.h
residual/trunk/engine/backend/platform/sdl/driver_gl.cpp
residual/trunk/engine/backend/platform/sdl/driver_gl.h
residual/trunk/engine/backend/platform/sdl/driver_sdl.cpp
residual/trunk/engine/backend/platform/sdl/driver_sdl.h
residual/trunk/engine/backend/platform/sdl/driver_tinygl.cpp
residual/trunk/engine/backend/platform/sdl/driver_tinygl.h
residual/trunk/engine/backend/saves/default/default-saves.cpp
residual/trunk/engine/backend/saves/default/default-saves.h
residual/trunk/engine/backend/saves/savefile.cpp
residual/trunk/engine/backend/timer/default/default-timer.cpp
residual/trunk/engine/backend/timer/default/default-timer.h
residual/trunk/engine/cmd_line.cpp
residual/trunk/engine/engine.cpp
residual/trunk/engine/imuse/imuse_mcmp_mgr.cpp
residual/trunk/engine/lab.cpp
residual/trunk/engine/lab.h
residual/trunk/engine/localize.cpp
residual/trunk/engine/lua/liolib.cpp
residual/trunk/engine/lua/lua.h
residual/trunk/engine/lua.cpp
residual/trunk/engine/main.cpp
residual/trunk/engine/resource.cpp
residual/trunk/engine/resource.h
residual/trunk/engine/savegame.cpp
residual/trunk/engine/savegame.h
residual/trunk/engine/smush/smush.cpp
residual/trunk/mixer/audiostream.cpp
residual/trunk/mixer/audiostream.h
residual/trunk/mixer/mixer.cpp
residual/trunk/mixer/mixer.h
residual/trunk/mixer/mixer_intern.h
residual/trunk/mixer/rate.cpp
residual/trunk/mixer/rate.h
Added Paths:
-----------
residual/trunk/common/archive.cpp
residual/trunk/common/archive.h
residual/trunk/common/zlib.cpp
residual/trunk/common/zlib.h
residual/trunk/engine/backend/fs/abstract-fs.cpp
residual/trunk/engine/backend/fs/posix/posix-fs.h
residual/trunk/engine/backend/fs/stdiostream.cpp
residual/trunk/engine/backend/fs/stdiostream.h
residual/trunk/engine/backend/saves/posix/
residual/trunk/engine/backend/saves/posix/posix-saves.cpp
residual/trunk/engine/backend/saves/posix/posix-saves.h
Removed Paths:
-------------
residual/trunk/engine/backend/saves/compressed/
Added: residual/trunk/common/archive.cpp
===================================================================
--- residual/trunk/common/archive.cpp (rev 0)
+++ residual/trunk/common/archive.cpp 2009-04-05 14:48:54 UTC (rev 39864)
@@ -0,0 +1,247 @@
+/* Residual - Virtual machine to run LucasArts' 3D adventure games
+ *
+ * Residual is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the AUTHORS
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/archive.h"
+#include "common/fs.h"
+#include "common/util.h"
+
+#include "engine/backend/platform/driver.h"
+
+namespace Common {
+
+GenericArchiveMember::GenericArchiveMember(String name, Archive *parent)
+ : _parent(parent), _name(name) {
+}
+
+String GenericArchiveMember::getName() const {
+ return _name;
+}
+
+SeekableReadStream *GenericArchiveMember::createReadStream() const {
+ return _parent->createReadStreamForMember(_name);
+}
+
+
+int Archive::listMatchingMembers(ArchiveMemberList &list, const String &pattern) {
+ // Get all "names" (TODO: "files" ?)
+ ArchiveMemberList allNames;
+ listMembers(allNames);
+
+ int matches = 0;
+
+ // need to match lowercase key
+ String lowercasePattern = pattern;
+ lowercasePattern.toLowercase();
+
+ ArchiveMemberList::iterator it = allNames.begin();
+ for ( ; it != allNames.end(); ++it) {
+ if ((*it)->getName().matchString(lowercasePattern, true)) {
+ list.push_back(*it);
+ matches++;
+ }
+ }
+
+ return matches;
+}
+
+
+
+SearchSet::ArchiveNodeList::iterator SearchSet::find(const String &name) {
+ ArchiveNodeList::iterator it = _list.begin();
+ for ( ; it != _list.end(); ++it) {
+ if (it->_name == name)
+ break;
+ }
+ return it;
+}
+
+SearchSet::ArchiveNodeList::const_iterator SearchSet::find(const String &name) const {
+ ArchiveNodeList::const_iterator it = _list.begin();
+ for ( ; it != _list.end(); ++it) {
+ if (it->_name == name)
+ break;
+ }
+ return it;
+}
+
+/*
+ Keep the nodes sorted according to descending priorities.
+ In case two or node nodes have the same priority, insertion
+ order prevails.
+*/
+void SearchSet::insert(const Node &node) {
+ ArchiveNodeList::iterator it = _list.begin();
+ for ( ; it != _list.end(); ++it) {
+ if (it->_priority < node._priority)
+ break;
+ }
+ _list.insert(it, node);
+}
+
+void SearchSet::add(const String &name, Archive *archive, int priority, bool autoFree) {
+ if (find(name) == _list.end()) {
+ Node node(priority, name, archive, autoFree);
+ insert(node);
+ } else {
+ if (autoFree)
+ delete archive;
+ warning("SearchSet::add: archive '%s' already present", name.c_str());
+ }
+
+}
+
+void SearchSet::addDirectory(const String &name, const String &directory, int priority, int depth) {
+ FSNode dir(directory);
+ addDirectory(name, dir, priority, depth);
+}
+
+void SearchSet::addDirectory(const String &name, const FSNode &dir, int priority, int depth) {
+ if (!dir.exists() || !dir.isDirectory())
+ return;
+
+ add(name, new FSDirectory(dir, depth), priority);
+}
+
+
+void SearchSet::remove(const String &name) {
+ ArchiveNodeList::iterator it = find(name);
+ if (it != _list.end()) {
+ if (it->_autoFree)
+ delete it->_arc;
+ _list.erase(it);
+ }
+}
+
+bool SearchSet::hasArchive(const String &name) const {
+ return (find(name) != _list.end());
+}
+
+void SearchSet::clear() {
+ for (ArchiveNodeList::iterator i = _list.begin(); i != _list.end(); ++i) {
+ if (i->_autoFree)
+ delete i->_arc;
+ }
+
+ _list.clear();
+}
+
+void SearchSet::setPriority(const String &name, int priority) {
+ ArchiveNodeList::iterator it = find(name);
+ if (it == _list.end()) {
+ warning("SearchSet::setPriority: archive '%s' is not present", name.c_str());
+ return;
+ }
+
+ if (priority == it->_priority)
+ return;
+
+ Node node(*it);
+ _list.erase(it);
+ node._priority = priority;
+ insert(node);
+}
+
+bool SearchSet::hasFile(const String &name) {
+ if (name.empty())
+ return false;
+
+ ArchiveNodeList::iterator it = _list.begin();
+ for ( ; it != _list.end(); ++it) {
+ if (it->_arc->hasFile(name))
+ return true;
+ }
+
+ return false;
+}
+
+int SearchSet::listMatchingMembers(ArchiveMemberList &list, const String &pattern) {
+ int matches = 0;
+
+ ArchiveNodeList::iterator it = _list.begin();
+ for ( ; it != _list.end(); ++it)
+ matches += it->_arc->listMatchingMembers(list, pattern);
+
+ return matches;
+}
+
+int SearchSet::listMembers(ArchiveMemberList &list) {
+ int matches = 0;
+
+ ArchiveNodeList::iterator it = _list.begin();
+ for ( ; it != _list.end(); ++it)
+ matches += it->_arc->listMembers(list);
+
+ return matches;
+}
+
+ArchiveMemberPtr SearchSet::getMember(const String &name) {
+ if (name.empty())
+ return ArchiveMemberPtr();
+
+ ArchiveNodeList::iterator it = _list.begin();
+ for ( ; it != _list.end(); ++it) {
+ if (it->_arc->hasFile(name))
+ return it->_arc->getMember(name);
+ }
+
+ return ArchiveMemberPtr();
+}
+
+SeekableReadStream *SearchSet::createReadStreamForMember(const String &name) const {
+ if (name.empty())
+ return 0;
+
+ ArchiveNodeList::const_iterator it = _list.begin();
+ for ( ; it != _list.end(); ++it) {
+ SeekableReadStream *stream = it->_arc->createReadStreamForMember(name);
+ if (stream)
+ return stream;
+ }
+
+ return 0;
+}
+
+
+DECLARE_SINGLETON(SearchManager);
+
+SearchManager::SearchManager() {
+ clear(); // Force a reset
+}
+
+void SearchManager::clear() {
+ SearchSet::clear();
+
+ // Always keep system specific archives in the SearchManager.
+ // But we give them a lower priority than the default priority (which is 0),
+ // so that archives added by client code are searched first.
+ if (g_driver)
+ g_driver->addSysArchivesToSearchSet(*this, -1);
+
+ // Add the current dir as a very last resort.
+ // See also bug #2137680.
+ addDirectory(".", ".", -2);
+}
+
+} // namespace Common
Property changes on: residual/trunk/common/archive.cpp
___________________________________________________________________
Added: svn:mime-type
+ text/plain
Added: svn:keywords
+ Date Revision Author URL Id
Added: svn:eol-style
+ native
Added: residual/trunk/common/archive.h
===================================================================
--- residual/trunk/common/archive.h (rev 0)
+++ residual/trunk/common/archive.h 2009-04-05 14:48:54 UTC (rev 39864)
@@ -0,0 +1,224 @@
+/* Residual - Virtual machine to run LucasArts' 3D adventure games
+ *
+ * Residual is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the AUTHORS
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef COMMON_ARCHIVE_H
+#define COMMON_ARCHIVE_H
+
+#include "common/str.h"
+#include "common/hash-str.h"
+#include "common/list.h"
+#include "common/ptr.h"
+#include "common/singleton.h"
+
+namespace Common {
+
+class FSNode;
+class SeekableReadStream;
+
+
+/**
+ * ArchiveMember is an abstract interface to represent elements inside
+ * implementations of Archive.
+ *
+ * Archive subclasses must provide their own implementation of ArchiveMember,
+ * and use it when serving calls to listMembers() and listMatchingMembers().
+ * Alternatively, the GenericArchiveMember below can be used.
+ */
+class ArchiveMember {
+public:
+ virtual ~ArchiveMember() { }
+ virtual SeekableReadStream *createReadStream() const = 0;
+ virtual String getName() const = 0;
+ virtual String getDisplayName() const { return getName(); }
+};
+
+typedef SharedPtr<ArchiveMember> ArchiveMemberPtr;
+typedef List<ArchiveMemberPtr> ArchiveMemberList;
+
+class Archive;
+
+/**
+ * Simple ArchiveMember implementation which allows
+ * creation of ArchiveMember compatible objects via
+ * a simple Archive and name pair.
+ *
+ * Note that GenericArchiveMember objects will not
+ * be working anymore after the 'parent' object
+ * is destroyed.
+ */
+class GenericArchiveMember : public ArchiveMember {
+ Archive *_parent;
+ String _name;
+public:
+ GenericArchiveMember(String name, Archive *parent);
+ String getName() const;
+ SeekableReadStream *createReadStream() const;
+};
+
+
+/**
+ * Archive allows searches of (file)names into an arbitrary container.
+ * It also supports opening a file and returning an usable input stream.
+ */
+class Archive {
+public:
+ virtual ~Archive() { }
+
+ /**
+ * Check if a name is present in the Archive. Patterns are not allowed,
+ * as this is meant to be a quick File::exists() replacement.
+ */
+ virtual bool hasFile(const String &name) = 0;
+
+ /**
+ * Add all the names present in the Archive which match pattern to
+ * list. Returned names can be used as parameters to createReadStreamForMember.
+ * Must not remove elements from the list.
+ *
+ * @return the number of names added to list
+ */
+ virtual int listMatchingMembers(ArchiveMemberList &list, const String &pattern);
+
+ /**
+ * Add all the names present in the Archive to list. Returned
+ * names can be used as parameters to createReadStreamForMember.
+ * Must not remove elements from the list.
+ *
+ * @return the number of names added to list
+ */
+ virtual int listMembers(ArchiveMemberList &list) = 0;
+
+ /**
+ * Returns a ArchiveMember representation of the given file.
+ */
+ virtual ArchiveMemberPtr getMember(const String &name) = 0;
+
+ /**
+ * Create a stream bound to a member in the archive. If no member with the
+ * specified name exists, then 0 is returned.
+ * @return the newly created input stream
+ */
+ virtual SeekableReadStream *createReadStreamForMember(const String &name) const = 0;
+};
+
+
+/**
+ * SearchSet enables access to a group of Archives through the Archive interface.
+ * Its intended usage is a situation in which there are no name clashes among names in the
+ * contained Archives, hence the simplistic policy of always looking for the first
+ * match. SearchSet *DOES* guarantee that searches are performed in *DESCENDING*
+ * priority order. In case of conflicting priorities, insertion order prevails.
+ */
+class SearchSet : public Archive {
+ struct Node {
+ int _priority;
+ String _name;
+ Archive *_arc;
+ bool _autoFree;
+ Node(int priority, const String &name, Archive *arc, bool autoFree)
+ : _priority(priority), _name(name), _arc(arc), _autoFree(autoFree) {
+ }
+ };
+ typedef List<Node> ArchiveNodeList;
+ ArchiveNodeList _list;
+
+ ArchiveNodeList::iterator find(const String &name);
+ ArchiveNodeList::const_iterator find(const String &name) const;
+
+ // Add an archive keeping the list sorted by ascending priorities.
+ void insert(const Node& node);
+
+public:
+ virtual ~SearchSet() { clear(); }
+
+ /**
+ * Add a new archive to the searchable set.
+ */
+ void add(const String& name, Archive *arch, int priority = 0, bool autoFree = true);
+
+ /**
+ * Create and add a FSDirectory by name
+ */
+ void addDirectory(const String &name, const String &directory, int priority = 0, int depth = 1);
+
+ /**
+ * Create and add a FSDirectory by FSNode
+ */
+ void addDirectory(const String &name, const FSNode &directory, int priority = 0, int depth = 1);
+
+ /**
+ * Remove an archive from the searchable set.
+ */
+ void remove(const String& name);
+
+ /**
+ * Check if a given archive name is already present.
+ */
+ bool hasArchive(const String &name) const;
+
+ /**
+ * Empties the searchable set.
+ */
+ virtual void clear();
+
+ /**
+ * Change the order of searches.
+ */
+ void setPriority(const String& name, int priority);
+
+ virtual bool hasFile(const String &name);
+ virtual int listMatchingMembers(ArchiveMemberList &list, const String &pattern);
+ virtual int listMembers(ArchiveMemberList &list);
+
+ virtual ArchiveMemberPtr getMember(const String &name);
+
+ /**
+ * Implements createReadStreamForMember from Archive base class. The current policy is
+ * opening the first file encountered that matches the name.
+ */
+ virtual SeekableReadStream *createReadStreamForMember(const String &name) const;
+};
+
+
+class SearchManager : public Singleton<SearchManager>, public SearchSet {
+public:
+
+ /**
+ * Resets the search manager to the default list of search paths (system
+ * specific dirs + current dir).
+ */
+ virtual void clear();
+
+private:
+ friend class Common::Singleton<SingletonBaseType>;
+ SearchManager();
+};
+
+/** Shortcut for accessing the search manager. */
+#define SearchMan Common::SearchManager::instance()
+
+} // namespace Common
+
+#endif
Property changes on: residual/trunk/common/archive.h
___________________________________________________________________
Added: svn:mime-type
+ text/plain
Added: svn:keywords
+ Date Revision Author URL Id
Added: svn:eol-style
+ native
Modified: residual/trunk/common/config-file.cpp
===================================================================
--- residual/trunk/common/config-file.cpp 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/config-file.cpp 2009-04-05 14:48:54 UTC (rev 39864)
@@ -60,7 +60,7 @@
bool ConfigFile::loadFromFile(const String &filename) {
File file;
- if (file.open(filename, File::kFileReadMode))
+ if (file.open(filename))
return loadFromStream(file);
else
return false;
@@ -70,6 +70,7 @@
SaveFileManager *saveFileMan = g_driver->getSavefileManager();
SeekableReadStream *loadFile;
+ assert(saveFileMan);
if (!(loadFile = saveFileMan->openForLoading(filename)))
return false;
@@ -79,7 +80,6 @@
}
bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
- char buf[MAXLINELEN];
Section section;
KeyValue kv;
String comment;
@@ -88,22 +88,25 @@
// TODO: Detect if a section occurs multiple times (or likewise, if
// a key occurs multiple times inside one section).
- while (!stream.eos()) {
+ while (!stream.eos() && !stream.ioFailed()) {
lineno++;
- if (!stream.readLine(buf, MAXLINELEN))
- break;
- if (buf[0] == '#') {
+ // Read a line
+ String line = stream.readLine();
+
+ if (line.size() == 0) {
+ // Do nothing
+ } else if (line[0] == '#') {
// Accumulate comments here. Once we encounter either the start
// of a new section, or a key-value-pair, we associate the value
// of the 'comment' variable with that entity.
- comment += buf;
+ comment += line;
#ifdef _WIN32
comment += "\r\n";
#else
comment += "\n";
#endif
- } else if (buf[0] == '(') {
+ } else if (line[0] == '(') {
// HACK: The following is a hack added by Kirben to support the
// "map.ini" used in the HE SCUMM game "SPY Fox in Hold the Mustard".
//
@@ -111,15 +114,15 @@
// but the current design of this class doesn't allow to do that
// in a nice fashion (a "isMustard" parameter is *not* a nice
// solution).
- comment += buf;
+ comment += line;
#ifdef _WIN32
comment += "\r\n";
#else
comment += "\n";
#endif
- } else if (buf[0] == '[') {
+ } else if (line[0] == '[') {
// It's a new section which begins here.
- char *p = buf + 1;
+ const char *p = line.c_str() + 1;
// Get the section name, and check whether it's valid (that
// is, verify that it only consists of alphanumerics,
// dashes and underscores).
@@ -131,23 +134,25 @@
else if (*p != ']')
error("ConfigFile::loadFromStream: Invalid character '%c' occured in section name in line %d", *p, lineno);
- *p = 0;
-
// Previous section is finished now, store it.
if (!section.name.empty())
_sections.push_back(section);
- section.name = buf + 1;
+ section.name = String(line.c_str() + 1, p);
section.keys.clear();
section.comment = comment;
comment.clear();
assert(isValidName(section.name));
} else {
- // Skip leading & trailing whitespaces
- char *t = rtrim(ltrim(buf));
+ // This line should be a line with a 'key=value' pair, or an empty one.
- // Skip empty lines
+ // Skip leading whitespaces
+ const char *t = line.c_str();
+ while (isspace(*t))
+ t++;
+
+ // Skip empty lines / lines with only whitespace
if (*t == 0)
continue;
@@ -156,14 +161,20 @@
error("ConfigFile::loadFromStream: Key/value pair found outside a section in line %d", lineno);
}
- // Split string at '=' into 'key' and 'value'.
- char *p = strchr(t, '=');
+ // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
+ const char *p = strchr(t, '=');
if (!p)
- error("ConfigFile::loadFromStream: Junk found in line line %d: '%s'", lineno, t);
- *p = 0;
+ error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
- kv.key = rtrim(t);
- kv.value = ltrim(p + 1);
+ // Extract the key/value pair
+ kv.key = String(t, p);
+ kv.value = String(p + 1);
+
+ // Trim of spaces
+ kv.key.trim();
+ kv.value.trim();
+
+ // Store comment
kv.comment = comment;
comment.clear();
@@ -181,8 +192,8 @@
}
bool ConfigFile::saveToFile(const String &filename) {
- File file;
- if (file.open(filename, File::kFileWriteMode))
+ DumpFile file;
+ if (file.open(filename))
return saveToStream(file);
else
return false;
@@ -192,6 +203,7 @@
SaveFileManager *saveFileMan = g_driver->getSavefileManager();
WriteStream *saveFile;
+ assert(saveFileMan);
if (!(saveFile = saveFileMan->openForSaving(filename)))
return false;
@@ -245,7 +257,7 @@
void ConfigFile::removeSection(const String §ion) {
assert(isValidName(section));
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
- if (!strcasecmp(section.c_str(), i->name.c_str())) {
+ if (section.equalsIgnoreCase(i->name)) {
_sections.erase(i);
return;
}
@@ -338,7 +350,7 @@
ConfigFile::Section *ConfigFile::getSection(const String §ion) {
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
- if (!strcasecmp(section.c_str(), i->name.c_str())) {
+ if (section.equalsIgnoreCase(i->name)) {
return &(*i);
}
}
@@ -347,7 +359,7 @@
const ConfigFile::Section *ConfigFile::getSection(const String §ion) const {
for (List<Section>::const_iterator i = _sections.begin(); i != _sections.end(); ++i) {
- if (!strcasecmp(section.c_str(), i->name.c_str())) {
+ if (section.equalsIgnoreCase(i->name)) {
return &(*i);
}
}
@@ -360,7 +372,7 @@
const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const {
for (List<KeyValue>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
- if (!strcasecmp(key.c_str(), i->key.c_str())) {
+ if (key.equalsIgnoreCase(i->key)) {
return &(*i);
}
}
@@ -369,7 +381,7 @@
void ConfigFile::Section::setKey(const String &key, const String &value) {
for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
- if (!strcasecmp(key.c_str(), i->key.c_str())) {
+ if (key.equalsIgnoreCase(i->key)) {
i->value = value;
return;
}
@@ -383,7 +395,7 @@
void ConfigFile::Section::removeKey(const String &key) {
for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
- if (!strcasecmp(key.c_str(), i->key.c_str())) {
+ if (key.equalsIgnoreCase(i->key)) {
keys.erase(i);
return;
}
Modified: residual/trunk/common/config-file.h
===================================================================
--- residual/trunk/common/config-file.h 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/config-file.h 2009-04-05 14:48:54 UTC (rev 39864)
@@ -53,8 +53,6 @@
*/
class ConfigFile {
public:
- typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> StringSet;
-
struct KeyValue {
String key;
String value;
@@ -120,7 +118,6 @@
const SectionList getSections() const { return _sections; }
const SectionKeyList getKeys(const String §ion) const;
- void listSections(StringSet &set);
void listKeyValues(StringMap &kv);
private:
Modified: residual/trunk/common/config-manager.cpp
===================================================================
--- residual/trunk/common/config-manager.cpp 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/config-manager.cpp 2009-04-05 14:48:54 UTC (rev 39864)
@@ -31,30 +31,13 @@
#include "common/config-manager.h"
#include "common/file.h"
+#include "common/fs.h"
#include "common/util.h"
+#include "engine/backend/platform/driver.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/Residual Preferences"
-#else
-#define DEFAULT_CONFIG_FILE ".residualrc"
-#endif
-#else
-#define DEFAULT_CONFIG_FILE "residual.ini"
-#endif
-
-#define MAXLINELEN 256
-
static bool isValidDomainName(const Common::String &domName) {
const char *p = domName.c_str();
while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
@@ -85,230 +68,182 @@
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");
+ // Open the default config file
+ assert(g_driver);
+ SeekableReadStream *stream = g_driver->createConfigReadStream();
+ _filename.clear(); // clear the filename to indicate that we are using the default config file
- strcat(configFile, "\\Application Data");
- CreateDirectory(configFile, NULL);
- }
+ // ... load it, if available ...
+ if (stream)
+ loadFromStream(*stream);
- strcat(configFile, "\\Residual");
- CreateDirectory(configFile, NULL);
- strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
- } else {
- // Check windows directory
- GetWindowsDirectory(configFile, MAXPATHLEN);
- strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
- }
+ // ... and close it again.
+ delete stream;
- #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();
}
void ConfigManager::loadConfigFile(const String &filename) {
- _appDomain.clear();
- _gameDomains.clear();
- _transientDomain.clear();
-
_filename = filename;
- _domainSaveOrder.clear();
- loadFile(_filename);
- printf("Using configuration file: %s\n", _filename.c_str());
-}
-void ConfigManager::loadFile(const String &filename) {
+ FSNode node(filename);
File cfg_file;
-
- if (!cfg_file.open(filename)) {
+ if (!cfg_file.open(node)) {
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());
+ loadFromStream(cfg_file);
+ }
+}
- // TODO: Detect if a domain occurs multiple times (or likewise, if
- // a key occurs multiple times inside one domain).
+void ConfigManager::loadFromStream(SeekableReadStream &stream) {
+ String domain;
+ String comment;
+ int lineno = 0;
- while (!cfg_file.eof() && !cfg_file.ioFailed()) {
- lineno++;
+ _appDomain.clear();
+ _gameDomains.clear();
+ _transientDomain.clear();
+ _domainSaveOrder.clear();
- // Read a line
- String line;
+ // 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 = stream.readLine();
+
+ 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;
#ifdef _WIN32
- while (line.lastChar() != '\r' && line.lastChar() != '\n') {
+ comment += "\r\n";
#else
- while (line.lastChar() != '\n') {
+ comment += "\n";
#endif
- char buf[MAXLINELEN];
- if (!cfg_file.readLine_NEW(buf, MAXLINELEN))
- break;
- line += buf;
- }
+ } 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++;
- 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++;
+ if (*p == '\0')
+ error("Config file buggy: missing ] in line %d", lineno);
+ else if (*p != ']')
+ error("Config file buggy: Invalid character '%c' occurred in section name in line %d", *p, lineno);
- 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);
- }
+ domain = String(line.c_str() + 1, p);
- // 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++;
+ _gameDomains[domain].setDomainComment(comment);
+ }
+ comment.clear();
- // Skip empty lines / lines with only whitespace
- if (*t == 0)
- continue;
+ _domainSaveOrder.push_back(domain);
+ } else {
+ // This line should be a line with a 'key=value' pair, or an empty one.
- // 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);
- }
+ // Skip leading whitespaces
+ const char *t = line.c_str();
+ while (isspace(*t))
+ t++;
- // 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);
+ // Skip empty lines / lines with only whitespace
+ if (*t == 0)
+ continue;
- // 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++;
+ // 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);
+ }
- // Trim trailing spaces
- p2 = t + strlen(t);
- while (p2 > t && isspace(*(p2-1)))
- p2--;
+ // 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);
- String value(t, p2 - t);
+ // Extract the key/value pair
+ String key(t, p);
+ String value(p + 1);
- // Finally, store the key/value pair in the active domain
- set(key, value, domain);
+ // Trim of spaces
+ key.trim();
+ value.trim();
- // Store comment
- if (domain == kApplicationDomain) {
- _appDomain.setKVComment(key, comment);
- } else {
- _gameDomains[domain].setKVComment(key, comment);
- }
- comment.clear();
+ // 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__
- File cfg_file;
+ WriteStream *stream;
-// TODO
-// if (!willwrite)
-// return;
-
- if (!cfg_file.open(_filename, File::kFileWriteMode)) {
- warning("Unable to write configuration file: %s", _filename.c_str());
+ if (_filename.empty()) {
+ // Write to the default config file
+ assert(g_driver);
+ stream = g_driver->createConfigWriteStream();
+ 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;
}
- DomainMap::const_iterator d;
+ stream = dump;
+ }
-
- // 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__
}
@@ -316,6 +251,12 @@
if (domain.empty())
return; // Don't bother writing empty domains.
+ // WORKAROUND: Fix for bug #1972625 "ALL: On-the-fly targets are
+ // written to the config file": Do not save domains that came from
+ // the command line
+ if (domain.contains("id_came_from_command_line"))
+ return;
+
String comment;
// Write domain comment (if any)
@@ -646,6 +587,10 @@
// the given name already exists?
_gameDomains[domName];
+
+ // Add it to the _domainSaveOrder, if it's not already in there
+ if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), domName) == _domainSaveOrder.end())
+ _domainSaveOrder.push_back(domName);
}
void ConfigManager::removeGameDomain(const String &domName) {
Modified: residual/trunk/common/config-manager.h
===================================================================
--- residual/trunk/common/config-manager.h 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/config-manager.h 2009-04-05 14:48:54 UTC (rev 39864)
@@ -35,6 +35,7 @@
namespace Common {
class WriteStream;
+class SeekableReadStream;
/**
@@ -143,19 +144,11 @@
bool hasGameDomain(const String &domName) const;
const DomainMap & getGameDomains() const { return _gameDomains; }
-/*
- TODO: Callback/change notification system
- typedef void (*ConfigCallback)(const ConstString &key, void *refCon);
-
- void registerCallback(ConfigCallback cfgc, void *refCon, const ConstString &key = String::emptyString)
- void unregisterCallback(ConfigCallback cfgc, const ConstString &key = String::emptyString)
-*/
-
private:
friend class Singleton<SingletonBaseType>;
ConfigManager();
- void loadFile(const String &filename);
+ void loadFromStream(SeekableReadStream &stream);
void writeDomain(WriteStream &stream, const String &name, const Domain &domain);
Domain _transientDomain;
Modified: residual/trunk/common/error.h
===================================================================
--- residual/trunk/common/error.h 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/error.h 2009-04-05 14:48:54 UTC (rev 39864)
@@ -26,22 +26,45 @@
#ifndef COMMON_ERROR_H
#define COMMON_ERROR_H
+namespace Common {
+
/**
- * This file contains enums with error codes commonly used.
+ * This file contains an enum with commonly used error codes.
*/
+
+
/**
- * Errors used in the SaveFileManager class.
+ * Error codes which may be reported by plugins under various circumstances.
+ *
+ * @todo Clarify the names; add more codes, resp. verify all existing ones are acutally useful.
+ * Also, try to avoid overlap.
+ * @todo Maybe introduce a naming convention? E.g. k-NOUN/ACTION-CONDITION-Error, so
+ * kPathInvalidError would be correct, but these would not be: kInvalidPath,
+ * kPathInvalid, kPathIsInvalid, kInvalidPathError
*/
-enum SFMError {
- SFM_NO_ERROR, //Default state, indicates no error has been recorded
- SFM_DIR_ACCESS, //stat(), mkdir()::EACCES: Search or write permission denied
- SFM_DIR_LINKMAX, //mkdir()::EMLINK: The link count of the parent directory would exceed {LINK_MAX}
- SFM_DIR_LOOP, //stat(), mkdir()::ELOOP: Too many symbolic links encountered while traversing the path
- SFM_DIR_NAMETOOLONG, //stat(), mkdir()::ENAMETOOLONG: The path name is too long
- SFM_DIR_NOENT, //stat(), mkdir()::ENOENT: A component of the path path does not exist, or the path is an empty string
- SFM_DIR_NOTDIR, //stat(), mkdir()::ENOTDIR: A component of the path prefix is not a directory
- SFM_DIR_ROFS //mkdir()::EROFS: The parent directory resides on a read-only file system
+enum Error {
+ kNoError = 0, //!< No error occured
+ kInvalidPathError, //!< Engine initialization: Invalid game path was passed
+ kNoGameDataFoundError, //!< Engine initialization: No game data was found in the specified location
+ kUnsupportedGameidError, //!< Engine initialization: Gameid not supported by this (Meta)Engine
+
+
+ kReadPermissionDenied, //!< Unable to read data due to missing read permission
+ kWritePermissionDenied, //!< Unable to write data due to missing write permission
+
+ // The following three overlap a bit with kInvalidPathError and each other. Which to keep?
+ kPathDoesNotExist, //!< The specified path does not exist
+ kPathNotDirectory, //!< The specified path does not point to a directory
+ kPathNotFile, //!< The specified path does not point to a file
+
+ kCreatingFileFailed,
+ kReadingFailed, //!< Failed creating a (savestate) file
+ kWritingFailed, //!< Failure to write data -- disk full?
+
+ kUnknownError //!< Catch-all error, used if no other error code matches
};
+} // End of namespace Common
+
#endif //COMMON_ERROR_H
Modified: residual/trunk/common/file.cpp
===================================================================
--- residual/trunk/common/file.cpp 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/file.cpp 2009-04-05 14:48:54 UTC (rev 39864)
@@ -23,439 +23,98 @@
*
*/
+#include "common/archive.h"
+#include "common/debug.h"
#include "common/file.h"
#include "common/fs.h"
-#include "common/hashmap.h"
#include "common/util.h"
-#include "common/debug.h"
-#include "common/hash-str.h"
-#include <errno.h>
-#if defined(MACOSX) || defined(IPHONE)
-#include "CoreFoundation/CoreFoundation.h"
-#endif
-
-#ifdef __PLAYSTATION2__
- // for those replaced fopen/fread/etc functions
- typedef unsigned long uint64;
- typedef signed long int64;
- #include "backends/platform/ps2/fileio.h"
-
- #define fopen(a, b) ps2_fopen(a, b)
- #define fclose(a) ps2_fclose(a)
- #define fseek(a, b, c) ps2_fseek(a, b, c)
- #define ftell(a) ps2_ftell(a)
- #define feof(a) ps2_feof(a)
- #define fread(a, b, c, d) ps2_fread(a, b, c, d)
- #define fwrite(a, b, c, d) ps2_fwrite(a, b, c, d)
-
- //#define fprintf ps2_fprintf // used in common/util.cpp
- //#define fflush(a) ps2_fflush(a) // used in common/util.cpp
-
- //#define fgetc(a) ps2_fgetc(a) // not used
- //#define fgets(a, b, c) ps2_fgets(a, b, c) // not used
- //#define fputc(a, b) ps2_fputc(a, b) // not used
- //#define fputs(a, b) ps2_fputs(a, b) // not used
-
- //#define fsize(a) ps2_fsize(a) // not used -- and it is not a standard function either
-#endif
-
-#ifdef __DS__
-
- // These functions replease the standard library functions of the same name.
- // As this header is included after the standard one, I have the chance to #define
- // all of these to my own code.
- //
- // A #define is the only way, as redefinig the functions would cause linker errors.
-
- // These functions need to be #undef'ed, as their original definition
- // in devkitarm is done with #includes (ugh!)
- #undef feof
- #undef clearerr
- //#undef getc
- //#undef ferror
-
- #include "backends/fs/ds/ds-fs.h"
-
-
- //void std_fprintf(FILE* handle, const char* fmt, ...); // used in common/util.cpp
- //void std_fflush(FILE* handle); // used in common/util.cpp
-
- //char* std_fgets(char* str, int size, FILE* file); // not used
- //int std_getc(FILE* handle); // not used
- //char* std_getcwd(char* dir, int dunno); // not used
- //void std_cwd(char* dir); // not used
- //int std_ferror(FILE* handle); // not used
-
- // Only functions used in the ScummVM source have been defined here!
- #define fopen(name, mode) DS::std_fopen(name, mode)
- #define fclose(handle) DS::std_fclose(handle)
- #define fread(ptr, size, items, file) DS::std_fread(ptr, size, items, file)
- #define fwrite(ptr, size, items, file) DS::std_fwrite(ptr, size, items, file)
- #define feof(handle) DS::std_feof(handle)
- #define ftell(handle) DS::std_ftell(handle)
- #define fseek(handle, offset, whence) DS::std_fseek(handle, offset, whence)
- #define clearerr(handle) DS::std_clearerr(handle)
-
- //#define printf(fmt, ...) consolePrintf(fmt, ##__VA_ARGS__)
-
- //#define fprintf(file, fmt, ...) { char str[128]; sprintf(str, fmt, ##__VA_ARGS__); DS::std_fwrite(str, strlen(str), 1, file); }
- //#define fflush(file) DS::std_fflush(file) // used in common/util.cpp
-
- //#define fgets(str, size, file) DS::std_fgets(str, size, file) // not used
- //#define getc(handle) DS::std_getc(handle) // not used
- //#define getcwd(dir, dunno) DS::std_getcwd(dir, dunno) // not used
- //#define ferror(handle) DS::std_ferror(handle) // not used
-
-#endif
-
-#ifdef __SYMBIAN32__
- #undef feof
- #undef clearerr
-
- #define FILE void
-
- FILE* symbian_fopen(const char* name, const char* mode);
- void symbian_fclose(FILE* handle);
- size_t symbian_fread(const void* ptr, size_t size, size_t numItems, FILE* handle);
- size_t symbian_fwrite(const void* ptr, size_t size, size_t numItems, FILE* handle);
- bool symbian_feof(FILE* handle);
- long int symbian_ftell(FILE* handle);
- int symbian_fseek(FILE* handle, long int offset, int whence);
- void symbian_clearerr(FILE* handle);
-
- // Only functions used in the ScummVM source have been defined here!
- #define fopen(name, mode) symbian_fopen(name, mode)
- #define fclose(handle) symbian_fclose(handle)
- #define fread(ptr, size, items, file) symbian_fread(ptr, size, items, file)
- #define fwrite(ptr, size, items, file) symbian_fwrite(ptr, size, items, file)
- #define feof(handle) symbian_feof(handle)
- #define ftell(handle) symbian_ftell(handle)
- #define fseek(handle, offset, whence) symbian_fseek(handle, offset, whence)
- #define clearerr(handle) symbian_clearerr(handle)
-#endif
-
namespace Common {
-typedef HashMap<String, int> StringIntMap;
-
-// The following two objects could be turned into static members of class
-// File. However, then we would be forced to #include hashmap in file.h
-// which seems to be a high price just for a simple beautification...
-static StringIntMap *_defaultDirectories;
-static StringMap *_filesMap;
-
-static FILE *fopenNoCase(const String &filename, const String &directory, const char *mode) {
- FILE *file = NULL;
- String dirBuf(directory);
- String fileBuf(filename);
-
- if (fileBuf == "(stdin)") {
- return stdin;
- } else if (fileBuf == "(stdout)") {
- return stdout;
- } else if (fileBuf == "(stderr)") {
- return stderr;
- }
- if (file)
- return file;
-
-
-#if !defined(__GP32__) && !defined(PALMOS_MODE)
- // Add a trailing slash, if necessary.
- if (!dirBuf.empty()) {
- const char c = dirBuf.lastChar();
- if (c != ':' && c != '/' && c != '\\')
- dirBuf += '/';
- }
-#endif
-
- // Append the filename to the path string
- String pathBuf(dirBuf);
- pathBuf += fileBuf;
-
- //
- // Try to open the file normally
- //
- file = fopen(pathBuf.c_str(), mode);
-
- //
- // Try again, with file name converted to upper case
- //
- if (!file) {
- fileBuf.toUppercase();
- pathBuf = dirBuf + fileBuf;
- file = fopen(pathBuf.c_str(), mode);
- }
-
- //
- // Try again, with file name converted to lower case
- //
- if (!file) {
- fileBuf.toLowercase();
- pathBuf = dirBuf + fileBuf;
- file = fopen(pathBuf.c_str(), mode);
- }
-
- //
- // Try again, with file name capitalized
- //
- if (!file) {
- fileBuf.toLowercase();
- fileBuf.setChar(toupper(fileBuf[0]),0);
- pathBuf = dirBuf + fileBuf;
- file = fopen(pathBuf.c_str(), mode);
- }
-
-#ifdef __amigaos4__
- //
- // Work around for possibility that someone uses AmigaOS "newlib" build with SmartFileSystem (blocksize 512 bytes), leading
- // to buffer size being only 512 bytes. "Clib2" sets the buffer size to 8KB, resulting smooth movie playback. This forces the buffer
- // to be enough also when using "newlib" compile on SFS.
- //
- if (file) {
- setvbuf(file, NULL, _IOFBF, 8192);
- }
-#endif
-
- return file;
-}
-
void File::addDefaultDirectory(const String &directory) {
- FilesystemNode dir(directory);
- addDefaultDirectoryRecursive(dir, 1);
+ FSNode dir(directory);
+ addDefaultDirectory(dir);
}
-void File::addDefaultDirectoryRecursive(const String &directory, int level, const String &prefix) {
- FilesystemNode dir(directory);
- addDefaultDirectoryRecursive(dir, level, prefix);
+void File::addDefaultDirectory(const FSNode &dir) {
+ if (dir.exists() && dir.isDirectory())
+ SearchMan.addDirectory(dir.getPath(), dir);
}
-void File::addDefaultDirectory(const FilesystemNode &directory) {
- addDefaultDirectoryRecursive(directory, 1);
-}
-
-void File::addDefaultDirectoryRecursive(const FilesystemNode &dir, int level, const String &prefix) {
- if (level <= 0)
- return;
-
- FSList fslist;
- if (!dir.getChildren(fslist, FilesystemNode::kListAll)) {
- // Failed listing the contents of this node, so it is either not a
- // directory, or just doesn't exist at all.
- return;
- }
-
- if (!_defaultDirectories)
- _defaultDirectories = new StringIntMap;
-
- // Do not add directories multiple times, unless this time they are added
- // with a bigger depth.
- const String &directory(dir.getPath());
- if (_defaultDirectories->contains(directory) && (*_defaultDirectories)[directory] >= level)
- return;
- (*_defaultDirectories)[directory] = level;
-
- if (!_filesMap)
- _filesMap = new StringMap;
-
- for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
- if (file->isDirectory()) {
- addDefaultDirectoryRecursive(file->getPath(), level - 1, prefix + file->getName() + "/");
- } else {
- String lfn(prefix);
- lfn += file->getName();
- lfn.toLowercase();
- if (!_filesMap->contains(lfn)) {
- (*_filesMap)[lfn] = file->getPath();
- }
- }
- }
-}
-
-void File::resetDefaultDirectories() {
- delete _defaultDirectories;
- delete _filesMap;
-
- _defaultDirectories = 0;
- _filesMap = 0;
-}
-
File::File()
- : _handle(0), _ioFailed(false) {
+ : _handle(0) {
}
-//#define DEBUG_FILE_REFCOUNT
-
File::~File() {
-#ifdef DEBUG_FILE_REFCOUNT
- warning("File::~File on file '%s'", _name.c_str());
-#endif
close();
}
+bool File::open(const String &filename) {
+ return open(filename, SearchMan);
+}
-bool File::open(const String &filename, AccessMode mode) {
- assert(mode == kFileReadMode || mode == kFileWriteMode);
+bool File::open(const String &filename, Archive &archive) {
+ assert(!filename.empty());
+ assert(!_handle);
- if (filename.empty()) {
- error("File::open: No filename was specified");
- }
-
- if (_handle) {
- error("File::open: This file object already is opened (%s), won't open '%s'", _name.c_str(), filename.c_str());
- }
-
- _name.clear();
clearIOFailed();
- String fname(filename);
- fname.toLowercase();
-
- const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb";
- if (mode == kFileWriteMode) {
- _handle = fopenNoCase(filename, "", modeStr);
- } else if (_filesMap && _filesMap->contains(fname)) {
- fname = (*_filesMap)[fname];
- debug(3, "Opening hashed: %s", fname.c_str());
- _handle = fopen(fname.c_str(), modeStr);
- } else if (_filesMap && _filesMap->contains(fname + ".")) {
+ SeekableReadStream *stream = 0;
+
+ if ((stream = archive.createReadStreamForMember(filename))) {
+ debug(3, "Opening hashed: %s", filename.c_str());
+ } else if ((stream = archive.createReadStreamForMember(filename + "."))) {
// WORKAROUND: Bug #1458388: "SIMON1: Game Detection fails"
// sometimes instead of "GAMEPC" we get "GAMEPC." (note trailing dot)
- fname = (*_filesMap)[fname + "."];
- debug(3, "Opening hashed: %s", fname.c_str());
- _handle = fopen(fname.c_str(), modeStr);
- } else {
-
- if (_defaultDirectories) {
- // Try all default directories
- StringIntMap::const_iterator x(_defaultDirectories->begin());
- for (; _handle == NULL && x != _defaultDirectories->end(); ++x) {
- _handle = fopenNoCase(filename, x->_key, modeStr);
- }
- }
-
- // Last resort: try the current directory
- if (_handle == NULL)
- _handle = fopenNoCase(filename, "", modeStr);
-
- // Last last (really) resort: try looking inside the application bundle on Mac OS X for the lowercase file.
-#if defined(MACOSX) || defined(IPHONE)
- if (!_handle) {
- CFStringRef cfFileName = CFStringCreateWithBytes(NULL, (const UInt8 *)filename.c_str(), filename.size(), kCFStringEncodingASCII, false);
- CFURLRef fileUrl = CFBundleCopyResourceURL(CFBundleGetMainBundle(), cfFileName, NULL, NULL);
- if (fileUrl) {
- UInt8 buf[256];
- if (CFURLGetFileSystemRepresentation(fileUrl, false, (UInt8 *)buf, 256)) {
- _handle = fopen((char *)buf, modeStr);
- }
- CFRelease(fileUrl);
- }
- CFRelease(cfFileName);
- }
-#endif
-
+ debug(3, "Opening hashed: %s.", filename.c_str());
}
- if (_handle == NULL) {
- if (mode == kFileReadMode)
- debug(2, "File %s not found", filename.c_str());
- else
- debug(2, "File %s not opened", filename.c_str());
- return false;
- }
-
-
- _name = filename;
-
-#ifdef DEBUG_FILE_REFCOUNT
- warning("File::open on file '%s'", _name.c_str());
-#endif
-
- return true;
+ return open(stream, filename);
}
-bool File::open(const FilesystemNode &node, AccessMode mode) {
- assert(mode == kFileReadMode || mode == kFileWriteMode);
+bool File::open(const FSNode &node) {
+ assert(!_handle);
if (!node.exists()) {
- warning("File::open: Trying to open a FilesystemNode which does not exist");
+ warning("File::open: '%s' does not exist", node.getPath().c_str());
return false;
} else if (node.isDirectory()) {
- warning("File::open: Trying to open a FilesystemNode which is a directory");
+ warning("File::open: '%s' is a directory", node.getPath().c_str());
return false;
- } /*else if (!node.isReadable() && mode == kFileReadMode) {
- warning("File::open: Trying to open an unreadable FilesystemNode object for reading");
- return false;
- } else if (!node.isWritable() && mode == kFileWriteMode) {
- warning("File::open: Trying to open an unwritable FilesystemNode object for writing");
- return false;
- }*/
-
- String filename(node.getName());
-
- if (_handle) {
- error("File::open: This file object already is opened (%s), won't open '%s'", _name.c_str(), filename.c_str());
}
+ SeekableReadStream *stream = node.createReadStream();
+ return open(stream, node.getPath());
+}
+
+bool File::open(SeekableReadStream *stream, const Common::String &name) {
+ assert(!_handle);
clearIOFailed();
- _name.clear();
- const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb";
-
- _handle = fopen(node.getPath().c_str(), modeStr);
-
- if (_handle == NULL) {
- if (mode == kFileReadMode)
- debug(2, "File %s not found", filename.c_str());
- else
- debug(2, "File %s not opened", filename.c_str());
- return false;
+ if (stream) {
+ _handle = stream;
+ _name = name;
+ } else {
+ debug(2, "File::open: opening '%s' failed", name.c_str());
}
-
- _name = filename;
-
-#ifdef DEBUG_FILE_REFCOUNT
- warning("File::open on file '%s'", _name.c_str());
-#endif
-
- return true;
+ return _handle != NULL;
}
-bool File::exists(const String &filename) {
- // First try to find the file via a FilesystemNode (in case an absolute
- // path was passed). This is only used to filter out directories.
- FilesystemNode file(filename);
- if (file.exists())
- return !file.isDirectory();
- // See if the file is already mapped
- if (_filesMap && _filesMap->contains(filename)) {
- FilesystemNode file2((*_filesMap)[filename]);
-
- if (file2.exists())
- return !file2.isDirectory();
+bool File::exists(const String &filename) {
+ if (SearchMan.hasFile(filename)) {
+ return true;
+ } else if (SearchMan.hasFile(filename + ".")) {
+ // WORKAROUND: Bug #1458388: "SIMON1: Game Detection fails"
+ // sometimes instead of "GAMEPC" we get "GAMEPC." (note trailing dot)
+ return true;
}
- // Try all default directories
- if (_defaultDirectories) {
- StringIntMap::const_iterator i(_defaultDirectories->begin());
- for (; i != _defaultDirectories->end(); ++i) {
- FilesystemNode file2(i->_key + filename);
-
- if(file2.exists())
- return !file2.isDirectory();
- }
- }
-
- //Try opening the file inside the local directory as a last resort
- File tmp;
- return tmp.open(filename, kFileReadMode);
+ return false;
}
void File::close() {
- if (_handle && !(_handle == stdin || _handle == stdout || _handle == stderr))
- fclose((FILE *)_handle);
+ delete _handle;
_handle = NULL;
}
@@ -465,90 +124,108 @@
bool File::ioFailed() const {
// TODO/FIXME: Just use ferror() here?
- return _ioFailed != 0;
+ return !_handle || _handle->ioFailed();
}
void File::clearIOFailed() {
- // TODO/FIXME: Just use clearerr() here?
- _ioFailed = false;
+ if (_handle)
+ _handle->clearIOFailed();
}
-bool File::eof() const {
- if (_handle == NULL) {
- error("File::eof: File is not open!");
- return false;
- }
+bool File::err() const {
+ assert(_handle);
+ return _handle->err();
+}
- return feof((FILE *)_handle) != 0;
+void File::clearErr() {
+ assert(_handle);
+ _handle->clearErr();
}
-uint32 File::pos() const {
- if (_handle == NULL) {
- error("File::pos: File is not open!");
- return 0;
- }
+bool File::eos() const {
+ assert(_handle);
+ return _handle->eos();
+}
- return ftell((FILE *)_handle);
+int32 File::pos() const {
+ assert(_handle);
+ return _handle->pos();
}
-uint32 File::size() const {
- if (_handle == NULL) {
- error("File::size: File is not open!");
- return 0;
- }
+int32 File::size() const {
+ assert(_handle);
+ return _handle->size();
+}
- uint32 oldPos = ftell((FILE *)_handle);
- fseek((FILE *)_handle, 0, SEEK_END);
- uint32 length = ftell((FILE *)_handle);
- fseek((FILE *)_handle, oldPos, SEEK_SET);
+bool File::seek(int32 offs, int whence) {
+ assert(_handle);
+ return _handle->seek(offs, whence);
+}
- return length;
+uint32 File::read(void *ptr, uint32 len) {
+ assert(_handle);
+ return _handle->read(ptr, len);
}
-void File::seek(int32 offs, int whence) {
- if (_handle == NULL) {
- error("File::seek: File is not open!");
- return;
- }
- if (fseek((FILE *)_handle, offs, whence) != 0)
- clearerr((FILE *)_handle);
+DumpFile::DumpFile() : _handle(0) {
}
-uint32 File::read(void *ptr, uint32 len) {
- byte *ptr2 = (byte *)ptr;
- uint32 real_len;
+DumpFile::~DumpFile() {
+ close();
+}
- if (_handle == NULL) {
- error("File::read: File is not open!");
- return 0;
- }
+bool DumpFile::open(const String &filename) {
+ assert(!filename.empty());
+ assert(!_handle);
- if (len == 0)
- return 0;
+ FSNode node(filename);
+ return open(node);
+}
- real_len = fread(ptr2, 1, len, (FILE *)_handle);
- if (real_len < len) {
- _ioFailed = true;
+bool DumpFile::open(const FSNode &node) {
+ assert(!_handle);
+
+ if (node.isDirectory()) {
+ warning("DumpFile::open: FSNode is a directory");
+ return false;
}
- return real_len;
+ _handle = node.createWriteStream();
+
+ if (_handle == NULL)
+ debug(2, "File %s not found", node.getName().c_str());
+
+ return _handle != NULL;
}
-uint32 File::write(const void *ptr, uint32 len) {
- if (_handle == NULL) {
- error("File::write: File is not open!");
- return 0;
- }
+void DumpFile::close() {
+ delete _handle;
+ _handle = NULL;
+}
- if (len == 0)
- return 0;
+bool DumpFile::isOpen() const {
+ return _handle != NULL;
+}
- if ((uint32)fwrite(ptr, 1, len, (FILE *)_handle) != len) {
- _ioFailed = true;
- }
+bool DumpFile::err() const {
+ assert(_handle);
+ return _handle->ioFailed();
+}
- return len;
+void DumpFile::clearErr() {
+ assert(_handle);
+ _handle->clearIOFailed();
}
+uint32 DumpFile::write(const void *ptr, uint32 len) {
+ assert(_handle);
+ return _handle->write(ptr, len);
+}
+
+bool DumpFile::flush() {
+ assert(_handle);
+ return _handle->flush();
+}
+
} // End of namespace Common
Modified: residual/trunk/common/file.h
===================================================================
--- residual/trunk/common/file.h 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/file.h 2009-04-05 14:48:54 UTC (rev 39864)
@@ -27,46 +27,31 @@
#define COMMON_FILE_H
#include "common/sys.h"
+#include "common/noncopyable.h"
#include "common/str.h"
#include "common/stream.h"
-class FilesystemNode;
-
namespace Common {
-class File : public SeekableReadStream, public WriteStream {
+class Archive;
+class FSNode;
+
+/**
+ * TODO: vital to document this core class properly!!! For both users and implementors
+ */
+class File : public SeekableReadStream, public NonCopyable {
protected:
/** File handle to the actual file; 0 if no file is open. */
- void *_handle;
+ SeekableReadStream *_handle;
- /** Status flag which tells about recent I/O failures. */
- bool _ioFailed;
-
- /** The name of this file, for debugging. */
+ /** The name of this file, kept for debugging purposes. */
String _name;
-private:
- // Disallow copying File objects. There is not strict reason for this,
- // except that so far we never had real need for such a feature, and
- // code that accidentally copied File objects tended to break in strange
- // ways.
- File(const File &f);
- File &operator =(const File &f);
-
public:
- enum AccessMode {
- kFileReadMode = 1,
- kFileWriteMode = 2
- };
static void addDefaultDirectory(const String &directory);
- static void addDefaultDirectoryRecursive(const String &directory, int level = 4, const String &prefix = "");
+ static void addDefaultDirectory(const FSNode &directory);
- static void addDefaultDirectory(const FilesystemNode &directory);
- static void addDefaultDirectoryRecursive(const FilesystemNode &directory, int level = 4, const String &prefix = "");
-
- static void resetDefaultDirectories();
-
File();
virtual ~File();
@@ -75,14 +60,56 @@
* (those were/are added by addDefaultDirectory and/or
* addDefaultDirectoryRecursive).
*
- * @param filename: the file to check for
- * @return: true if the file exists, else false
+ * @param filename the file to check for
+ * @return true if the file exists, false otherwise
*/
static bool exists(const String &filename);
- virtual bool open(const String &filename, AccessMode mode = kFileReadMode);
- virtual bool open(const FilesystemNode &node, AccessMode mode = kFileReadMode);
+ /**
+ * Try to open the file with the given filename, by searching SearchMan.
+ * @note Must not be called if this file already is open (i.e. if isOpen returns true).
+ *
+ * @param filename the name of the file to open
+ * @return true if file was opened successfully, false otherwise
+ */
+ virtual bool open(const String &filename);
+ /**
+ * Try to open the file with the given filename from within the given archive.
+ * @note Must not be called if this file already is open (i.e. if isOpen returns true).
+ *
+ * @param filename the name of the file to open
+ * @param archive the archive in which to search for the file
+ * @return true if file was opened successfully, false otherwise
+ */
+ virtual bool open(const String &filename, Archive &archive);
+
+ /**
+ * Try to open the file corresponding to the give node. Will check whether the
+ * node actually refers to an existing file (and not a directory), and handle
+ * those cases gracefully.
+ * @note Must not be called if this file already is open (i.e. if isOpen returns true).
+ *
+ * @param filename the name of the file to open
+ * @param archive the archive in which to search for the file
+ * @return true if file was opened successfully, false otherwise
+ */
+ virtual bool open(const FSNode &node);
+
+ /**
+ * Try to 'open' the given stream. That is, we just wrap around it, and if stream
+ * is a NULL pointer, we gracefully treat this as if opening failed.
+ * @note Must not be called if this file already is open (i.e. if isOpen returns true).
+ *
+ * @param stream a pointer to a SeekableReadStream, or 0
+ * @param name a string describing the 'file' corresponding to stream
+ * @return true if stream was non-zero, false otherwise
+ */
+ virtual bool open(SeekableReadStream *stream, const Common::String &name);
+
+ /**
+ * Close the file, if open.
+ */
virtual void close();
/**
@@ -93,28 +120,58 @@
bool isOpen() const;
/**
- * Returns the filename of the opened file.
+ * Returns the filename of the opened file for debugging purposes.
*
* @return: the filename
*/
- const char *name() const { return _name.c_str(); }
+ const char *getName() const { return _name.c_str(); }
bool ioFailed() const;
void clearIOFailed();
- bool eos() const { return eof(); }
+ bool err() const;
+ void clearErr();
+ bool eos() const;
+ virtual int32 pos() const;
+ virtual int32 size() const;
+ bool seek(int32 offs, int whence = SEEK_SET);
+ uint32 read(void *dataPtr, uint32 dataSize);
+};
+
+
+/**
+ * TODO: document this class
+ *
+ * Some design ideas:
+ * - automatically drop all files into dumps/ dir? Might not be desired in all cases
+ */
+class DumpFile : public WriteStream, public NonCopyable {
+protected:
+ /** File handle to the actual file; 0 if no file is open. */
+ WriteStream *_handle;
+
+public:
+ DumpFile();
+ virtual ~DumpFile();
+
+ virtual bool open(const String &filename);
+ virtual bool open(const FSNode &node);
+
+ virtual void close();
+
/**
- * Checks for end of file.
+ * Checks if the object opened a file successfully.
*
- * @return: true if the end of file is reached, false otherwise.
+ * @return: true if any file is opened, false otherwise.
*/
- virtual bool eof() const;
+ bool isOpen() const;
- virtual uint32 pos() const;
- virtual uint32 size() const;
- void seek(int32 offs, int whence = SEEK_SET);
- uint32 read(void *dataPtr, uint32 dataSize);
- uint32 write(const void *dataPtr, uint32 dataSize);
+ bool err() const;
+ void clearErr();
+
+ virtual uint32 write(const void *dataPtr, uint32 dataSize);
+
+ virtual bool flush();
};
} // End of namespace Common
Modified: residual/trunk/common/fs.cpp
===================================================================
--- residual/trunk/common/fs.cpp 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/fs.cpp 2009-04-05 14:48:54 UTC (rev 39864)
@@ -8,18 +8,19 @@
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
- *
+
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
+ *
*/
#include "common/util.h"
@@ -27,81 +28,51 @@
#include "engine/backend/fs/abstract-fs.h"
#include "engine/backend/fs/fs-factory.h"
-static bool matchString(const char *str, const char *pat) {
- const char *p = 0;
- const char *q = 0;
+namespace Common {
- for (;;) {
- switch (*pat) {
- case '*':
- p = ++pat;
- q = str;
- break;
-
- default:
- if (*pat != *str) {
- if (p) {
- pat = p;
- str = ++q;
- if (!*str)
- return !*pat;
- break;
- }
- else
- return false;
- }
- // fallthrough
- case '?':
- if (!*str)
- return !*pat;
- pat++;
- str++;
- }
- }
+FSNode::FSNode() {
}
-FilesystemNode::FilesystemNode() {
-}
-
-FilesystemNode::FilesystemNode(AbstractFilesystemNode *realNode)
+FSNode::FSNode(AbstractFSNode *realNode)
: _realNode(realNode) {
}
-FilesystemNode::FilesystemNode(const Common::String &p) {
+FSNode::FSNode(const Common::String &p) {
+ assert(g_driver);
FilesystemFactory *factory = g_driver->getFilesystemFactory();
- AbstractFilesystemNode *tmp = 0;
-
+ AbstractFSNode *tmp = 0;
+
if (p.empty() || p == ".")
tmp = factory->makeCurrentDirectoryFileNode();
else
tmp = factory->makeFileNodePath(p);
- _realNode = Common::SharedPtr<AbstractFilesystemNode>(tmp);
+ _realNode = Common::SharedPtr<AbstractFSNode>(tmp);
}
-bool FilesystemNode::operator<(const FilesystemNode& node) const {
+bool FSNode::operator<(const FSNode& node) const {
if (isDirectory() != node.isDirectory())
return isDirectory();
- return strcasecmp(getDisplayName().c_str(), node.getDisplayName().c_str()) < 0;
+ return getDisplayName().compareToIgnoreCase(node.getDisplayName()) < 0;
}
-bool FilesystemNode::exists() const {
+bool FSNode::exists() const {
if (_realNode == 0)
return false;
return _realNode->exists();
}
-FilesystemNode FilesystemNode::getChild(const Common::String &n) const {
- if (_realNode == 0)
- return *this;
+FSNode FSNode::getChild(const Common::String &n) const {
+ // If this node is invalid or not a directory, return an invalid node
+ if (_realNode == 0 || !_realNode->isDirectory())
+ return FSNode();
- assert(_realNode->isDirectory());
- AbstractFilesystemNode *node = _realNode->getChild(n);
- return FilesystemNode(node);
+ AbstractFSNode *node = _realNode->getChild(n);
+ return FSNode(node);
}
-bool FilesystemNode::getChildren(FSList &fslist, ListMode mode, bool hidden) const {
+bool FSNode::getChildren(FSList &fslist, ListMode mode, bool hidden) const {
if (!_realNode || !_realNode->isDirectory())
return false;
@@ -112,94 +83,264 @@
fslist.clear();
for (AbstractFSList::iterator i = tmp.begin(); i != tmp.end(); ++i) {
- fslist.push_back(FilesystemNode(*i));
+ fslist.push_back(FSNode(*i));
}
return true;
}
-Common::String FilesystemNode::getDisplayName() const {
+Common::String FSNode::getDisplayName() const {
assert(_realNode);
return _realNode->getDisplayName();
}
-Common::String FilesystemNode::getName() const {
+Common::String FSNode::getName() const {
assert(_realNode);
return _realNode->getName();
}
-FilesystemNode FilesystemNode::getParent() const {
+FSNode FSNode::getParent() const {
if (_realNode == 0)
return *this;
- AbstractFilesystemNode *node = _realNode->getParent();
+ AbstractFSNode *node = _realNode->getParent();
if (node == 0) {
return *this;
} else {
- return FilesystemNode(node);
+ return FSNode(node);
}
}
-Common::String FilesystemNode::getPath() const {
+Common::String FSNode::getPath() const {
assert(_realNode);
return _realNode->getPath();
}
-bool FilesystemNode::isDirectory() const {
+bool FSNode::isDirectory() const {
if (_realNode == 0)
return false;
return _realNode->isDirectory();
}
-bool FilesystemNode::isReadable() const {
+bool FSNode::isReadable() const {
if (_realNode == 0)
return false;
return _realNode->isReadable();
}
-bool FilesystemNode::isWritable() const {
+bool FSNode::isWritable() const {
if (_realNode == 0)
return false;
return _realNode->isWritable();
}
-bool FilesystemNode::lookupFile(FSList &results, const Common::String &p, bool hidden, bool exhaustive, int depth) const {
- if (!isDirectory())
+Common::SeekableReadStream *FSNode::createReadStream() const {
+ if (_realNode == 0)
+ return 0;
+
+ if (!_realNode->exists()) {
+ warning("FSNode::createReadStream: FSNode does not exist");
return false;
+ } else if (_realNode->isDirectory()) {
+ warning("FSNode::createReadStream: FSNode is a directory");
+ return false;
+ }
- FSList children;
- FSList subdirs;
- Common::String pattern = p;
+ return _realNode->createReadStream();
+}
- pattern.toUppercase();
+Common::WriteStream *FSNode::createWriteStream() const {
+ if (_realNode == 0)
+ return 0;
- // First match all files on this level
- getChildren(children, FilesystemNode::kListAll, hidden);
- for (FSList::iterator entry = children.begin(); entry != children.end(); ++entry) {
- if (entry->isDirectory()) {
- if (depth != 0)
- subdirs.push_back(*entry);
- } else {
- Common::String filename = entry->getName();
- filename.toUppercase();
- if (matchString(filename.c_str(), pattern.c_str())) {
- results.push_back(*entry);
+ if (_realNode->isDirectory()) {
+ warning("FSNode::createWriteStream: FSNode is a directory");
+ return 0;
+ }
- if (!exhaustive)
- return true; // Abort on first match if no exhaustive search was requested
+ return _realNode->createWriteStream();
+}
+
+FSDirectory::FSDirectory(const FSNode &node, int depth)
+ : _node(node), _cached(false), _depth(depth) {
+}
+
+FSDirectory::FSDirectory(const String &prefix, const FSNode &node, int depth)
+ : _node(node), _cached(false), _depth(depth) {
+
+ setPrefix(prefix);
+}
+
+FSDirectory::FSDirectory(const String &name, int depth)
+ : _node(name), _cached(false), _depth(depth) {
+}
+
+FSDirectory::FSDirectory(const String &prefix, const String &name, int depth)
+ : _node(name), _cached(false), _depth(depth) {
+
+ setPrefix(prefix);
+}
+
+FSDirectory::~FSDirectory() {
+}
+
+void FSDirectory::setPrefix(const String &prefix) {
+ _prefix = prefix;
+
+ if (!_prefix.empty() && !_prefix.hasSuffix("/"))
+ _prefix += "/";
+}
+
+FSNode FSDirectory::getFSNode() const {
+ return _node;
+}
+
+FSNode *FSDirectory::lookupCache(NodeCache &cache, const String &name) const {
+ // make caching as lazy as possible
+ if (!name.empty()) {
+ ensureCached();
+
+ if (cache.contains(name))
+ return &cache[name];
+ }
+
+ return 0;
+}
+
+bool FSDirectory::hasFile(const String &name) {
+ if (name.empty() || !_node.isDirectory())
+ return false;
+
+ FSNode *node = lookupCache(_fileCache, name);
+ return node && node->exists();
+}
+
+ArchiveMemberPtr FSDirectory::getMember(const String &name) {
+ if (name.empty() || !_node.isDirectory())
+ return ArchiveMemberPtr();
+
+ FSNode *node = lookupCache(_fileCache, name);
+
+ if (!node || !node->exists()) {
+ warning("FSDirectory::getMember: FSNode does not exist");
+ return ArchiveMemberPtr();
+ } else if (node->isDirectory()) {
+ warning("FSDirectory::getMember: FSNode is a directory");
+ return ArchiveMemberPtr();
+ }
+
+ return ArchiveMemberPtr(new FSNode(*node));
+}
+
+SeekableReadStream *FSDirectory::createReadStreamForMember(const String &name) const {
+ if (name.empty() || !_node.isDirectory())
+ return 0;
+
+ FSNode *node = lookupCache(_fileCache, name);
+ if (!node)
+ return 0;
+ SeekableReadStream *stream = node->createReadStream();
+ if (!stream)
+ warning("FSDirectory::createReadStreamForMember: Can't create stream for file '%s'", name.c_str());
+
+ return stream;
+}
+
+FSDirectory *FSDirectory::getSubDirectory(const String &name, int depth) {
+ return getSubDirectory(String::emptyString, name, depth);
+}
+
+FSDirectory *FSDirectory::getSubDirectory(const String &prefix, const String &name, int depth) {
+ if (name.empty() || !_node.isDirectory())
+ return 0;
+
+ FSNode *node = lookupCache(_subDirCache, name);
+ if (!node)
+ return 0;
+
+ return new FSDirectory(prefix, *node, depth);
+}
+
+void FSDirectory::cacheDirectoryRecursive(FSNode node, int depth, const String& prefix) const {
+ if (depth <= 0)
+ return;
+
+ FSList list;
+ node.getChildren(list, FSNode::kListAll, false);
+
+ FSList::iterator it = list.begin();
+ for ( ; it != list.end(); ++it) {
+ String name = prefix + it->getName();
+
+ // don't touch name as it might be used for warning messages
+ String lowercaseName = name;
+ lowercaseName.toLowercase();
+
+ // since the hashmap is case insensitive, we need to check for clashes when caching
+ if (it->isDirectory()) {
+ if (_subDirCache.contains(lowercaseName)) {
+ warning("FSDirectory::cacheDirectory: name clash when building cache, ignoring sub-directory '%s'", name.c_str());
+ } else {
+ cacheDirectoryRecursive(*it, depth - 1, lowercaseName + "/");
+ _subDirCache[lowercaseName] = *it;
}
+ } else {
+ if (_fileCache.contains(lowercaseName)) {
+ warning("FSDirectory::cacheDirectory: name clash when building cache, ignoring file '%s'", name.c_str());
+ } else {
+ _fileCache[lowercaseName] = *it;
+ }
}
}
- // Now scan all subdirs
- for (FSList::iterator child = subdirs.begin(); child != subdirs.end(); ++child) {
- child->lookupFile(results, pattern, hidden, exhaustive, depth - 1);
- if (!exhaustive && !results.empty())
- return true; // Abort on first match if no exhaustive search was requested
+}
+
+void FSDirectory::ensureCached() const {
+ if (_cached)
+ return;
+ cacheDirectoryRecursive(_node, _depth, _prefix);
+ _cached = true;
+}
+
+int FSDirectory::listMatchingMembers(ArchiveMemberList &list, const String &pattern) {
+ if (!_node.isDirectory())
+ return 0;
+
+ // Cache dir data
+ ensureCached();
+
+ String lowercasePattern(pattern);
+ lowercasePattern.toLowercase();
+
+ int matches = 0;
+ NodeCache::iterator it = _fileCache.begin();
+ for ( ; it != _fileCache.end(); ++it) {
+ if (it->_key.matchString(lowercasePattern, true)) {
+ list.push_back(ArchiveMemberPtr(new FSNode(it->_value)));
+ matches++;
+ }
}
+ return matches;
+}
- return !results.empty();
+int FSDirectory::listMembers(ArchiveMemberList &list) {
+ if (!_node.isDirectory())
+ return 0;
+
+ // Cache dir data
+ ensureCached();
+
+ int files = 0;
+ for (NodeCache::iterator it = _fileCache.begin(); it != _fileCache.end(); ++it) {
+ list.push_back(ArchiveMemberPtr(new FSNode(it->_value)));
+ ++files;
+ }
+
+ return files;
}
+
+
+} // End of namespace Common
Modified: residual/trunk/common/fs.h
===================================================================
--- residual/trunk/common/fs.h 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/fs.h 2009-04-05 14:48:54 UTC (rev 39864)
@@ -8,68 +8,58 @@
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
- *
+
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
+ *
*/
#ifndef COMMON_FS_H
#define COMMON_FS_H
#include "common/array.h"
+#include "common/archive.h"
#include "common/ptr.h"
#include "common/str.h"
-//namespace Common {
+class AbstractFSNode;
-class FilesystemNode;
-class AbstractFilesystemNode;
+namespace Common {
+class FSNode;
+class SeekableReadStream;
+class WriteStream;
+
/**
* List of multiple file system nodes. E.g. the contents of a given directory.
* This is subclass instead of just a typedef so that we can use forward
* declarations of it in other places.
*/
-class FSList : public Common::Array<FilesystemNode> {};
+class FSList : public Array<FSNode> {};
/**
- * FilesystemNode provides an abstraction for file paths, allowing for portable
- * file system browsing. To this ends, multiple or single roots have to be supported
- * (compare Unix with a single root, Windows with multiple roots C:, D:, ...).
+ * FSNode, short for "File System Node", provides an abstraction for file
+ * paths, allowing for portable file system browsing. This means for example,
+ * that multiple or single roots have to be supported (compare Unix with a
+ * single root, Windows with multiple roots C:, D:, ...).
*
* To this end, we abstract away from paths; implementations can be based on
* paths (and it's left to them whether / or \ or : is the path separator :-);
* but it is also possible to use inodes or vrefs (MacOS 9) or anything else.
- *
- * NOTE: Backends still have to provide a way to extract a path from a FSIntern
- *
- * You may ask now: "isn't this cheating? Why do we go through all this when we use
- * a path in the end anyway?!?".
- * Well, for once as long as we don't provide our own file open/read/write API, we
- * still have to use fopen(). Since all our targets already support fopen(), it should
- * be possible to get a fopen() compatible string for any file system node.
- *
- * Secondly, with this abstraction layer, we still avoid a lot of complications based on
- * differences in FS roots, different path separators, or even systems with no real
- * paths (MacOS 9 doesn't even have the notion of a "current directory").
- * And if we ever want to support devices with no FS in the classical sense (Palm...),
- * we can build upon this.
- *
- * This class acts as a wrapper around the AbstractFilesystemNode class defined in backends/fs.
*/
-class FilesystemNode {
+class FSNode : public ArchiveMember {
private:
- Common::SharedPtr<AbstractFilesystemNode> _realNode;
- FilesystemNode(AbstractFilesystemNode *realNode);
+ SharedPtr<AbstractFSNode> _realNode;
+ FSNode(AbstractFSNode *realNode);
public:
/**
@@ -82,14 +72,14 @@
};
/**
- * Create a new pathless FilesystemNode. Since there's no path associated
+ * Create a new pathless FSNode. Since there's no path associated
* with this node, path-related operations (i.e. exists(), isDirectory(),
* getPath()) will always return false or raise an assertion.
*/
- FilesystemNode();
+ FSNode();
/**
- * Create a new FilesystemNode referring to the specified path. This is
+ * Create a new FSNode referring to the specified path. This is
* the counterpart to the path() method.
*
* If path is empty or equals ".", then a node representing the "current
@@ -97,35 +87,47 @@
* operating system doesn't support the concept), some other directory is
* used (usually the root directory).
*/
- explicit FilesystemNode(const Common::String &path);
+ explicit FSNode(const String &path);
- virtual ~FilesystemNode() {}
+ virtual ~FSNode() {}
/**
* Compare the name of this node to the name of another. Directories
* go before normal files.
*/
- bool operator<(const FilesystemNode& node) const;
+ bool operator<(const FSNode& node) const;
/**
- * Indicates whether the object referred by this path exists in the filesystem or not.
+ * Indicates whether the object referred by this node exists in the filesystem or not.
*
- * @return bool true if the path exists, false otherwise.
+ * @return bool true if the node exists, false otherwise.
*/
virtual bool exists() const;
/**
- * Fetch a child node of this node, with the given name. Only valid for
- * directory nodes (an assertion is triggered otherwise).
- * If no child node with the given name exists, an invalid node is returned.
+ * Create a new node referring to a child node of the current node, which
+ * must be a directory node (otherwise an invalid node is returned).
+ * If a child matching the name exists, a normal node for it is returned.
+ * If no child with the name exists, a node for it is still returned,
+ * but exists() will return 'false' for it. This node can however be used
+ * to create a new file using the createWriteStream() method.
+ *
+ * @todo If createWriteStream() (or a hypothetical future mkdir() method) is used,
+ * this should affect what exists/isDirectory/isReadable/isWritable return
+ * for existing nodes. However, this is not the case for many existing
+ * FSNode implementations. Either fix those, or document that FSNodes
+ * can become 'stale'...
+ *
+ * @param name the name of a child of this directory
+ * @return the node referring to the child with the given name
*/
- FilesystemNode getChild(const Common::String &name) const;
+ FSNode getChild(const String &name) const;
/**
- * Return a list of child nodes of this directory node. If called on a node
+ * Return a list of all child nodes of this directory node. If called on a node
* that does not represent a directory, false is returned.
*
- * @return true if succesful, false otherwise (e.g. when the directory does not exist).
+ * @return true if successful, false otherwise (e.g. when the directory does not exist).
*/
virtual bool getChildren(FSList &fslist, ListMode mode = kListDirectoriesOnly, bool hidden = false) const;
@@ -136,39 +138,40 @@
*
* @return the display name
*/
- virtual Common::String getDisplayName() const;
+ virtual String getDisplayName() const;
/**
- * Return a string representation of the name of the file. This is can be
+ * Return a string representation of the name of the file. This can be
* used e.g. by detection code that relies on matching the name of a given
* file. But it is *not* suitable for use with fopen / File::open, nor
* should it be archived.
*
* @return the file name
*/
- virtual Common::String getName() const;
+ virtual String getName() const;
/**
- * Return a string representation of the file which can be passed to fopen(),
- * and is suitable for archiving (i.e. writing to the config file).
- * This will usually be a 'path' (hence the name of the method), but can
- * be anything that fulfills the above criterions.
+ * Return a string representation of the file which is suitable for
+ * archiving (i.e. writing to the config file). This will usually be a
+ * 'path' (hence the name of the method), but can be anything that meets
+ * the above criterions. What a 'path' is differs greatly from system to
+ * system anyway.
*
* @note Do not assume that this string contains (back)slashes or any
* other kind of 'path separators'.
*
* @return the 'path' represented by this filesystem node
*/
- virtual Common::String getPath() const;
+ virtual String getPath() const;
/**
* Get the parent node of this node. If this node has no parent node,
* then it returns a duplicate of this node.
*/
- FilesystemNode getParent() const;
+ FSNode getParent() const;
/**
- * Indicates whether the path refers to a directory or not.
+ * Indicates whether the node refers to a directory or not.
*
* @todo Currently we assume that a node that is not a directory
* automatically is a file (ignoring things like symlinks or pipes).
@@ -179,50 +182,162 @@
virtual bool isDirectory() const;
/**
- * Indicates whether the object referred by this path can be read from or not.
+ * Indicates whether the object referred by this node can be read from or not.
*
- * If the path refers to a directory, readability implies being able to read
+ * If the node refers to a directory, readability implies being able to read
* and list the directory entries.
*
- * If the path refers to a file, readability implies being able to read the
+ * If the node refers to a file, readability implies being able to read the
* contents of the file.
*
- * @return bool true if the object can be read, false otherwise.
+ * @return true if the object can be read, false otherwise.
*/
virtual bool isReadable() const;
/**
- * Indicates whether the object referred by this path can be written to or not.
+ * Indicates whether the object referred by this node can be written to or not.
*
- * If the path refers to a directory, writability implies being able to modify
+ * If the node refers to a directory, writability implies being able to modify
* the directory entry (i.e. rename the directory, remove it or write files inside of it).
*
- * If the path refers to a file, writability implies being able to write data
+ * If the node refers to a file, writability implies being able to write data
* to the file.
*
- * @return bool true if the object can be written to, false otherwise.
+ * @return true if the object can be written to, false otherwise.
*/
virtual bool isWritable() const;
/**
- * Searches recursively for files matching the specified pattern inside this directory and
- * all its subdirectories. It is safe to call this method for non-directories, in this case
- * it will just return false.
+ * Creates a SeekableReadStream instance corresponding to the file
+ * referred by this node. This assumes that the node actually refers
+ * to a readable file. If this is not the case, 0 is returned.
*
- * The files in each directory are scanned first. Other than that, a depth first search
- * is performed.
+ * @return pointer to the stream object, 0 in case of a failure
+ */
+ virtual SeekableReadStream *createReadStream() const;
+
+ /**
+ * Creates a WriteStream instance corresponding to the file
+ * referred by this node. This assumes that the node actually refers
+ * to a readable file. If this is not the case, 0 is returned.
*
- * @param results List to put the matches in.
- * @param pattern Pattern of the files to look for.
- * @param hidden Whether to search hidden files or not.
- * @param exhaustive Whether to continue searching after one match has been found.
- * @param depth How many levels to search through (-1 = search all subdirs, 0 = only the current one)
- *
- * @return true if matches could be found, false otherwise.
+ * @return pointer to the stream object, 0 in case of a failure
*/
- virtual bool lookupFile(FSList &results, const Common::String &pattern, bool hidden, bool exhaustive, int depth = -1) const;
+ virtual WriteStream *createWriteStream() const;
};
-//} // End of namespace Common
+/**
+ * FSDirectory models a directory tree from the filesystem and allows users
+ * to access it through the Archive interface. Searching is case-insensitive,
+ * since the intended goal is supporting retrieval of game data.
+ *
+ * FSDirectory can represent a single directory, or a tree with specified depth,
+ * depending on the value passed to the 'depth' parameter in the constructors.
+ * Filenames are cached with their relative path, with elements separated by
+ * slashes, e.g.:
+ *
+ * c:\my\data\file.ext
+ *
+ * would be cached as 'data/file.ext' if FSDirectory was created on 'c:/my' with
+ * depth > 1. If depth was 1, then the 'data' subdirectory would have been
+ * ignored, instead.
+ * Again, only SLASHES are used as separators independently from the
+ * underlying file system.
+ *
+ * Relative paths can be specified when calling matching functions like createReadStreamForMember(),
+ * hasFile(), listMatchingMembers() and listMembers(). Please see the function
+ * specific comments for more information.
+ *
+ * Client code can customize cache by using the constructors with the 'prefix'
+ * parameter. In this case, the prefix is prepended to each entry in the cache,
+ * and effectively treated as a 'virtual' parent subdirectory. FSDirectory adds
+ * a trailing slash to prefix if needed. Following on with the previous example
+ * and using 'your' as prefix, the cache entry would have been 'your/data/file.ext'.
+ *
+ */
+class FSDirectory : public Archive {
+ FSNode _node;
+ String _prefix; // string that is prepended to each cache item key
+ void setPrefix(const String &prefix);
+
+ // Caches are case insensitive, clashes are dealt with when creating
+ // Key is stored in lowercase.
+ typedef HashMap<String, FSNode, IgnoreCase_Hash, IgnoreCase_EqualTo> NodeCache;
+ mutable NodeCache _fileCache, _subDirCache;
+ mutable bool _cached;
+ mutable int _depth;
+
+ // look for a match
+ FSNode *lookupCache(NodeCache &cache, const String &name) const;
+
+ // cache management
+ void cacheDirectoryRecursive(FSNode node, int depth, const String& prefix) const;
+
+ // fill cache if not already cached
+ void ensureCached() const;
+
+public:
+ /**
+ * Create a FSDirectory representing a tree with the specified depth. Will result in an
+ * unbound FSDirectory if name is not found on the filesystem or if the node is not a
+ * valid directory.
+ */
+ FSDirectory(const String &name, int depth = 1);
+ FSDirectory(const FSNode &node, int depth = 1);
+
+ /**
+ * Create a FSDirectory representing a tree with the specified depth. The parameter
+ * prefix is prepended to the keys in the cache. See class comment.
+ */
+ FSDirectory(const String &prefix, const String &name, int depth = 1);
+ FSDirectory(const String &prefix, const FSNode &node, int depth = 1);
+
+ virtual ~FSDirectory();
+
+ /**
+ * This return the underlying FSNode of the FSDirectory.
+ */
+ FSNode getFSNode() const;
+
+ /**
+ * Create a new FSDirectory pointing to a sub directory of the instance. See class comment
+ * for an explanation of the prefix parameter.
+ * @return a new FSDirectory instance
+ */
+ FSDirectory *getSubDirectory(const String &name, int depth = 1);
+ FSDirectory *getSubDirectory(const String &prefix, const String &name, int depth = 1);
+
+ /**
+ * Checks for existence in the cache. A full match of relative path and filename is needed
+ * for success.
+ */
+ virtual bool hasFile(const String &name);
+
+ /**
+ * Returns a list of matching file names. Pattern can use GLOB wildcards.
+ */
+ virtual int listMatchingMembers(ArchiveMemberList &list, const String &pattern);
+
+ /**
+ * Returns a list of all the files in the cache.
+ */
+ virtual int listMembers(ArchiveMemberList &list);
+
+ /**
+ * Get a ArchiveMember representation of the specified file. A full match of relative
+ * path and filename is needed for success.
+ */
+ virtual ArchiveMemberPtr getMember(const String &name);
+
+ /**
+ * Open the specified file. A full match of relative path and filename is needed
+ * for success.
+ */
+ virtual SeekableReadStream *createReadStreamForMember(const String &name) const;
+};
+
+
+} // End of namespace Common
+
#endif //COMMON_FS_H
Modified: residual/trunk/common/savefile.h
===================================================================
--- residual/trunk/common/savefile.h 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/savefile.h 2009-04-05 14:48:54 UTC (rev 39864)
@@ -39,27 +39,14 @@
* That typically means "save games", but also includes things like the
* IQ points in Indy3.
*/
-class InSaveFile : public SeekableReadStream {};
+typedef SeekableReadStream InSaveFile;
/**
* A class which allows game engines to save game state data.
* That typically means "save games", but also includes things like the
* IQ points in Indy3.
*/
-class OutSaveFile : public WriteStream {
-public:
- /**
- * Close this savefile, to be called right before destruction of this
- * savefile. The idea is that this ways, I/O errors that occur
- * during closing/flushing of the file can still be handled by the
- * game engine.
- *
- * By default, this just flushes the stream.
- */
- virtual void finalize() {
- flush();
- }
-};
+typedef WriteStream OutSaveFile;
/**
@@ -78,7 +65,7 @@
class SaveFileManager : NonCopyable {
protected:
- SFMError _error;
+ Error _error;
String _errorDesc;
/**
@@ -86,7 +73,7 @@
* @param error Code identifying the last error.
* @param errorDesc String describing the last error.
*/
- virtual void setError(SFMError error, const String &errorDesc) { _error = error; _errorDesc = errorDesc; }
+ virtual void setError(Error error, const String &errorDesc) { _error = error; _errorDesc = errorDesc; }
public:
virtual ~SaveFileManager() {}
@@ -94,14 +81,14 @@
/**
* Clears the last set error code and string.
*/
- virtual void clearError() { _error = SFM_NO_ERROR; _errorDesc = ""; }
+ virtual void clearError() { _error = kNoError; _errorDesc.clear(); }
/**
- * Returns the last occurred error code. If none occurred, returns SFM_NO_ERROR.
+ * Returns the last occurred error code. If none occurred, returns kNoError.
*
- * @return A SFMError indicating the type of the last error.
+ * @return A value indicating the type of the last error.
*/
- virtual SFMError getError() { return _error; }
+ virtual Error getError() { return _error; }
/**
* Returns the last occurred error description. If none occurred, returns 0.
@@ -149,11 +136,11 @@
/**
* Request a list of available savegames with a given DOS-style pattern,
- * also known as "glob" in the UNIX world. Refer to the Common::match()
+ * also known as "glob" in the UNIX world. Refer to the Common::matchString()
* function to learn about the precise pattern format.
* @param pattern Pattern to match. Wildcards like * or ? are available.
* @return list of strings for all present file names.
- * @see Common::match
+ * @see Common::matchString()
*/
virtual Common::StringList listSavefiles(const char *pattern) = 0;
};
Modified: residual/trunk/common/stream.cpp
===================================================================
--- residual/trunk/common/stream.cpp 2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/stream.cpp 2009-04-05 14:48:54 UTC (rev 39864)
@@ -43,8 +43,10 @@
uint32 MemoryReadStream::read(void *dataPtr, uint32 dataSize) {
// Read at most as many bytes as are still available...
- if (dataSize > _size - _pos)
+ if (dataSize > _size - _pos) {
dataSize = _size - _pos;
+ _eos = true;
+ }
memcpy(dataPtr, _ptr, dataSize);
if (_encbyte) {
@@ -60,14 +62,14 @@
return dataSize;
}
-void MemoryReadStream::seek(int32 offs, int whence) {
+bool MemoryReadStream::seek(int32 offs, int whence) {
// Pre-Condition
assert(_pos <= _size);
switch (whence) {
case SEEK_END:
// SEEK_END works just like SEEK_SET, only 'reversed',
// i.e. from the end.
- offs = _size - offs;
+ offs = _size + offs;
// Fall through
case SEEK_SET:
_ptr = _ptrOrig + offs;
@@ -81,94 +83,46 @@
}
// Post-Condition
assert(_pos <= _size);
+
+ // Reset end-of-stream flag on a successful seek
+ _eos = false;
+ return true; // FIXME: STREAM REWRITE
}
-#define LF 0x0A
-#define CR 0x0D
+enum {
+ LF = 0x0A,
+ CR = 0x0D
+};
-char *SeekableReadStream::readLine(char *buf, size_t bufSize) {
- assert(buf && bufSize > 0);
+char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) {
+ assert(buf != 0 && bufSize > 1);
char *p = buf;
size_t len = 0;
- char c;
+ char c = 0;
- if (buf == 0 || bufSize == 0 || eos()) {
+ // If end-of-file occurs before any characters are read, return NULL
+ // and the buffer contents remain unchanged.
+ if (eos() || err()) {
return 0;
}
- // We don't include the newline character(s) in the buffer, and we
- // always terminate it - we never read more than len-1 characters.
+ // Loop as long as there is still free space in the buffer,
+ // and the line has not ended
+ while (len + 1 < bufSize && c != LF) {
+ c = readByte();
- // EOF is treated as a line break, unless it was the first character
- // that was read.
+ if (eos()) {
+ // If end-of-file occurs before any characters are read, return
+ // NULL and the buffer contents remain unchanged.
+ if (len == 0)
+ return 0;
- // 0 is treated as a line break, even though it should never occur in
- // a text file.
-
- // DOS and Windows use CRLF line breaks
- // Unix and OS X use LF line breaks
- // Macintosh before OS X uses CR line breaks
-
-
- c = readByte();
- if (eos() || ioFailed()) {
- return 0;
- }
-
- while (!eos() && len + 1 < bufSize) {
-
- if (ioFailed())
- return 0;
-
- if (c == 0 || c == LF)
break;
-
- if (c == CR) {
- c = readByte();
- if (c != LF && !eos())
- seek(-1, SEEK_CUR);
- break;
}
- *p++ = c;
- len++;
-
- c = readByte();
- }
-
- // This should fix a bug while using readLine with Common::File
- // it seems that it sets the eos flag after an invalid read
- // and at the same time the ioFailed flag
- // the config file parser fails out of that reason for the new themes
- if (eos()) {
- clearIOFailed();
- }
-
- *p = 0;
- return buf;
-}
-
-char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) {
- assert(buf != 0 && bufSize > 1);
- char *p = buf;
- size_t len = 0;
- char c = 0;
-
- // If end-of-file occurs before any characters are read, return NULL
- // and the buffer contents remain unchanged.
- if (eos() || ioFailed()) {
- return 0;
- }
-
- // Loop as long as the stream has not ended, there is still free
- // space in the buffer, and the line has not ended
- while (!eos() && len + 1 < bufSize && c != LF) {
- c = readByte();
-
- // If end-of-file occurs before any characters are read, return
- // NULL and the buffer contents remain unchanged. If an error
- /// occurs, return NULL and the buffer contents are indeterminate.
- if (ioFailed() || (len == 0 && eos()))
+ // If an error occurs, return NULL and the buffer contents
+ // are indeterminate.
+ if (err())
return 0;
// Check for CR or CR/LF
@@ -178,35 +132,57 @@
if (c == CR) {
// Look at the next char -- is it LF? If not, seek back
c = readByte();
- if (c != LF && !eos())
+
+ if (err()) {
+ return 0; // error: the buffer contents are indeterminate
+ }
+ if (eos()) {
+ // The CR was the last character in the file.
+ // Reset the eos() flag since we successfully finished a line
+ clearErr();
+ } else if (c != LF) {
seek(-1, SEEK_CUR);
+ }
+
// Treat CR & CR/LF as plain LF
c = LF;
}
-
+
*p++ = c;
len++;
}
- // FIXME:
- // This should fix a bug while using readLine with Common::File
- // it seems that it sets the eos flag after an invalid read
- // and at the same time the ioFailed flag
- // the config file parser fails out of that reason for the new themes
- if (eos()) {
- clearIOFailed();
- }
-
// We always terminate the buffer if no error occured
*p = 0;
return buf;
}
+String SeekableReadStream::readLine() {
+ // Read a line
+ String line;
+ while (line.lastChar() != '\n') {
+ char buf[256];
+ if (!readLine_NEW(buf, 256))
+ break;
+ line += buf;
+ }
+ if (line.lastChar() == '\n')
+ line.deleteLastChar();
+
+ return line;
+}
+
+
+
uint32 SubReadStream::read(void *dataPtr, uint32 dataSize) {
- dataSize = MIN(dataSize, _end - _pos);
+ if (dataSize > _end - _pos) {
+ dataSize = _end - _pos;
+ _eos = true;
+ }
dataSize = _parentStream->read(dataPtr, dataSize);
+ _eos |= _parentStream->eos();
_pos += dataSize;
return dataSize;
@@ -219,15 +195,16 @@
assert(_begin <= _end);
_pos = _begin;
_parentStream->seek(_pos);
+ _eos = false;
}
-void SeekableSubReadStream::seek(int32 offset, int whence) {
+bool SeekableSubReadStream::seek(int32 offset, int whence) {
assert(_pos >= _begin);
assert(_pos <= _end);
switch(whence) {
case SEEK_END:
- offset = size() - offset;
+ offset = size() + offset;
// fallthrough
case SEEK_SET:
_pos = _begin + offset;
@@ -239,7 +216,91 @@
assert(_pos >= _begin);
assert(_pos <= _end);
- _parentStream->seek(_pos);
+ bool ret = _parentStream->seek(_pos);
+ if (ret) _eos = false; // reset eos on successful seek
+
+ return ret;
}
+BufferedReadStream::BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream)
+ : _parentStream(parentStream),
+ _disposeParentStream(disposeParentStream),
+ _pos(0),
+ _bufSize(0),
+ _realBufSize(bufSize) {
+
+ assert(parentStream);
+ _buf = new byte[bufSize];
+ assert(_buf);
+}
+
+BufferedReadStream::~BufferedReadStream() {
+ if (_disposeParentStream)
+ delete _parentStream;
+ delete _buf;
+}
+
+uint32 BufferedReadStream::read(void *dataPtr, uint32 dataSize) {
+ uint32 alreadyRead = 0;
+ const uint32 bufBytesLeft = _bufSize - _pos;
+
+ // Check whether the data left in the buffer suffices....
+ if (dataSize > bufBytesLeft) {
+ // Nope, we need to read more data
+
+ // First, flush the buffer, if it is non-empty
+ if (0 < bufBytesLeft) {
+ memcpy(dataPtr, _buf + _pos, bufBytesLeft);
+ _pos = _bufSize;
+ alreadyRead += bufBytesLeft;
+ dataPtr = (byte *)dataPtr + bufBytesLeft;
+ dataSize -= bufBytesLeft;
+ }
+
+ // At this point the buffer is empty. Now if the read request
+ // exceeds the buffer size, just satisfy it directly.
+ if (dataSize > _bufSize)
+ return alreadyRead + _parentStream->read(dataPtr, dataSize);
+
+ // Refill the buffer.
+ // If we didn't read as many bytes as requested, the reason
+ // is EOF or an error. In that case we truncate the buffer
+ // size, as well as the number of bytes we are going to
+ // return to the caller.
+ _bufSize = _parentStream->read(_buf, _realBufSize);
+ _pos = 0;
+ if (dataSize > _bufSize)
+ dataSize = _bufSize;
+ }
+
+ // Satisfy the request from the buffer
+ memcpy(dataPtr, _buf + _pos, dataSize);
+ _pos += dataSize;
+ return alreadyRead + dataSize;
+}
+
+BufferedSeekableReadStream::BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream)
+ : BufferedReadStream(parentStream, bufSize, disposeParentStream),
+ _parentStream(parentStream) {
+}
+
+bool BufferedSeekableReadStream::seek(int32 offset, int whence) {
+ // If it is a "local" seek, we may get away with "seeking" around
+ // in the buffer only.
+ // Note: We could try to handle SEEK_END and SEEK_SET, too, but
+ // since they are rarely used, it seems not worth the effort.
+ if (whence == SEEK_CUR && (int)_pos + offset >= 0 && _pos + offset <= _bufSize) {
+ _pos += offset;
+ } else {
+ // Seek was not local enough, so we reset the buffer and
+ // just seeks normally in the parent stream.
+ if (whence == SEEK_CUR)
+ offset -= (_bufSize - _pos);
+ _pos = _bufSize;
+ _parentStream->seek(offset, whence);
+ }
+
+ return true; // FIXME: STREAM REWRITE
+}
+
} // End of namespace Common
Modified: residual/trunk/common/stream.h
===================================================================
@@ Diff output truncated at 100000 characters. @@
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
More information about the Scummvm-git-logs
mailing list