From 59ed3a77b4710c8a3b529dcbbd1eacd02bba84d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Ker=C3=A4nen?= Date: Fri, 16 Oct 2015 20:35:27 +0300 Subject: [PATCH] Refactor|FS|libcore: Registering new File interpreters FileSystem is now allows registering any number of File interpreters. Added interpreters in LibraryFile and ZipArchive. --- .../sdk/libcore/include/de/data/ziparchive.h | 7 +- .../libcore/include/de/filesys/IInterpreter | 1 + .../libcore/include/de/filesys/filesystem.h | 15 +++++ .../libcore/include/de/filesys/iinterpreter.h | 54 ++++++++++++++++ .../libcore/include/de/filesys/libraryfile.h | 9 ++- doomsday/sdk/libcore/src/core/app.cpp | 11 +++- doomsday/sdk/libcore/src/data/ziparchive.cpp | 50 ++++++++++++++- .../sdk/libcore/src/filesys/filesystem.cpp | 64 +++++-------------- .../sdk/libcore/src/filesys/libraryfile.cpp | 23 +++++-- 9 files changed, 175 insertions(+), 59 deletions(-) create mode 100644 doomsday/sdk/libcore/include/de/filesys/IInterpreter create mode 100644 doomsday/sdk/libcore/include/de/filesys/iinterpreter.h diff --git a/doomsday/sdk/libcore/include/de/data/ziparchive.h b/doomsday/sdk/libcore/include/de/data/ziparchive.h index 44e0b444c0..2880793406 100644 --- a/doomsday/sdk/libcore/include/de/data/ziparchive.h +++ b/doomsday/sdk/libcore/include/de/data/ziparchive.h @@ -14,7 +14,7 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #ifndef LIBDENG2_ZIPARCHIVE_H @@ -22,6 +22,7 @@ #include "../Archive" #include "../NativePath" +#include "../filesys/IInterpreter" namespace de { @@ -98,6 +99,10 @@ class DENG2_PUBLIC ZipArchive : public Archive */ static bool recognize(NativePath const &path); + struct DENG2_PUBLIC Interpreter : public filesys::IInterpreter { + File *interpretFile(File *sourceData) const override; + }; + protected: void readFromSource(Entry const &entry, Path const &path, IBlock &uncompressedData) const; diff --git a/doomsday/sdk/libcore/include/de/filesys/IInterpreter b/doomsday/sdk/libcore/include/de/filesys/IInterpreter new file mode 100644 index 0000000000..f09f967e2d --- /dev/null +++ b/doomsday/sdk/libcore/include/de/filesys/IInterpreter @@ -0,0 +1 @@ +#include "iinterpreter.h" diff --git a/doomsday/sdk/libcore/include/de/filesys/filesystem.h b/doomsday/sdk/libcore/include/de/filesys/filesystem.h index a8f3c59b47..e64fc265ac 100644 --- a/doomsday/sdk/libcore/include/de/filesys/filesystem.h +++ b/doomsday/sdk/libcore/include/de/filesys/filesystem.h @@ -24,6 +24,7 @@ #include "../Folder" #include "../FileIndex" #include "../System" +#include "IInterpreter" #include #include @@ -116,6 +117,20 @@ class DENG2_PUBLIC FileSystem : public System */ FileSystem(); + /** + * Registers a new file content interpreter. + * + * A file interpreter takes a "raw" file (e.g., byte array) and provides + * access to the file contents in a high-level manner (e.g., an Image). + * Registered interpreters get used automatically when feeds populate + * folders with files. + * + * All registered interpreters are consulted in last-to-first order. + * + * @param interpreter Interpreter object. Ownership not taken. + */ + void addInterpreter(filesys::IInterpreter const &interpreter); + void printIndex(); Folder &root(); diff --git a/doomsday/sdk/libcore/include/de/filesys/iinterpreter.h b/doomsday/sdk/libcore/include/de/filesys/iinterpreter.h new file mode 100644 index 0000000000..8a8ab78a64 --- /dev/null +++ b/doomsday/sdk/libcore/include/de/filesys/iinterpreter.h @@ -0,0 +1,54 @@ +/** @file iinterpreter.h File interpreter. + * + * @authors Copyright (c) 2015 Jaakko Keränen + * + * @par License + * LGPL: http://www.gnu.org/licenses/lgpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 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 Lesser + * General Public License for more details. You should have received a copy of + * the GNU Lesser General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#ifndef LIBDENG2_FILESYS_IINTERPRETER_H +#define LIBDENG2_FILESYS_IINTERPRETER_H + +namespace de { + +class File; + +namespace filesys { + +/** + * File interpreter interface. + */ +class IInterpreter +{ +public: + virtual ~IInterpreter() {} + + /** + * Attempts to interpret a file. + * + * @param file File whose contents are being interpreted. If + * interpretation is possible, ownership of @a file is + * given to the interpreter. + * + * @return Interpreter for the file (ownership given to caller), or + * NULL if no possible interpretation was recognized (in which case + * ownership of @a file is retained by the caller). + */ + virtual File *interpretFile(File *file) const = 0; +}; + +} // namespace filesys +} // namespace de + +#endif // LIBDENG2_FILESYS_IINTERPRETER_H + diff --git a/doomsday/sdk/libcore/include/de/filesys/libraryfile.h b/doomsday/sdk/libcore/include/de/filesys/libraryfile.h index 9478602ee5..22e7ef2594 100644 --- a/doomsday/sdk/libcore/include/de/filesys/libraryfile.h +++ b/doomsday/sdk/libcore/include/de/filesys/libraryfile.h @@ -14,18 +14,19 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #ifndef LIBDENG2_LIBRARYFILE_H #define LIBDENG2_LIBRARYFILE_H #include "../File" +#include "IInterpreter" namespace de { class Library; - + /** * Provides a way to load and unload a shared library. The Library will be * loaded automatically when someone attempts to use it. Unloading the @@ -108,6 +109,10 @@ class DENG2_PUBLIC LibraryFile : public File */ static bool recognize(File const &file); + struct DENG2_PUBLIC Interpreter : public filesys::IInterpreter { + File *interpretFile(File *sourceData) const override; + }; + private: Library *_library; }; diff --git a/doomsday/sdk/libcore/src/core/app.cpp b/doomsday/sdk/libcore/src/core/app.cpp index 0048b775b3..cbc2b4b529 100644 --- a/doomsday/sdk/libcore/src/core/app.cpp +++ b/doomsday/sdk/libcore/src/core/app.cpp @@ -20,19 +20,20 @@ #include "de/Animation" #include "de/App" #include "de/ArchiveFeed" +#include "de/ArchiveFolder" #include "de/ArrayValue" #include "de/Block" #include "de/DictionaryValue" #include "de/DirectoryFeed" #include "de/FileLogSink" -#include "de/PackageFeed" +#include "de/LibraryFile" #include "de/Log" #include "de/LogBuffer" #include "de/LogFilter" #include "de/Module" #include "de/NativeFile" #include "de/NumberValue" -#include "de/ArchiveFolder" +#include "de/PackageFeed" #include "de/Record" #include "de/ScriptSystem" #include "de/Version" @@ -143,6 +144,12 @@ DENG2_PIMPL(App) // Built-in systems. systems << &fs << &scriptSys; + // Built-in file interpreters. + static LibraryFile::Interpreter intrpLibraryFile; + static ZipArchive::Interpreter intrpZipArchive; + fs.addInterpreter(intrpLibraryFile); + fs.addInterpreter(intrpZipArchive); + // Native App module. appModule.addArray("audienceForGameChange"); // game change observers scriptSys.addNativeModule("App", appModule); diff --git a/doomsday/sdk/libcore/src/data/ziparchive.cpp b/doomsday/sdk/libcore/src/data/ziparchive.cpp index a6c8beafff..c0c739e16b 100644 --- a/doomsday/sdk/libcore/src/data/ziparchive.cpp +++ b/doomsday/sdk/libcore/src/data/ziparchive.cpp @@ -14,7 +14,7 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #include "de/ZipArchive" @@ -30,6 +30,10 @@ #include "de/Date" #include "de/Zeroed" +// Interpretations: +#include "de/game/SavedSession" +#include "de/ArchiveFolder" + #include #include @@ -632,4 +636,48 @@ void ZipArchive::ZipEntry::update() } } +File *ZipArchive::Interpreter::interpretFile(File *sourceData) const +{ + if(recognize(*sourceData)) + { + try + { + // It is a ZIP archive: we will represent it as a folder. + std::unique_ptr package; + + if(sourceData->name().fileNameExtension() == ".save") + { + /// @todo fixme: Don't assume this is a save package. + LOG_RES_VERBOSE("Interpreted %s as a SavedSession") << sourceData->description(); + package.reset(new game::SavedSession(*sourceData, sourceData->name())); + } + else + { + LOG_RES_VERBOSE("Interpreted %s as a ZIP format archive") << sourceData->description(); + package.reset(new ArchiveFolder(*sourceData, sourceData->name())); + } + + // Archive opened successfully, give ownership of the source to the folder. + package->setSource(sourceData); + return package.release(); + } + catch(Archive::FormatError const &) + { + // Even though it was recognized as an archive, the file + // contents may still prove to be corrupted. + LOG_RES_WARNING("Archive in %s is invalid") << sourceData->description(); + } + catch(IByteArray::OffsetError const &) + { + LOG_RES_WARNING("Archive in %s is truncated") << sourceData->description(); + } + catch(IIStream::InputError const &er) + { + LOG_RES_WARNING("Failed to read %s") << sourceData->description(); + LOGDEV_RES_WARNING("%s") << er.asText(); + } + } + return nullptr; +} + } // namespace de diff --git a/doomsday/sdk/libcore/src/filesys/filesystem.cpp b/doomsday/sdk/libcore/src/filesys/filesystem.cpp index 6287432d54..40e6efb924 100644 --- a/doomsday/sdk/libcore/src/filesys/filesystem.cpp +++ b/doomsday/sdk/libcore/src/filesys/filesystem.cpp @@ -14,7 +14,7 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #include "de/FS" @@ -35,6 +35,8 @@ static FileIndex const emptyIndex; // never contains any files DENG2_PIMPL_NOREF(FileSystem) { + QList interpreters; + /// The main index to all files in the file system. FileIndex index; @@ -58,6 +60,11 @@ DENG2_PIMPL_NOREF(FileSystem) FileSystem::FileSystem() : d(new Instance) {} +void FileSystem::addInterpreter(filesys::IInterpreter const &interpreter) +{ + d->interpreters.prepend(&interpreter); +} + void FileSystem::refresh() { LOG_AS("FS::refresh"); @@ -136,62 +143,23 @@ Folder &FileSystem::makeFolderWithFeed(String const &path, Feed *feed, File *FileSystem::interpret(File *sourceData) { - LOG_AS("FS::interpret"); - - /// @todo One should be able to define new interpreters dynamically. + DENG2_ASSERT(sourceData != nullptr); + LOG_AS("FS::interpret"); try { - if(LibraryFile::recognize(*sourceData)) - { - LOG_RES_VERBOSE("Interpreted ") << sourceData->description() << " as a shared library"; - - // It is a shared library intended for Doomsday. - return new LibraryFile(sourceData); - } - if(ZipArchive::recognize(*sourceData)) + for(filesys::IInterpreter const *i : d->interpreters) { - try - { - // It is a ZIP archive: we will represent it as a folder. - std::auto_ptr package; - - if(sourceData->name().fileNameExtension() == ".save") - { - /// @todo fixme: Don't assume this is a save package. - LOG_RES_VERBOSE("Interpreted %s as a SavedSession") << sourceData->description(); - package.reset(new game::SavedSession(*sourceData, sourceData->name())); - } - else - { - LOG_RES_VERBOSE("Interpreted %s as a ZIP format archive") << sourceData->description(); - package.reset(new ArchiveFolder(*sourceData, sourceData->name())); - } - - // Archive opened successfully, give ownership of the source to the folder. - package->setSource(sourceData); - return package.release(); - } - catch(Archive::FormatError const &) - { - // Even though it was recognized as an archive, the file - // contents may still prove to be corrupted. - LOG_RES_WARNING("Archive in %s is invalid") << sourceData->description(); - } - catch(IByteArray::OffsetError const &) - { - LOG_RES_WARNING("Archive in %s is truncated") << sourceData->description(); - } - catch(IIStream::InputError const &er) + if(auto *file = i->interpretFile(sourceData)) { - LOG_RES_WARNING("Failed to read %s") << sourceData->description(); - LOGDEV_RES_WARNING("%s") << er.asText(); + return file; } } } - catch(Error const &err) + catch(Error const &er) { - LOG_RES_ERROR("") << err.asText(); + LOG_RES_ERROR("Failed to interpret contents of %s: %s") + << sourceData->description() << er.asText(); // The error is one we don't know how to handle. We were given // responsibility of the source file, so it has to be deleted. diff --git a/doomsday/sdk/libcore/src/filesys/libraryfile.cpp b/doomsday/sdk/libcore/src/filesys/libraryfile.cpp index dee8a3f4ff..e7f3e58b7d 100644 --- a/doomsday/sdk/libcore/src/filesys/libraryfile.cpp +++ b/doomsday/sdk/libcore/src/filesys/libraryfile.cpp @@ -14,7 +14,7 @@ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy of * the GNU Lesser General Public License along with this program; if not, see: - * http://www.gnu.org/licenses + * http://www.gnu.org/licenses */ #include "de/LibraryFile" @@ -36,8 +36,8 @@ LibraryFile::~LibraryFile() { DENG2_FOR_AUDIENCE2(Deletion, i) i->fileBeingDeleted(*this); audienceForDeletion().clear(); - - deindex(); + + deindex(); delete _library; } @@ -54,7 +54,7 @@ Library &LibraryFile::library() { return *_library; } - + /// @todo A method for File for making a NativeFile out of any File. NativeFile *native = source()->maybeAs(); if(!native) @@ -64,7 +64,7 @@ Library &LibraryFile::library() throw UnsupportedSourceError("LibraryFile::library", source()->description() + ": can only load from NativeFile"); } - + _library = new Library(native->nativePath()); return *_library; } @@ -125,3 +125,16 @@ bool LibraryFile::recognize(File const &file) #endif return false; } + +File *LibraryFile::Interpreter::interpretFile(File *sourceData) const +{ + if(recognize(*sourceData)) + { + LOG_RES_VERBOSE("Interpreted ") << sourceData->description() + << " as a shared library"; + + // It is a shared library intended for Doomsday. + return new LibraryFile(sourceData); + } + return nullptr; +}