diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/archive.cpp | 288 | ||||
-rw-r--r-- | common/archive.h | 201 |
2 files changed, 489 insertions, 0 deletions
diff --git a/common/archive.cpp b/common/archive.cpp new file mode 100644 index 0000000000..b963cf4ceb --- /dev/null +++ b/common/archive.cpp @@ -0,0 +1,288 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * 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/file.h" +#include "common/util.h" + +namespace Common { + + +FSDirectory::FSDirectory(const FilesystemNode &node, int depth) + : _node(node), _cached(false), _depth(depth) { +} + +FSDirectory::FSDirectory(const String &name, int depth) + : _node(name), _cached(false), _depth(depth) { +} + +FSDirectory::~FSDirectory() { +} + +FilesystemNode FSDirectory::getFSNode() const { + return _node; +} + +FilesystemNode FSDirectory::lookupCache(NodeCache &cache, const String &name) { + // make caching as lazy as possible + if (!name.empty()) { + if (!_cached) { + cacheDirectoryRecursive(_node, _depth, ""); + _cached = true; + } + + if (cache.contains(name)) + return cache[name]; + } + + return FilesystemNode(); +} + +bool FSDirectory::hasFile(const String &name) { + if (name.empty() || !_node.isDirectory()) { + return false; + } + + FilesystemNode node = lookupCache(_fileCache, name); + return node.exists(); +} + +FilePtr FSDirectory::openFile(const String &name) { + if (name.empty() || !_node.isDirectory()) { + return FilePtr(); + } + + FilesystemNode node = lookupCache(_fileCache, name); + + if (!node.exists()) { + warning("FSDirectory::openFile: Trying to open a FilesystemNode which does not exist"); + return FilePtr(); + } else if (node.isDirectory()) { + warning("FSDirectory::openFile: Trying to open a FilesystemNode which is a directory"); + return FilePtr(); + } + + SeekableReadStream *stream = node.openForReading(); + if (!stream) { + warning("FSDirectory::openFile: Can't create stream for file '%s'", name.c_str()); + } + + return FilePtr(stream); +} + +SharedPtr<FSDirectory> FSDirectory::getSubDirectory(const String &name) { + if (name.empty() || !_node.isDirectory()) { + // return a null SharedPtr + return SharedPtr<FSDirectory>(); + } + + FilesystemNode node = lookupCache(_subDirCache, name); + return SharedPtr<FSDirectory>(new FSDirectory(node)); +} + +void FSDirectory::cacheDirectoryRecursive(FilesystemNode node, int depth, const String& prefix) { + if (depth <= 0) { + return; + } + + FSList list; + node.getChildren(list, FilesystemNode::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; + } + } + } + +} + +int FSDirectory::matchPattern(StringList &list, const String &pattern) { + if (pattern.empty() || !_node.isDirectory()) { + return 0; + } + + if (!_cached) { + cacheDirectoryRecursive(_node, _depth, ""); + _cached = true; + } + + int matches = 0; + + // need to match lowercase key + String lowercasePattern = pattern; + lowercasePattern.toLowercase(); + + // Full *key* match, with path separators (backslashes) considered + // as normal characters. + NodeCache::iterator it = _fileCache.begin(); + for ( ; it != _fileCache.end(); it++) { + if (matchString((*it)._key.c_str(), lowercasePattern.c_str())) { + list.push_back((*it)._key.c_str()); + } + } + + return matches; +} + + + + + +SearchSet::SearchSet() { + +} + +SearchSet::~SearchSet() { + +} + +SearchSet::ArchiveList::iterator SearchSet::find(const String &name) const { + ArchiveList::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) { + ArchiveList::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, ArchivePtr archive, uint priority) { + if (find(name) == _list.end()) { + Node node = { priority, name, archive }; + insert(node); + } else { + warning("SearchSet::add: archive '%s' already present", name.c_str()); + } + +} + +void SearchSet::remove(const String& name) { + ArchiveList::iterator it = find(name); + if (it != _list.end()) { + _list.erase(it); + } +} + +void SearchSet::clear() { + _list.clear(); +} + +void SearchSet::setPriority(const String& name, uint priority) { + ArchiveList::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; + } + + ArchiveList::iterator it = _list.begin(); + for ( ; it != _list.end(); it++) { + if ((*it)._arc->hasFile(name)) { + return true; + } + } + + return false; +} + +int SearchSet::matchPattern(StringList &list, const String &pattern) { + // Shall we short circuit out if pattern is empty? + + int matches = 0; + + ArchiveList::iterator it = _list.begin(); + for ( ; it != _list.end(); it++) { + matches += (*it)._arc->matchPattern(list, pattern); + } + + return matches; +} + +FilePtr SearchSet::openFile(const String &name) { + if (name.empty()) { + return FilePtr(); + } + + ArchiveList::iterator it = _list.begin(); + for ( ; it != _list.end(); it++) { + if ((*it)._arc->hasFile(name)) { + return (*it)._arc->openFile(name); + } + } + + return FilePtr(); +} + + +} // namespace Common diff --git a/common/archive.h b/common/archive.h new file mode 100644 index 0000000000..636fb6383f --- /dev/null +++ b/common/archive.h @@ -0,0 +1,201 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * 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_ARCHIVES_H +#define COMMON_ARCHIVES_H + +#include "common/fs.h" +#include "common/str.h" +#include "common/hash-str.h" +#include "common/list.h" +#include "common/ptr.h" +#include "common/stream.h" + +namespace Common { + +typedef SharedPtr<SeekableReadStream> FilePtr; + +/** + * 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 names to the provided list according to the pattern. Returned + * names can be used as parameters to fileOpen. + * Must not remove elements from the list. + * + * @return The number of names added to list. + */ + virtual int matchPattern(StringList &list, const String &pattern) = 0; + + /** + * Add all the names present in the Archive. Returned + * names can be used as parameters to fileOpen. + * Must not remove elements from the list. + * + * @return The number of names added to list. + */ + virtual int getAllNames(StringList &list) { + return matchPattern(list, "*"); + } + + /** + * Create a stream bound to a file in the archive. + * @return The newly created input stream. + */ + virtual FilePtr openFile(const String &name) = 0; +}; + + +typedef SharedPtr<Archive> ArchivePtr; + + +/** + * FSDirectory models a directory tree from the filesystem and allows users + * to access it through the Archive interface. FSDirectory can represent a + * single directory, or a tree with specified depth, rooted in a 'base' + * directory. + * Searching is case-insensitive, as the main intended goal is supporting + * retrieval of game data. First case-insensitive match is returned when + * searching, thus making FSDirectory heavily dependant on the underlying + * FilesystemNode implementation. + */ +class FSDirectory : public Archive { + FilesystemNode _node; + + // Caches are case insensitive, clashes are dealt with when creating + // Key is stored in lowercase. + typedef HashMap<String, FilesystemNode, IgnoreCase_Hash, IgnoreCase_EqualTo> NodeCache; + NodeCache _fileCache, _subDirCache; + + // look for a match + FilesystemNode lookupCache(NodeCache &cache, const String &name); + + // cache management + void cacheDirectoryRecursive(FilesystemNode node, int depth, const String& prefix); + bool _cached; + int _depth; + +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 is not a directory. + */ + FSDirectory(const String &name, int depth = 1); + + /** + * Create a FSDirectory representing a tree with the specified depth. Will result in an + * unbound FSDirectory if node does not exist or is not a directory. + */ + FSDirectory(const FilesystemNode &node, int depth = 1); + + virtual ~FSDirectory(); + + /** + * This return the underlying FSNode of the FSDirectory. + */ + FilesystemNode getFSNode() const; + + /** + * Create a new FSDirectory pointing to a sub directory of the instance. + * @return A new FSDirectory instance conveniently wrapped in a SharedPtr. + */ + SharedPtr<FSDirectory> getSubDirectory(const String &name); + + virtual bool hasFile(const String &name); + virtual int matchPattern(StringList &list, const String &pattern); + virtual FilePtr openFile(const String &name); +}; + + + +/** + * 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 { + uint _priority; + String _name; + ArchivePtr _arc; + }; + typedef List<Node> ArchiveList; + ArchiveList _list; + + ArchiveList::iterator find(const String &name) const; + + // Add an archive keeping the list sorted by ascending priorities. + void insert(const Node& node); + +public: + SearchSet(); + virtual ~SearchSet(); + + /** + * Add a new Archive to the searchable set. + */ + void add(const String& name, ArchivePtr archive, uint priority = 0); + + /** + * Remove an Archive from the searchable set. + */ + void remove(const String& name); + + /** + * Empties the searchable set. + */ + void clear(); + + /** + * Change the order of searches. + */ + void setPriority(const String& name, uint priority); + + virtual bool hasFile(const String &name); + virtual int matchPattern(StringList &list, const String &pattern); + + /** + * Implements openFile from Archive base class. The current policy is + * opening the first file encountered that matches the name. + */ + virtual FilePtr openFile(const String &name); +}; + +} // namespace Common + +#endif |