1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/system.h"
24 #include "common/punycode.h"
25 #include "common/textconsole.h"
26 #include "backends/fs/abstract-fs.h"
27 #include "backends/fs/fs-factory.h"
28
29 namespace Common {
30
FSNode()31 FSNode::FSNode() {
32 }
33
FSNode(AbstractFSNode * realNode)34 FSNode::FSNode(AbstractFSNode *realNode)
35 : _realNode(realNode) {
36 }
37
FSNode(const Path & p)38 FSNode::FSNode(const Path &p) {
39 assert(g_system);
40 FilesystemFactory *factory = g_system->getFilesystemFactory();
41 AbstractFSNode *tmp = nullptr;
42
43 if (p.empty() || p == Path("."))
44 tmp = factory->makeCurrentDirectoryFileNode();
45 else
46 tmp = factory->makeFileNodePath(p.toString());
47 _realNode = SharedPtr<AbstractFSNode>(tmp);
48 }
49
operator <(const FSNode & node) const50 bool FSNode::operator<(const FSNode& node) const {
51 // Directories come before files, i.e., are "lower".
52 if (isDirectory() != node.isDirectory())
53 return isDirectory();
54
55 // If both nodes are of the same type (two files or two dirs),
56 // then sort by name, ignoring case.
57 return getName().compareToIgnoreCase(node.getName()) < 0;
58 }
59
exists() const60 bool FSNode::exists() const {
61 return _realNode && _realNode->exists();
62 }
63
getChild(const String & n) const64 FSNode FSNode::getChild(const String &n) const {
65 // If this node is invalid or not a directory, return an invalid node
66 if (_realNode == nullptr || !_realNode->isDirectory())
67 return FSNode();
68
69 AbstractFSNode *node = _realNode->getChild(n);
70 return FSNode(node);
71 }
72
getChildren(FSList & fslist,ListMode mode,bool hidden) const73 bool FSNode::getChildren(FSList &fslist, ListMode mode, bool hidden) const {
74 if (!_realNode || !_realNode->isDirectory())
75 return false;
76
77 AbstractFSList tmp;
78
79 if (!_realNode->getChildren(tmp, mode, hidden))
80 return false;
81
82 fslist.clear();
83 for (AbstractFSList::iterator i = tmp.begin(); i != tmp.end(); ++i) {
84 fslist.push_back(FSNode(*i));
85 }
86
87 return true;
88 }
89
getDisplayName() const90 U32String FSNode::getDisplayName() const {
91 assert(_realNode);
92 return _realNode->getDisplayName();
93 }
94
getName() const95 String FSNode::getName() const {
96 assert(_realNode);
97 // We transparently decode any punycode-named files
98 return punycode_decodefilename(_realNode->getName());
99 }
100
getParent() const101 FSNode FSNode::getParent() const {
102 if (_realNode == nullptr)
103 return *this;
104
105 AbstractFSNode *node = _realNode->getParent();
106 if (node == nullptr) {
107 return *this;
108 } else {
109 return FSNode(node);
110 }
111 }
112
getPath() const113 String FSNode::getPath() const {
114 assert(_realNode);
115 return _realNode->getPath();
116 }
117
isDirectory() const118 bool FSNode::isDirectory() const {
119 return _realNode && _realNode->isDirectory();
120 }
121
isReadable() const122 bool FSNode::isReadable() const {
123 return _realNode && _realNode->isReadable();
124 }
125
isWritable() const126 bool FSNode::isWritable() const {
127 return _realNode && _realNode->isWritable();
128 }
129
createReadStream() const130 SeekableReadStream *FSNode::createReadStream() const {
131 if (_realNode == nullptr)
132 return nullptr;
133
134 if (!_realNode->exists()) {
135 warning("FSNode::createReadStream: '%s' does not exist", getName().c_str());
136 return nullptr;
137 } else if (_realNode->isDirectory()) {
138 warning("FSNode::createReadStream: '%s' is a directory", getName().c_str());
139 return nullptr;
140 }
141
142 return _realNode->createReadStream();
143 }
144
createWriteStream() const145 SeekableWriteStream *FSNode::createWriteStream() const {
146 if (_realNode == nullptr)
147 return nullptr;
148
149 if (_realNode->isDirectory()) {
150 warning("FSNode::createWriteStream: '%s' is a directory", getName().c_str());
151 return nullptr;
152 }
153
154 return _realNode->createWriteStream();
155 }
156
createDirectory() const157 bool FSNode::createDirectory() const {
158 if (_realNode == nullptr)
159 return false;
160
161 if (_realNode->exists()) {
162 if (_realNode->isDirectory()) {
163 warning("FSNode::createDirectory: '%s' already exists", getName().c_str());
164 } else {
165 warning("FSNode::createDirectory: '%s' is a file", getName().c_str());
166 }
167 return false;
168 }
169
170 return _realNode->createDirectory();
171 }
172
FSDirectory(const FSNode & node,int depth,bool flat,bool ignoreClashes,bool includeDirectories)173 FSDirectory::FSDirectory(const FSNode &node, int depth, bool flat, bool ignoreClashes, bool includeDirectories)
174 : _node(node), _cached(false), _depth(depth), _flat(flat), _ignoreClashes(ignoreClashes),
175 _includeDirectories(includeDirectories) {
176 }
177
FSDirectory(const Path & prefix,const FSNode & node,int depth,bool flat,bool ignoreClashes,bool includeDirectories)178 FSDirectory::FSDirectory(const Path &prefix, const FSNode &node, int depth, bool flat,
179 bool ignoreClashes, bool includeDirectories)
180 : _node(node), _cached(false), _depth(depth), _flat(flat), _ignoreClashes(ignoreClashes),
181 _includeDirectories(includeDirectories) {
182
183 setPrefix(prefix.rawString());
184 }
185
FSDirectory(const Path & name,int depth,bool flat,bool ignoreClashes,bool includeDirectories)186 FSDirectory::FSDirectory(const Path &name, int depth, bool flat, bool ignoreClashes, bool includeDirectories)
187 : _node(name), _cached(false), _depth(depth), _flat(flat), _ignoreClashes(ignoreClashes),
188 _includeDirectories(includeDirectories) {
189 }
190
FSDirectory(const Path & prefix,const Path & name,int depth,bool flat,bool ignoreClashes,bool includeDirectories)191 FSDirectory::FSDirectory(const Path &prefix, const Path &name, int depth, bool flat,
192 bool ignoreClashes, bool includeDirectories)
193 : _node(name), _cached(false), _depth(depth), _flat(flat), _ignoreClashes(ignoreClashes),
194 _includeDirectories(includeDirectories) {
195
196 setPrefix(prefix.rawString());
197 }
198
~FSDirectory()199 FSDirectory::~FSDirectory() {
200 }
201
setPrefix(const String & prefix)202 void FSDirectory::setPrefix(const String &prefix) {
203 _prefix = prefix;
204
205 if (!_prefix.empty() && _prefix.lastChar() != DIR_SEPARATOR)
206 _prefix += DIR_SEPARATOR;
207 }
208
getFSNode() const209 FSNode FSDirectory::getFSNode() const {
210 return _node;
211 }
212
lookupCache(NodeCache & cache,const String & name) const213 FSNode *FSDirectory::lookupCache(NodeCache &cache, const String &name) const {
214 // make caching as lazy as possible
215 if (!name.empty()) {
216 ensureCached();
217
218 if (cache.contains(name))
219 return &cache[name];
220 }
221
222 return nullptr;
223 }
224
hasFile(const Path & path) const225 bool FSDirectory::hasFile(const Path &path) const {
226 String name = path.rawString();
227 if (name.empty() || !_node.isDirectory())
228 return false;
229
230 FSNode *node = lookupCache(_fileCache, name);
231 return node && node->exists();
232 }
233
getMember(const Path & path) const234 const ArchiveMemberPtr FSDirectory::getMember(const Path &path) const {
235 String name = path.rawString();
236 if (name.empty() || !_node.isDirectory())
237 return ArchiveMemberPtr();
238
239 FSNode *node = lookupCache(_fileCache, name);
240
241 if (!node || !node->exists()) {
242 warning("FSDirectory::getMember: '%s' does not exist", Common::toPrintable(name).c_str());
243 return ArchiveMemberPtr();
244 } else if (node->isDirectory()) {
245 warning("FSDirectory::getMember: '%s' is a directory", Common::toPrintable(name).c_str());
246 return ArchiveMemberPtr();
247 }
248
249 return ArchiveMemberPtr(new FSNode(*node));
250 }
251
createReadStreamForMember(const Path & path) const252 SeekableReadStream *FSDirectory::createReadStreamForMember(const Path &path) const {
253 String name = path.rawString();
254 if (name.empty() || !_node.isDirectory())
255 return nullptr;
256
257 FSNode *node = lookupCache(_fileCache, name);
258 if (!node)
259 return nullptr;
260 SeekableReadStream *stream = node->createReadStream();
261 if (!stream)
262 warning("FSDirectory::createReadStreamForMember: Can't create stream for file '%s'", Common::toPrintable(name).c_str());
263
264 return stream;
265 }
266
getSubDirectory(const Path & name,int depth,bool flat,bool ignoreClashes)267 FSDirectory *FSDirectory::getSubDirectory(const Path &name, int depth, bool flat, bool ignoreClashes) {
268 return getSubDirectory(Path(), name, depth, flat, ignoreClashes);
269 }
270
getSubDirectory(const Path & prefix,const Path & name,int depth,bool flat,bool ignoreClashes)271 FSDirectory *FSDirectory::getSubDirectory(const Path &prefix, const Path &name, int depth,
272 bool flat, bool ignoreClashes) {
273 String rawName = name.rawString();
274 if (rawName.empty() || !_node.isDirectory())
275 return nullptr;
276
277 FSNode *node = lookupCache(_subDirCache, rawName);
278 if (!node)
279 return nullptr;
280
281 return new FSDirectory(prefix, *node, depth, flat, ignoreClashes);
282 }
283
cacheDirectoryRecursive(FSNode node,int depth,const Path & prefix) const284 void FSDirectory::cacheDirectoryRecursive(FSNode node, int depth, const Path& prefix) const {
285 if (depth <= 0)
286 return;
287
288 FSList list;
289 node.getChildren(list, FSNode::kListAll);
290
291 FSList::iterator it = list.begin();
292 for ( ; it != list.end(); ++it) {
293 String name = prefix.rawString() + it->getName();
294
295 // don't touch name as it might be used for warning messages
296 String lowercaseName = name;
297 lowercaseName.toLowercase();
298
299 // since the hashmap is case insensitive, we need to check for clashes when caching
300 if (it->isDirectory()) {
301 if (!_flat && _subDirCache.contains(lowercaseName)) {
302 // Always warn in this case as it's when there are 2 directories at the same place with different case
303 // That means a problem in user installation as lookups are always done case insensitive
304 warning("FSDirectory::cacheDirectory: name clash when building cache, ignoring sub-directory '%s'",
305 Common::toPrintable(name).c_str());
306 } else {
307 if (_subDirCache.contains(lowercaseName)) {
308 if (!_ignoreClashes) {
309 warning("FSDirectory::cacheDirectory: name clash when building subDirCache with subdirectory '%s'",
310 Common::toPrintable(name).c_str());
311 }
312 }
313 cacheDirectoryRecursive(*it, depth - 1, _flat ? prefix : lowercaseName + DIR_SEPARATOR);
314 _subDirCache[lowercaseName] = *it;
315 }
316 } else {
317 if (_fileCache.contains(lowercaseName)) {
318 if (!_ignoreClashes) {
319 warning("FSDirectory::cacheDirectory: name clash when building cache, ignoring file '%s'",
320 Common::toPrintable(name).c_str());
321 }
322 } else {
323 _fileCache[lowercaseName] = *it;
324 }
325 }
326 }
327
328 }
329
ensureCached() const330 void FSDirectory::ensureCached() const {
331 if (_cached)
332 return;
333 cacheDirectoryRecursive(_node, _depth, _prefix);
334 _cached = true;
335 }
336
listMatchingMembers(ArchiveMemberList & list,const Path & pattern) const337 int FSDirectory::listMatchingMembers(ArchiveMemberList &list, const Path &pattern) const {
338 if (!_node.isDirectory())
339 return 0;
340
341 // Cache dir data
342 ensureCached();
343
344 // need to match lowercase key, since all entries in our file cache are
345 // stored as lowercase.
346 String lowercasePattern(pattern.rawString());
347 lowercasePattern.toLowercase();
348
349 // Prevent wildcards from matching the directory separator.
350 const char wildcardExclusions[] = { DIR_SEPARATOR, '\0' };
351
352 int matches = 0;
353 for (NodeCache::const_iterator it = _fileCache.begin(); it != _fileCache.end(); ++it) {
354 if (it->_key.matchString(lowercasePattern, false, wildcardExclusions)) {
355 list.push_back(ArchiveMemberPtr(new FSNode(it->_value)));
356 matches++;
357 }
358 }
359 if (_includeDirectories) {
360 for (NodeCache::const_iterator it = _subDirCache.begin(); it != _subDirCache.end(); ++it) {
361 if (it->_key.matchString(lowercasePattern, false, wildcardExclusions)) {
362 list.push_back(ArchiveMemberPtr(new FSNode(it->_value)));
363 matches++;
364 }
365 }
366 }
367
368 return matches;
369 }
370
listMembers(ArchiveMemberList & list) const371 int FSDirectory::listMembers(ArchiveMemberList &list) const {
372 if (!_node.isDirectory())
373 return 0;
374
375 // Cache dir data
376 ensureCached();
377
378 int files = 0;
379 for (NodeCache::const_iterator it = _fileCache.begin(); it != _fileCache.end(); ++it) {
380 list.push_back(ArchiveMemberPtr(new FSNode(it->_value)));
381 ++files;
382 }
383
384 if (_includeDirectories) {
385 for (NodeCache::const_iterator it = _subDirCache.begin(); it != _subDirCache.end(); ++it) {
386 list.push_back(ArchiveMemberPtr(new FSNode(it->_value)));
387 ++files;
388 }
389 }
390
391 return files;
392 }
393
394
395 } // End of namespace Common
396