Skip to content

Commit

Permalink
Refactor|FS|libcore: Registering new File interpreters
Browse files Browse the repository at this point in the history
FileSystem is now allows registering any number of File interpreters.

Added interpreters in LibraryFile and ZipArchive.
  • Loading branch information
skyjake committed Oct 16, 2015
1 parent 8bacbcf commit 59ed3a7
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 59 deletions.
7 changes: 6 additions & 1 deletion doomsday/sdk/libcore/include/de/data/ziparchive.h
Expand Up @@ -14,14 +14,15 @@
* 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</small>
* http://www.gnu.org/licenses</small>
*/

#ifndef LIBDENG2_ZIPARCHIVE_H
#define LIBDENG2_ZIPARCHIVE_H

#include "../Archive"
#include "../NativePath"
#include "../filesys/IInterpreter"

namespace de {

Expand Down Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions doomsday/sdk/libcore/include/de/filesys/IInterpreter
@@ -0,0 +1 @@
#include "iinterpreter.h"
15 changes: 15 additions & 0 deletions doomsday/sdk/libcore/include/de/filesys/filesystem.h
Expand Up @@ -24,6 +24,7 @@
#include "../Folder"
#include "../FileIndex"
#include "../System"
#include "IInterpreter"

#include <QFlags>
#include <functional>
Expand Down Expand Up @@ -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();
Expand Down
54 changes: 54 additions & 0 deletions doomsday/sdk/libcore/include/de/filesys/iinterpreter.h
@@ -0,0 +1,54 @@
/** @file iinterpreter.h File interpreter.
*
* @authors Copyright (c) 2015 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* LGPL: http://www.gnu.org/licenses/lgpl.html
*
* <small>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</small>
*/

#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

9 changes: 7 additions & 2 deletions doomsday/sdk/libcore/include/de/filesys/libraryfile.h
Expand Up @@ -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</small>
* http://www.gnu.org/licenses</small>
*/

#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
Expand Down Expand Up @@ -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;
};
Expand Down
11 changes: 9 additions & 2 deletions doomsday/sdk/libcore/src/core/app.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down
50 changes: 49 additions & 1 deletion doomsday/sdk/libcore/src/data/ziparchive.cpp
Expand Up @@ -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</small>
* http://www.gnu.org/licenses</small>
*/

#include "de/ZipArchive"
Expand All @@ -30,6 +30,10 @@
#include "de/Date"
#include "de/Zeroed"

// Interpretations:
#include "de/game/SavedSession"
#include "de/ArchiveFolder"

#include <cstring>
#include <zlib.h>

Expand Down Expand Up @@ -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<ArchiveFolder> 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
64 changes: 16 additions & 48 deletions doomsday/sdk/libcore/src/filesys/filesystem.cpp
Expand Up @@ -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</small>
* http://www.gnu.org/licenses</small>
*/

#include "de/FS"
Expand All @@ -35,6 +35,8 @@ static FileIndex const emptyIndex; // never contains any files

DENG2_PIMPL_NOREF(FileSystem)
{
QList<filesys::IInterpreter const *> interpreters;

/// The main index to all files in the file system.
FileIndex index;

Expand All @@ -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");
Expand Down Expand Up @@ -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<ArchiveFolder> 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.
Expand Down

0 comments on commit 59ed3a7

Please sign in to comment.