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