1 /** @file filesystem.cpp File System.
2 *
3 * @author Copyright © 2009-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @author Copyright © 2013 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * LGPL: http://www.gnu.org/licenses/lgpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
15 * General Public License for more details. You should have received a copy of
16 * the GNU Lesser General Public License along with this program; if not, see:
17 * http://www.gnu.org/licenses</small>
18 */
19
20 #include "de/FS"
21
22 #include "de/App"
23 #include "de/ArchiveFeed"
24 #include "de/ArchiveFolder"
25 #include "de/DirectoryFeed"
26 #include "de/DictionaryValue"
27 #include "de/Guard"
28 #include "de/LibraryFile"
29 #include "de/Log"
30 #include "de/LogBuffer"
31 #include "de/Loop"
32 #include "de/NativePath"
33 #include "de/ScriptSystem"
34 #include "de/TextValue"
35 #include "de/ZipArchive"
36
37 #include <QHash>
38 #include <condition_variable>
39
40 namespace de {
41
42 static FileIndex const emptyIndex; // never contains any files
43
DENG2_PIMPL_NOREF(FileSystem)44 DENG2_PIMPL_NOREF(FileSystem)
45 {
46 std::mutex busyMutex;
47 int busyLevel = 0;
48 std::condition_variable busyFinished;
49
50 Record fsModule;
51
52 QList<filesys::IInterpreter const *> interpreters;
53
54 /// The main index to all files in the file system.
55 FileIndex index;
56
57 /// Index of file types. Each entry in the index is another index of names
58 /// to file instances.
59 typedef QHash<String, FileIndex *> TypeIndex; // owned
60 LockableT<TypeIndex> typeIndex;
61
62 QSet<FileIndex *> userIndices; // not owned
63
64 /// The root folder of the entire file system.
65 std::unique_ptr<Folder> root;
66
67 Impl()
68 {
69 root.reset(new Folder);
70
71 ScriptSystem::get().addNativeModule("FS", fsModule);
72 }
73
74 ~Impl()
75 {
76 root.reset();
77
78 DENG2_GUARD(typeIndex);
79 qDeleteAll(typeIndex.value);
80 typeIndex.value.clear();
81 }
82
83 FileIndex &getTypeIndex(String const &typeName)
84 {
85 DENG2_GUARD(typeIndex);
86 FileIndex *&idx = typeIndex.value[typeName];
87 if (!idx)
88 {
89 idx = new FileIndex;
90 }
91 return *idx;
92 }
93
94 DENG2_PIMPL_AUDIENCE(Busy)
95 };
96
DENG2_AUDIENCE_METHOD(FileSystem,Busy)97 DENG2_AUDIENCE_METHOD(FileSystem, Busy)
98
99 FileSystem::FileSystem() : d(new Impl)
100 {}
101
addInterpreter(filesys::IInterpreter const & interpreter)102 void FileSystem::addInterpreter(filesys::IInterpreter const &interpreter)
103 {
104 d->interpreters.prepend(&interpreter);
105 }
106
refreshAsync()107 void FileSystem::refreshAsync()
108 {
109 // We may need to wait until a previous population is complete.
110 Folder::afterPopulation([this] ()
111 {
112 LOG_AS("FS::refresh");
113 d->root->populate(Folder::PopulateAsyncFullTree);
114 });
115 }
116
makeFolder(String const & path,FolderCreationBehaviors behavior)117 Folder &FileSystem::makeFolder(String const &path, FolderCreationBehaviors behavior)
118 {
119 LOG_AS("FS::makeFolder");
120
121 Folder *subFolder = d->root->tryLocate<Folder>(path);
122 if (!subFolder)
123 {
124 // This folder does not exist yet. Let's create it.
125 // If the parent folder is missing, it won't be populated yet.
126 Folder &parentFolder = makeFolder(path.fileNamePath(), behavior & ~PopulateNewFolder);
127
128 // It is possible that the parent folder has already populated the folder
129 // we're looking for.
130 if (Folder *folder = parentFolder.tryLocate<Folder>(path.fileName()))
131 {
132 return *folder;
133 }
134
135 // Folders may be interpreted just like any other file; however, they must
136 // remain instances derived from Folder.
137 subFolder = &interpret(new Folder(path.fileName()))->as<Folder>();
138
139 // If parent folder is writable, this will be too.
140 if (parentFolder.mode() & File::Write)
141 {
142 subFolder->setMode(File::Write);
143 }
144
145 // Inherit parent's feeds?
146 if (behavior & (InheritPrimaryFeed | InheritAllFeeds))
147 {
148 DENG2_GUARD(parentFolder);
149 foreach (Feed *parentFeed, parentFolder.feeds())
150 {
151 Feed *feed = parentFeed->newSubFeed(subFolder->name());
152 if (!feed) continue; // Check next one instead.
153
154 LOGDEV_RES_XVERBOSE_DEBUGONLY("Creating subfeed \"%s\" from %s",
155 subFolder->name() << parentFeed->description());
156
157 subFolder->attach(feed);
158
159 if (!behavior.testFlag(InheritAllFeeds)) break;
160 }
161 }
162
163 parentFolder.add(subFolder);
164 index(*subFolder);
165
166 if (behavior.testFlag(PopulateNewFolder))
167 {
168 // Populate the new folder.
169 subFolder->populate();
170 }
171 }
172 return *subFolder;
173 }
174
makeFolderWithFeed(String const & path,Feed * feed,Folder::PopulationBehavior populationBehavior,FolderCreationBehaviors behavior)175 Folder &FileSystem::makeFolderWithFeed(String const &path, Feed *feed,
176 Folder::PopulationBehavior populationBehavior,
177 FolderCreationBehaviors behavior)
178 {
179 makeFolder(path.fileNamePath(), behavior);
180
181 Folder &folder = makeFolder(path, DontInheritFeeds /* we have a specific feed to attach */);
182 folder.clear();
183 folder.clearFeeds();
184 folder.attach(feed);
185 if (behavior & PopulateNewFolder)
186 {
187 folder.populate(populationBehavior);
188 }
189 return folder;
190 }
191
interpret(File * sourceData)192 File *FileSystem::interpret(File *sourceData)
193 {
194 DENG2_ASSERT(sourceData != nullptr);
195
196 LOG_AS("FS::interpret");
197 try
198 {
199 for (filesys::IInterpreter const *i : d->interpreters)
200 {
201 if (auto *file = i->interpretFile(sourceData))
202 {
203 return file;
204 }
205 }
206 }
207 catch (Error const &er)
208 {
209 LOG_RES_ERROR("Failed to interpret contents of %s: %s")
210 << sourceData->description() << er.asText();
211
212 // The error is one we don't know how to handle. We were given
213 // responsibility of the source file, so it has to be deleted.
214 delete sourceData;
215
216 throw;
217 }
218 return sourceData;
219 }
220
nameIndex() const221 FileIndex const &FileSystem::nameIndex() const
222 {
223 return d->index;
224 }
225
findAll(String const & path,FoundFiles & found) const226 int FileSystem::findAll(String const &path, FoundFiles &found) const
227 {
228 LOG_AS("FS::findAll");
229
230 found.clear();
231 d->index.findPartialPath(path, found);
232 return int(found.size());
233 }
234
forAll(String const & partialPath,std::function<LoopResult (File &)> func)235 LoopResult FileSystem::forAll(String const &partialPath, std::function<LoopResult (File &)> func)
236 {
237 FoundFiles files;
238 findAll(partialPath, files);
239 for (File *f : files)
240 {
241 if (auto result = func(*f)) return result;
242 }
243 return LoopContinue;
244 }
245
findAllOfType(String const & typeIdentifier,String const & path,FoundFiles & found) const246 int FileSystem::findAllOfType(String const &typeIdentifier, String const &path, FoundFiles &found) const
247 {
248 LOG_AS("FS::findAllOfType");
249
250 return findAllOfTypes(StringList() << typeIdentifier, path, found);
251 }
252
forAllOfType(String const & typeIdentifier,String const & path,std::function<LoopResult (File &)> func)253 LoopResult FileSystem::forAllOfType(String const &typeIdentifier, String const &path,
254 std::function<LoopResult (File &)> func)
255 {
256 FoundFiles files;
257 findAllOfType(typeIdentifier, path, files);
258 for (File *f : files)
259 {
260 if (auto result = func(*f)) return result;
261 }
262 return LoopContinue;
263 }
264
findAllOfTypes(StringList typeIdentifiers,String const & path,FoundFiles & found) const265 int FileSystem::findAllOfTypes(StringList typeIdentifiers, String const &path, FoundFiles &found) const
266 {
267 LOG_AS("FS::findAllOfTypes");
268
269 found.clear();
270 foreach (String const &id, typeIdentifiers)
271 {
272 indexFor(id).findPartialPath(path, found);
273 }
274 return int(found.size());
275 }
276
find(String const & path) const277 File &FileSystem::find(String const &path) const
278 {
279 return find<File>(path);
280 }
281
index(File & file)282 void FileSystem::index(File &file)
283 {
284 d->index.maybeAdd(file);
285
286 //qDebug() << "[FS] Indexing" << file.path() << DENG2_TYPE_NAME(file);
287
288 // Also make an entry in the type index.
289 d->getTypeIndex(DENG2_TYPE_NAME(file)).maybeAdd(file);
290
291 // Also offer to custom indices.
292 foreach (FileIndex *user, d->userIndices)
293 {
294 user->maybeAdd(file);
295 }
296 }
297
deindex(File & file)298 void FileSystem::deindex(File &file)
299 {
300 d->index.remove(file);
301 d->getTypeIndex(DENG2_TYPE_NAME(file)).remove(file);
302
303 // Also remove from any custom indices.
304 foreach (FileIndex *user, d->userIndices)
305 {
306 user->remove(file);
307 }
308 }
309
copySerialized(String const & sourcePath,String const & destinationPath,CopyBehaviors behavior)310 File &FileSystem::copySerialized(String const &sourcePath, String const &destinationPath,
311 CopyBehaviors behavior) // static
312 {
313 auto &fs = get();
314
315 Block contents;
316 *fs.root().locate<File const>(sourcePath).source() >> contents;
317
318 File *dest = &fs.root().replaceFile(destinationPath);
319 *dest << contents;
320 dest->flush();
321
322 if (behavior & ReinterpretDestination)
323 {
324 // We can now reinterpret and populate the contents of the archive.
325 //qDebug() << "[FS] Reinterpreting" << dest->path();
326 dest = dest->reinterpret();
327 }
328
329 if (behavior.testFlag(PopulateDestination) && is<Folder>(dest))
330 {
331 dest->as<Folder>().populate();
332 }
333
334 return *dest;
335 }
336
timeChanged(Clock const &)337 void FileSystem::timeChanged(Clock const &)
338 {
339 // perform time-based processing (indexing/pruning/refreshing)
340 }
341
changeBusyLevel(int increment)342 void FileSystem::changeBusyLevel(int increment)
343 {
344 using namespace std;
345
346 bool notify = false;
347 BusyStatus bs = Idle;
348 {
349 lock_guard<mutex> g(d->busyMutex);
350 const int oldLevel = d->busyLevel;
351 d->busyLevel += increment;
352 if (d->busyLevel == 0)
353 {
354 notify = true;
355 bs = Idle;
356 d->busyFinished.notify_all();
357 }
358 else if (oldLevel == 0)
359 {
360 notify = true;
361 bs = Busy;
362 }
363 }
364 if (notify)
365 {
366 Loop::mainCall([this, bs]() {
367 lock_guard<mutex> g(d->busyMutex);
368 // Only notify if the busy level is still up to date.
369 if ((bs == Busy && d->busyLevel > 0) ||
370 (bs == Idle && d->busyLevel == 0))
371 {
372 DENG2_FOR_AUDIENCE2(Busy, i) { i->fileSystemBusyStatusChanged(bs); }
373 }
374 });
375 }
376 }
377
busyLevel() const378 int FileSystem::busyLevel() const
379 {
380 std::lock_guard<std::mutex> lk(d->busyMutex);
381 return d->busyLevel;
382 }
383
waitForIdle()384 void FileSystem::waitForIdle() // static
385 {
386 using namespace std;
387
388 auto &fs = get();
389 unique_lock<mutex> lk(fs.d->busyMutex);
390 if (fs.d->busyLevel > 0)
391 {
392 LOG_MSG("Waiting until file system is ready");
393 fs.d->busyFinished.wait(lk);
394 }
395 }
396
indexFor(String const & typeName) const397 FileIndex const &FileSystem::indexFor(String const &typeName) const
398 {
399 return d->getTypeIndex(typeName);
400 }
401
addUserIndex(FileIndex & userIndex)402 void FileSystem::addUserIndex(FileIndex &userIndex)
403 {
404 d->userIndices.insert(&userIndex);
405 }
406
removeUserIndex(FileIndex & userIndex)407 void FileSystem::removeUserIndex(FileIndex &userIndex)
408 {
409 d->userIndices.remove(&userIndex);
410 }
411
printIndex()412 void FileSystem::printIndex()
413 {
414 if (!LogBuffer::get().isEnabled(LogEntry::Generic | LogEntry::Dev | LogEntry::Verbose))
415 return;
416
417 LOG_DEBUG("Main FS index has %i entries") << d->index.size();
418 d->index.print();
419
420 DENG2_GUARD_FOR(d->typeIndex, G);
421 DENG2_FOR_EACH_CONST(Impl::TypeIndex, i, d->typeIndex.value)
422 {
423 LOG_DEBUG("Index for type '%s' has %i entries") << i.key() << i.value()->size();
424
425 LOG_AS_STRING(i.key());
426 i.value()->print();
427 }
428 }
429
accessNativeLocation(NativePath const & nativePath,File::Flags flags)430 String FileSystem::accessNativeLocation(NativePath const &nativePath, File::Flags flags) // static
431 {
432 static const String SYS_NATIVE = "/sys/native";
433 static const String VAR_MAPPING = "accessNames";
434
435 auto &fs = get();
436
437 Folder &sysNative = fs.makeFolder(SYS_NATIVE);
438 if (!sysNative.objectNamespace().hasMember(VAR_MAPPING))
439 {
440 sysNative.objectNamespace().addDictionary(VAR_MAPPING);
441 }
442
443 // String accessPath = SYS_NATIVE;
444
445 // The accessed paths are kept in a dictionary so we'll know when the same path is
446 // reaccessed and needs to be replaced.
447 DictionaryValue &mapping = sysNative[VAR_MAPPING].value<DictionaryValue>();
448 const TextValue key(nativePath);
449 if (!mapping.contains(key))
450 {
451 // Generate a unique access path.
452 String name;
453 do
454 {
455 name = String("%1").arg(Rangei(0, 65536).random(), 4, 16, QChar('0'));
456 } while (sysNative.has(name));
457 mapping.setElement(key, new TextValue(name));
458 }
459
460 File &f = DirectoryFeed::manuallyPopulateSingleFile(
461 nativePath, fs.makeFolder(sysNative.path() / mapping[key].asText()));
462 f.setMode(flags);
463 return f.path();
464
465 /* String const path = folders / nativePath.fileNamePath().fileName();
466 if (!fs.root().has(path))
467 {
468 DirectoryFeed::Flags feedFlags = DirectoryFeed::OnlyThisFolder;
469 if (flags.testFlag(File::Write)) feedFlags |= DirectoryFeed::AllowWrite;
470 fs.makeFolderWithFeed(path,
471 new DirectoryFeed(nativePath.fileNamePath(), feedFlags),
472 Folder::PopulateOnlyThisFolder,
473 FS::DontInheritFeeds | FS::PopulateNewFolder)
474 .setMode(flags);
475 }*/
476 //return path / nativePath.fileName();
477 }
478
root()479 Folder &FileSystem::root()
480 {
481 return *d->root;
482 }
483
root() const484 Folder const &FileSystem::root() const
485 {
486 return *d->root;
487 }
488
rootFolder()489 Folder &FileSystem::rootFolder() // static
490 {
491 return get().root();
492 }
493
get()494 FileSystem &FileSystem::get() // static
495 {
496 return App::fileSystem();
497 }
498
499 } // namespace de
500