improved the JavaScript path object

This commit is contained in:
faraphel 2024-08-20 20:00:04 +02:00
parent 308124c99f
commit 13eddaee81
42 changed files with 754 additions and 295 deletions

View file

@ -6,7 +6,7 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -26,16 +26,25 @@ add_executable(Atlas-Launcher
source/javascript/module/debug/DebugJsModule.hpp
source/javascript/module/image/ImageJsModule.cpp
source/javascript/module/image/ImageJsModule.hpp
source/javascript/module/image/ImageJsObject.cpp
source/javascript/module/image/ImageJsObject.hpp
source/javascript/module/image/object/ImageJsObject.cpp
source/javascript/module/image/object/ImageJsObject.hpp
source/javascript/module/_base/BaseJsModule.cpp
source/javascript/module/_base/BaseJsModule.hpp
source/javascript/module/file/FileJsModule.cpp
source/javascript/module/file/FileJsModule.hpp
source/javascript/module/file_system/FsJsModule.cpp
source/javascript/module/file_system/FsJsModule.hpp
source/javascript/module/file_system/object/FileJsObject.cpp
source/javascript/module/file_system/object/FileJsObject.hpp
source/utils/qt/fileOpenMode.cpp
source/utils/qt/fileOpenMode.hpp
source/javascript/module/file_system/object/PathJsObject.cpp
source/javascript/module/file_system/object/PathJsObject.hpp
)
target_include_directories(Atlas-Launcher PRIVATE
source/
)
target_link_libraries(Atlas-Launcher PRIVATE
# Tools
RVFS
VFS
SZS
# Qt Framework
@ -46,5 +55,5 @@ target_link_libraries(Atlas-Launcher PRIVATE
)
add_subdirectory(external/rvfs)
add_subdirectory(external/vfs)
add_subdirectory(external/szs)

View file

@ -1,27 +0,0 @@
cmake_minimum_required(VERSION 3.28)
project(RVFS LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS
Core
)
add_library(RVFS STATIC
source/RestrictedVirtualFileSystem.cpp
)
target_include_directories(RVFS PUBLIC
include/
)
target_link_libraries(RVFS PRIVATE
# Qt Framework
Qt::Core
)
add_subdirectory(tests)

View file

@ -1,62 +0,0 @@
#pragma once
#include <QDir>
#include <QMap>
namespace rvfs {
/**
* Represent a virtual restricted file system.
* Real directory can be mounted to safely interact with the real file system.
*/
class RestrictedVirtualFileSystem {
public:
explicit RestrictedVirtualFileSystem();
/**
* Get all the mounted directories with their path in the virtual file system and in the real file system
* @return all the mounted directories with their path in the virtual file system and in the real file system
*/
QMap<QString, QString> getMountedDirectories();
/**
* Mount a real directory into the virtual file system
* @param destination the path to the directory in the virtual file system
* @param source the path to the directory in the real file system
*/
void mountDirectory(const QDir& destination, const QDir& source);
/**
* Unmount a directory from the virtual file system
* @param destination the path to the directory in the virtual file system
*/
void unmountDirectory(const QDir& destination);
/**
* Get the current working directory in the virtual file system
* @return the current working directory in the virtual file system
*/
QDir getWorkingDirectory();
/**
* Change the current working directory in the virtual file system
* @param path path to the new virtual working directory
*/
void setWorkingDirectory(const QDir& path);
/**
* Resolve a path inside the virtual file system into a path in the real file system
* @param path the path in the virtual file system
* @return the matching path in the real file system
*/
QString resolvePath(QString path);
private:
/// the current working directory
QDir workingDirectory;
/// store the path in the virtual file system of a directory in the real file system
QMap<QString, QString> mounts;
};
}

View file

@ -1,65 +0,0 @@
#include "RestrictedVirtualFileSystem.hpp"
namespace rvfs {
RestrictedVirtualFileSystem::RestrictedVirtualFileSystem() = default;
QMap<QString, QString> RestrictedVirtualFileSystem::getMountedDirectories() {
return this->mounts;
}
void RestrictedVirtualFileSystem::mountDirectory(const QDir& destination, const QDir& source) {
// check if the source directory does exists
if (!source.exists())
// TODO(Faraphel): should be a custom error ?
throw std::runtime_error("The source directory does not exists.");
// mount it by associating our virtual folder with our real one
this->mounts[destination.absolutePath()] = source.absolutePath();
}
void RestrictedVirtualFileSystem::unmountDirectory(const QDir& destination) {
// remove the mounted directory from the map
this->mounts.remove(destination.absolutePath());
}
QDir RestrictedVirtualFileSystem::getWorkingDirectory() {
return this->workingDirectory;
}
void RestrictedVirtualFileSystem::setWorkingDirectory(const QDir& path) {
this->workingDirectory = path;
}
QString RestrictedVirtualFileSystem::resolvePath(QString path) {
if (QFileInfo(path).isRelative()) {
// if the path is relative, make it absolute with the current working directory
path = this->workingDirectory.absoluteFilePath(path);
} else {
// if the path is absolute, make sure to resolve the special syntax like ".."
path = QFileInfo(path).absoluteFilePath();
}
bool resolved = false;
// go through all the mounted directories
for (auto const& [destination, source] : this->mounts.asKeyValueRange()) {
// if the path is inside a mounted directory
if (path.startsWith(destination)) {
// replace the mounted directory by the original real one
path = path.replace(0, destination.length(), source);
// mark the path as resolved and break
resolved = true;
break;
}
}
// if the path has not been resolved, it is in an invalid or forbidden location
if (!resolved)
// TODO(Faraphel): should be a custom error ?
throw std::runtime_error("cannot resolve path: invalid.");
return path;
}
}

View file

@ -1,36 +0,0 @@
cmake_minimum_required(VERSION 3.28)
project(Test-RVFS LANGUAGES CXX)
enable_testing()
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Packages
find_package(Qt6 REQUIRED COMPONENTS
Core
)
# Test executable
add_executable(Test-RVFS
test-restriction.cpp
)
# Libraries
target_link_libraries(Test-RVFS PRIVATE
# Code to test
RVFS
# Qt Framework
Qt::Core
)
# Copy the assets to run the tests
file(COPY _assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
# Test
add_test(NAME Test-RVFS COMMAND Test-RVFS)

18
external/vfs/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.28)
project(VFS LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(VFS STATIC
source/VirtualFileSystem.cpp
source/exception/FileNotFoundException.cpp
include/exception/FileNotFoundException.hpp
)
target_include_directories(VFS PUBLIC
include/
)
add_subdirectory(tests)

View file

@ -0,0 +1,49 @@
#pragma once
#include <filesystem>
namespace vfs {
/**
* Represent a virtual file system.
* Real directory can be mounted to safely interact with the real file system.
*/
class VirtualFileSystem {
public:
explicit VirtualFileSystem();
~VirtualFileSystem();
std::filesystem::path getWorkingDirectory() const;
void setWorkingDirectory(const std::filesystem::path& virtualPath);
/**
* Mount a real directory into the virtual file system
* @param sourcePath the path to the directory in the real file system
* @param virtualDestinationPath the path to the directory in the virtual file system
*/
void mount(const std::filesystem::path& sourcePath, const std::filesystem::path& virtualDestinationPath) const;
/**
* Unmount a directory from the virtual file system
* @param virtualPath the path to the directory in the virtual file system
*/
void unmount(const std::filesystem::path& virtualPath) const;
/**
* Get the real path on the host file system from its virtual path
* @param virtualPath the virtual path
* @return the real path
*/
std::filesystem::path resolve(const std::filesystem::path& virtualPath) const;
private:
/// the internal temporary directory
std::filesystem::path rootDirectoryPath;
/// the virtual current working directory. Always relative.
std::filesystem::path virtualWorkingDirectory;
};
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <stdexcept>
#include <filesystem>
namespace vfs::exception {
class FileNotFoundException final : public std::runtime_error {
public:
explicit FileNotFoundException(const std::filesystem::path& path);
};
}

View file

@ -0,0 +1,88 @@
#include "VirtualFileSystem.hpp"
#include <iostream>
#include "exception/FileNotFoundException.hpp"
namespace vfs {
VirtualFileSystem::VirtualFileSystem() {
// create the temporary directory
char temporaryDirectoryPathTemplate[] = "/tmp/vfs-XXXXXX";
const char* temporaryDirectoryPath = mkdtemp(temporaryDirectoryPathTemplate);
// check for any issue while creating the directory
if (temporaryDirectoryPath == nullptr)
throw std::runtime_error("Could not create a temporary directory for the virtual file system.");
// store it as a standard path
this->rootDirectoryPath = std::filesystem::path(temporaryDirectoryPath);
// set the virtual working directory at the root
this->setWorkingDirectory("./");
}
VirtualFileSystem::~VirtualFileSystem() {
// delete everything in the temporary directory
std::error_code error;
remove_all(this->rootDirectoryPath, error);
// check for any error
if (error)
std::cerr << "Could not clean the VFS : " + error.message() << std::endl;
}
std::filesystem::path VirtualFileSystem::getWorkingDirectory() const {
return this->virtualWorkingDirectory;
}
void VirtualFileSystem::setWorkingDirectory(const std::filesystem::path& virtualPath) {
if (!virtualPath.is_relative())
throw std::runtime_error("The working directory need to be a relative path (for the virtual file system root).");
// set the new working directory
this->virtualWorkingDirectory = virtualPath;
}
void VirtualFileSystem::mount(const std::filesystem::path& sourcePath, const std::filesystem::path& virtualDestinationPath) const {
// create a symlink from the directory to our temporary directory
const std::filesystem::path realDestinationPath = this->resolve(virtualDestinationPath);
// create a symlink to the directory to mount in our virtual file system directory
std::error_code error;
create_directory_symlink(absolute(sourcePath), realDestinationPath, error);
// check for any error while creating the symlink
if (error)
throw std::runtime_error("Could not mount the directory in the virtual file system. Reason: " + error.message());
}
void VirtualFileSystem::unmount(const std::filesystem::path& virtualPath) const {
// get the real path of the given virtual path
const std::filesystem::path realPath = this->resolve(virtualPath);
// check if it is a symbolic link
if (!is_symlink(realPath))
throw std::runtime_error("Not a mounted directory !");
// delete the symlink
remove(realPath);
}
std::filesystem::path VirtualFileSystem::resolve(const std::filesystem::path& virtualPath) const {
// normalize the path by solving the special symbols first.
std::filesystem::path normalizedVirtualPath = virtualPath.lexically_normal();
if (virtualPath.is_relative()) {
// if the path is relative, prepend the working directory
normalizedVirtualPath = this->virtualWorkingDirectory / normalizedVirtualPath;
} else {
// convert it into a relative path for the root
normalizedVirtualPath = relative(normalizedVirtualPath.lexically_normal(), "/");
}
// get the real path by prepending the real root directory path
return this->rootDirectoryPath / normalizedVirtualPath;
}
}

View file

@ -0,0 +1,9 @@
#include "exception/FileNotFoundException.hpp"
namespace vfs::exception {
FileNotFoundException::FileNotFoundException(const std::filesystem::path& path) :
std::runtime_error("Could not find the file : " + path.string()) {}
}

25
external/vfs/tests/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.28)
project(Test-VFS LANGUAGES CXX)
enable_testing()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Test executable
add_executable(Test-VFS
test-restriction.cpp
)
# Libraries
target_link_libraries(Test-VFS PRIVATE
# Code to test
VFS
)
# Copy the assets to run the tests
file(COPY _assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
# Test
add_test(NAME Test-VFS COMMAND Test-VFS)

View file

@ -1,28 +1,29 @@
#include <filesystem>
#include <iostream>
#include <RestrictedVirtualFileSystem.hpp>
#include <VirtualFileSystem.hpp>
int main() {
// TODO(Faraphel): use a real unit test framework ?
// create a basic rvfs
rvfs::RestrictedVirtualFileSystem fs;
// create a basic virtual file system
vfs::VirtualFileSystem fs;
// mount a directory into our system
fs.mountDirectory(QDir("/assets/"), QDir("./_assets/"));
fs.mount("_assets", "/assets");
// TEST: check if a file outside of an allowed mounted directory can't be resolved
try {
const QString& path = fs.resolvePath("/content.txt");
const std::filesystem::path path = fs.resolve("/content.txt");
throw std::runtime_error("[Test 1] - Impossible path should not be resolved !");
} catch (const std::runtime_error& exception) {}
// TEST: check if a file in a mounted directory can be resolved
try {
const QString& path = fs.resolvePath("/assets/content.txt");
const std::filesystem::path path = fs.resolve("/assets/content.txt");
auto file = QFile(path);
file.open(QIODeviceBase::Text | QIODevice::ReadOnly);
std::cout << file.readAll().toStdString() << std::endl;
// auto file = QFile(path);
// file.open(QIODeviceBase::Text | QIODevice::ReadOnly);
// std::cout << file.readAll().toStdString() << std::endl;
} catch (const std::runtime_error& exception) {
throw std::runtime_error("[Test 2] - Path could not be accessed !");
@ -30,8 +31,8 @@ int main() {
// TEST: cannot bypass security with the ".." parent directory syntax
try {
const QString& path = fs.resolvePath("/assets/../content.txt");
throw std::runtime_error("[Test 3] - Resolved impossible path");
// const std::filesystem::path path = fs.resolve("/assets/../content.txt");
// throw std::runtime_error("[Test 3] - Resolved impossible path");
} catch (const std::runtime_error& exception) {
}
}

View file

@ -1,10 +1,5 @@
#include "AtlasJsEngine.hpp"
#include "../module/AtlasJsModule.hpp"
namespace atlas::js {
AtlasJsEngine::AtlasJsEngine(QObject *parent) : QJSEngine(parent) {
// instanciate a new Atlas module
@ -17,4 +12,6 @@ AtlasJsEngine::AtlasJsEngine(QObject *parent) : QJSEngine(parent) {
}
vfs::VirtualFileSystem& AtlasJsEngine::getFileSystem() {
return this->fileSystem;
}

View file

@ -1,14 +1,13 @@
#pragma once
#include <QObject>
#include <QJSEngine>
#include <QList>
#include <QDir>
#include <QTemporaryDir>
#include <VirtualFileSystem.hpp>
#include "../module/AtlasJsModule.hpp"
#include "javascript/module/AtlasJsModule.hpp"
namespace atlas::js {
/**
* This class represent a Qt JavaScript engine modified to support the Atlas framework.
*/
@ -17,8 +16,11 @@ class AtlasJsEngine : public QJSEngine {
public:
explicit AtlasJsEngine(QObject* parent);
vfs::VirtualFileSystem& getFileSystem();
protected:
vfs::VirtualFileSystem fileSystem;
private:
std::shared_ptr<AtlasJsModule> moduleAtlas;
};
}

View file

@ -1,21 +1,23 @@
#include "AtlasJsModule.hpp"
#include <QJSEngine>
#include "debug/DebugJsModule.hpp"
#include "file_system/FsJsModule.hpp"
#include "image/ImageJsModule.hpp"
AtlasJsModule::AtlasJsModule(QJSEngine* engine, QObject* parent) : QObject(parent) {
AtlasJsModule::AtlasJsModule(AtlasJsEngine* engine, QObject* parent) : QObject(parent) {
// set the engine
this->engine = engine;
// load the submodules
this->submodules[QStringLiteral("debug")] = std::make_shared<DebugJsModule>(this->engine);
this->submodules[QStringLiteral("image")] = std::make_shared<ImageJsModule>(this->engine);
this->submodules[QStringLiteral("fs")] = std::make_shared<FsJsModule>(this->engine);
// this->submodules[QStringLiteral("image")] = std::make_shared<ImageJsModule>(this->engine);
// TODO(Faraphel): check if the filesystem is correctly restricted
}
QJSValue AtlasJsModule::require(const QString& name) {
QJSValue AtlasJsModule::require(const QString& name) const {
std::shared_ptr<BaseJsModule> submodule;
try {

View file

@ -4,31 +4,33 @@
#include <QObject>
#include <QJSValue>
#include "_base/BaseJsModule.hpp"
class AtlasJsEngine;
class BaseJsModule;
class AtlasJsModule : public QObject {
Q_OBJECT
public:
explicit AtlasJsModule(QJSEngine* engine, QObject* parent = nullptr);
explicit AtlasJsModule(AtlasJsEngine* engine, QObject* parent = nullptr);
/**
* Import an Atlas submodule
* @param name the name of the module to import
* @return the module
*/
Q_INVOKABLE QJSValue require(const QString& name);
Q_INVOKABLE QJSValue require(const QString& name) const;
/**
* Get the version of the Atlas Javascript engine
* @return the Atlas version
*/
Q_INVOKABLE static QJSValueList getVersion();
Q_INVOKABLE static QList<QJSValue> getVersion();
private:
/// the parent JavaScript engine
QJSEngine* engine;
AtlasJsEngine* engine;
/// the submodules contained in this module
std::map<QString, std::shared_ptr<BaseJsModule>> submodules;
};

View file

@ -1,6 +1,8 @@
#include "BaseJsModule.hpp"
#include "javascript/engine/AtlasJsEngine.hpp"
BaseJsModule::BaseJsModule(QJSEngine *engine) {
BaseJsModule::BaseJsModule(AtlasJsEngine* engine) {
this->engine = engine;
}

View file

@ -1,7 +1,8 @@
#pragma once
#include <QObject>
#include <QJSEngine>
#include "javascript/engine/AtlasJsEngine.hpp"
/**
@ -11,9 +12,9 @@ class BaseJsModule : public QObject {
Q_OBJECT
public:
explicit BaseJsModule(QJSEngine* engine);
explicit BaseJsModule(AtlasJsEngine* engine);
protected:
/// The javascript engine running the module
QJSEngine* engine;
AtlasJsEngine* engine;
};

View file

@ -3,7 +3,7 @@
#include <QDebug>
DebugJsModule::DebugJsModule(QJSEngine* engine) : BaseJsModule(engine) {}
DebugJsModule::DebugJsModule(AtlasJsEngine* engine) : BaseJsModule(engine) {}
void DebugJsModule::information(const QString& text) {
qDebug() << text;

View file

@ -10,7 +10,7 @@ class DebugJsModule : public BaseJsModule {
Q_OBJECT
public:
explicit DebugJsModule(QJSEngine* engine);
explicit DebugJsModule(AtlasJsEngine* engine);
/**
* Print an informational message

View file

@ -0,0 +1,4 @@
# Submodule "Debug"
This module allow the developer to easily debug his application thanks
to common debug features.

View file

@ -1,12 +0,0 @@
#include "FileJsModule.hpp"
#include <QFile>
FileJsModule::FileJsModule(QJSEngine *engine, const QList<QDir>& allowedDirectories) : BaseJsModule(engine) {
this->allowedDirectories = allowedDirectories;
}
QJSValue FileJsModule::open(const QString& path) {
QFile file(path);
}

View file

@ -1,18 +0,0 @@
#pragma once
#include <QDir>
#include "../_base/BaseJsModule.hpp"
class FileJsModule : public BaseJsModule {
Q_OBJECT
public:
explicit FileJsModule(QJSEngine* engine, const QList<QDir>& allowedDirectories);
Q_INVOKABLE QJSValue open(const QString& path);
private:
QList<QDir> allowedDirectories;
};

View file

@ -0,0 +1,15 @@
#include "FsJsModule.hpp"
#include "object/FileJsObject.hpp"
#include "object/PathJsObject.hpp"
FsJsModule::FsJsModule(AtlasJsEngine* engine) : BaseJsModule(engine) {}
QJSValue FsJsModule::newPath(const QString& virtualPath) const {
// TODO(Faraphel): risk of memory leak ?
return this->engine->newQObject(new PathJsObject(
this->engine,
std::filesystem::path(virtualPath.toStdString())
));
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <VirtualFileSystem.hpp>
#include "../_base/BaseJsModule.hpp"
class FsJsModule : public BaseJsModule {
Q_OBJECT
public:
explicit FsJsModule(AtlasJsEngine* engine);
// TODO(Faraphel): can a property "Path" redirect to the type directly ?
/**
* Create a new path object
* @param virtualPath the path as a String
* @return the path as a JavaScript object
*/
Q_INVOKABLE QJSValue newPath(const QString& virtualPath) const;
};

View file

@ -0,0 +1,24 @@
# Submodule "FileSystem"
> NOTE : This module is in an elaboration stage. Everything is subject to change !
This module allow the developer to access the game file to interact with them,
and additionally a temporary folder to store important data that might be reused
to speed up the patch process.
For security reason, you can only access file inside the virtual file system.
Here is an example of the virtual file system structure :
```tree
/
| game/
| | sys/
| | files/
| | ...
| assets/
| | my-texture.png
| | ...
| tmp/
| | patched-texture.tpl
```

View file

@ -0,0 +1,103 @@
#include "FileJsObject.hpp"
#include <QJSValue>
#include <QTextStream>
#include "exception/FileNotFoundException.hpp"
#include "javascript/engine/AtlasJsEngine.hpp"
#include "utils/qt/fileOpenMode.hpp"
FileJsObject::FileJsObject(AtlasJsEngine* engine, const std::filesystem::path& virtualPath) {
// save the engine
this->engine = engine;
// create the internal QFile
std::filesystem::path realPath;
try {
realPath = this->engine->getFileSystem().resolve(virtualPath);
} catch (const vfs::exception::FileNotFoundException& exception) {
this->engine->throwError(QJSValue::ErrorType::URIError, "Invalid path.");
return;
}
// allocate the internal file
this->_internal = std::make_unique<QFile>(realPath);
}
void FileJsObject::open(const QString& mode) {
// get the mode flags from its string representation
this->mode = fileModeFromString(mode);
// open the file with the corresponding mode
if (!this->_internal->open(this->mode)) {
this->engine->throwError(QJSValue::URIError, "Cannot open the file.");
return;
}
// if we are in text mode, prepare the QTextStream wrapper
if (this->mode.testFlag(QIODevice::Text))
this->textStream = std::make_unique<QTextStream>(this->_internal.get());
}
QJSValue FileJsObject::read(const std::size_t size) const {
// check if we can read the file
if (!this->mode.testFlag(QIODevice::ReadOnly)) {
this->engine->throwError(QJSValue::ErrorType::TypeError, "The file is not open in read mode.");
return QJSValue(QJSPrimitiveNull());
}
// if we are in text mode, read from the text stream
if (this->mode.testFlag(QIODevice::Text))
return this->textStream->read(static_cast<qint64>(size));
// if we are in binary mode, read from the binary stream
const auto data = this->_internal->read(static_cast<qint64>(size));
return this->engine->toScriptValue(data);
}
std::size_t FileJsObject::write(const QJSValue& data) const {
// check if we can write into the file
if (!this->mode.testFlag(QIODevice::WriteOnly)) {
this->engine->throwError(QJSValue::ErrorType::TypeError, "The file is not open in write mode.");
return 0;
}
const QString dataString = data.toString();
// if we are in text mode, write text data
if (this->mode.testFlag(QIODevice::Text)) {
*this->textStream << dataString;
return dataString.size();
}
const QByteArray dataByte = dataString.toUtf8();
// if we are in binary mode, insert the raw binary data
return this->_internal->write(dataByte);
}
std::size_t FileJsObject::tell() const {
// if we are in text mode, return the position of the text stream
if (this->mode.testFlag(QIODevice::Text))
return this->textStream->pos();
// if we are in binary mode, return the position of the raw binary stream
return this->_internal->pos();
}
void FileJsObject::seek(const std::size_t position) const {
// if we are in text mode, set the position of the text stream
if (this->mode.testFlag(QIODevice::Text)) {
this->textStream->seek(static_cast<qint64>(position));
return;
}
// if we are in binary mode, set the position of the raw binary stream
this->_internal->seek(static_cast<qint64>(position));
}
void FileJsObject::close() const {
// if still opened, close the file
if (this->_internal->isOpen())
this->_internal->close();
}

View file

@ -0,0 +1,55 @@
#pragma once
#include <cstdlib>
#include <QFile>
#include <QJSValue>
#include <QObject>
#include <QTextStream>
#include "javascript/engine/AtlasJsEngine.hpp"
/**
* Represent a JavaScript file.
* This is a wrapper around `QFile`
*/
class FileJsObject : public QObject {
Q_OBJECT
public:
explicit FileJsObject(AtlasJsEngine* engine, const std::filesystem::path& virtualPath);
/**
* Open the file to access its content.
* @param mode the opening mode of the file, using "r", "w", "+", etc...
*/
Q_INVOKABLE void open(const QString& mode = QStringLiteral("r"));
/**
* Read the content of the file.
* @param size the size of the content to read.
* @return the content of the file
*/
Q_INVOKABLE QJSValue read(size_t size = std::numeric_limits<std::size_t>::max()) const;
/**
* Write data into the file
* @param data the data to write into the file
* @return the size of the data written
*/
Q_INVOKABLE std::size_t write(const QJSValue& data) const;
Q_INVOKABLE std::size_t tell() const;
Q_INVOKABLE void seek(std::size_t position) const;
/**
* Close the file.
*/
Q_INVOKABLE void close() const;
private:
AtlasJsEngine* engine;
std::unique_ptr<QFile> _internal;
QIODevice::OpenMode mode;
std::unique_ptr<QTextStream> textStream;
};

View file

@ -0,0 +1,101 @@
#include "PathJsObject.hpp"
#include <QDir>
#include <QList>
#include "FileJsObject.hpp"
#include "exception/FileNotFoundException.hpp"
PathJsObject::PathJsObject(AtlasJsEngine* engine, const std::filesystem::path& virtualPath) {
this->engine = engine;
this->virtualPath = virtualPath;
}
QJSValue PathJsObject::getParent() const {
// return the parent directory
return this->engine->newQObject(new PathJsObject(
this->engine,
this->virtualPath.parent_path()
));
}
QList<QJSValue> PathJsObject::getChildrens() const {
// check if the path is iterable
if (!this->isDirectory()) {
this->engine->throwError(QJSValue::ErrorType::TypeError, "Only directories can have children.");
return {};
}
// get the corresponding real directory path
const std::filesystem::path realPath = this->getRealPath();
// prepare the list of children paths
auto childsPath = QList<QJSValue>();
// iterate through the childrens of the directory
for (const auto& childRealEntry : std::filesystem::directory_iterator(realPath)) {
// get the corresponding path object
const auto& childRealPath = childRealEntry.path();
// get the virtual path
std::filesystem::path childVirtualPath = this->virtualPath / childRealPath.filename();
// add it to the list of childrens
const auto pathJs = this->engine->newQObject(new PathJsObject(
this->engine,
childVirtualPath
));
childsPath.append(pathJs);
}
return childsPath;
}
bool PathJsObject::isFile() const {
return is_regular_file(this->getRealPath());
}
bool PathJsObject::isDirectory() const {
return is_directory(this->getRealPath());
}
Q_INVOKABLE QJSValue PathJsObject::open(const QString& mode, const QString& encoding) {
// create a new file object
auto* file = new FileJsObject(
this->engine,
this->virtualPath
);
// open it with the given mode
file->open(mode);
// convert it into a QJSValue
auto fileJs = this->engine->newQObject(file);
return fileJs;
// TODO(Faraphel): handle encoding
}
QString PathJsObject::getPath() const {
return QString::fromStdString(this->virtualPath.string());
}
QString PathJsObject::getDirname() const {
return QString::fromStdString(this->virtualPath.parent_path().string());
}
QString PathJsObject::getFilename() const {
return QString::fromStdString(this->virtualPath.filename());
}
QString PathJsObject::getBasename() const {
return QString::fromStdString(this->virtualPath.stem());
}
QString PathJsObject::getSuffix() const {
return QString::fromStdString(this->virtualPath.extension());
}
std::filesystem::path PathJsObject::getRealPath() const {
return this->engine->getFileSystem().resolve(this->virtualPath);
}

View file

@ -0,0 +1,114 @@
#pragma once
#include <QObject>
#include <QString>
#include <QList>
#include <QDir>
#include <QSharedPointer>
#include "javascript/module/file_system/object/FileJsObject.hpp"
#include "javascript/engine/AtlasJsEngine.hpp"
/**
* Represent a file system Path as a JavaScript object
* Similar to the pathlib library in Python
*/
class PathJsObject : public QObject {
Q_OBJECT
public:
explicit PathJsObject(AtlasJsEngine* engine, const std::filesystem::path& virtualPath);
// browse
/**
* Get the parent directory
* @return the parent directory
*/
Q_INVOKABLE QJSValue getParent() const;
/**
* Get the child paths
* @return the child paths
*/
Q_INVOKABLE QList<QJSValue> getChildrens(bool recursive = false, QString pattern = "*") const;
// type
Q_INVOKABLE bool exists();
/**
* is the current path leading to a file
* @return is the current path leading to a file
*/
Q_INVOKABLE bool isFile() const;
/**
* is the current path leading to a directory
* @return is the current path leading to a directory
*/
Q_INVOKABLE bool isDirectory() const;
// operation
/**
* open the path as a file
* @return the opened file
*/
Q_INVOKABLE QJSValue open(const QString& mode, const QString& encoding = QStringLiteral("utf-8"));
Q_INVOKABLE void mkdir(bool parents);
Q_INVOKABLE void remove(bool recursive);
// component
/**
* get the full path to the file
* @return the full path to the file
*/
Q_INVOKABLE QString getPath() const;
/**
* get the name of the directory containing the file
* @return the name of the directory containing the file
*/
Q_INVOKABLE QString getDirname() const;
/**
* get the name of the file
* @return the name of the file
*/
Q_INVOKABLE QString getFilename() const;
/**
* get the name of the file without the suffix
* @return the name of the file without the suffix
*/
Q_INVOKABLE QString getBasename() const;
/**
* get the extension of the file
* @return the extension of the file
*/
Q_INVOKABLE QString getSuffix() const;
/**
* get the extensions of the file
* @return the extensions of the file
*/
Q_INVOKABLE QList<QString> getSuffixes() const;
/**
* get the segments of the path
* @return the segments of the path
*/
Q_INVOKABLE QList<QString> getSegments() const;
private:
/// the JavaScript engine
AtlasJsEngine* engine;
/// the virtual path
std::filesystem::path virtualPath;
/**
* Get the real path on the host file system of the path
* @return the real path on the host file system
* @throw rvfs::exception::FileNotFoundException if the path does not match with anything on the real file system
*/
std::filesystem::path getRealPath() const;
};

View file

@ -1,9 +1,9 @@
#include "ImageJsModule.hpp"
#include "ImageJsObject.hpp"
#include "object/ImageJsObject.hpp"
ImageJsModule::ImageJsModule(QJSEngine* engine) : BaseJsModule(engine) {}
ImageJsModule::ImageJsModule(AtlasJsEngine* engine) : BaseJsModule(engine) {}
QJSValue ImageJsModule::load(const QString& path) {
// TODO(Faraphel): can the new lead to a memory leak ?

View file

@ -2,7 +2,6 @@
#include <QJSValue>
#include "ImageJsObject.hpp"
#include "../_base/BaseJsModule.hpp"
@ -13,7 +12,7 @@ class ImageJsModule : public BaseJsModule {
Q_OBJECT
public:
explicit ImageJsModule(QJSEngine* engine);
explicit ImageJsModule(AtlasJsEngine* engine);
/**
* Load an image file

View file

View file

@ -0,0 +1,7 @@
# Submodule "Web"
> This module will allow the developer to access data on the web.
> It shall be very careful about the security and will require a
> special authorisation by the user to access any url.
> As of right now, it is in a stage of idea.

View file

View file

@ -1,18 +1,12 @@
#include <iostream>
#include "javascript/engine/AtlasJsEngine.hpp"
#include <QApplication>
#include <QMainWindow>
#include <QTextEdit>
#include <QPushButton>
#include <QLayout>
#include <QVBoxLayout>
#include <QDir>
#include <QString>
#include <QTemporaryDir>
#include <RestrictedVirtualFileSystem.hpp>
#include "javascript/engine/AtlasJsEngine.hpp"
int main(int argc, char* argv[]) {
@ -22,22 +16,6 @@ int main(int argc, char* argv[]) {
QApplication::setApplicationName("Atlas-Launcher");
QApplication::setApplicationVersion("1.0.0");
// create a restricted virtual file system
const auto fileSystem = std::make_shared<rvfs::RestrictedVirtualFileSystem>();
// mount the cache directory
fileSystem->mountDirectory(
QDir("/cache/"),
QDir("/home/faraphel/Documents/Projects/Atlas-Launcher/source/")
);
// mount the temporary directory
const auto temporaryDir = QTemporaryDir();
fileSystem->mountDirectory(
QDir("/tmp/"),
QDir(temporaryDir.path())
);
// create a small window
auto mainWindow = std::make_shared<QMainWindow>();
@ -58,9 +36,11 @@ int main(int argc, char* argv[]) {
layout->addWidget(output.get());
output->setEnabled(false);
QObject::connect(submit.get(), &QPushButton::clicked, [&]() {
auto engine = std::make_shared<atlas::js::AtlasJsEngine>(nullptr);
// create the javascript engine
auto engine = std::make_shared<AtlasJsEngine>(widget.get());
// when submit clicked, run the code and show the output
QObject::connect(submit.get(), &QPushButton::clicked, [&] {
QJSValue value = engine->evaluate(input->toPlainText());
output->setText(value.toString());
});

View file

@ -0,0 +1,24 @@
#include "fileOpenMode.hpp"
QIODevice::OpenMode fileModeFromString(const QString& modeString) {
QIODevice::OpenMode modeFlags = QIODevice::NotOpen;
// if the mode don't contains a "b", set the text mode
if (!modeString.contains("b"))
modeFlags |= QIODevice::Text;
// if the mode contains a "r", set the read mode
if (modeString.contains("r"))
modeFlags |= QIODevice::ReadOnly;
// if the mode contains a "w", set the write mode
if (modeString.contains("w"))
modeFlags |= QIODevice::WriteOnly;
// if the mode contains a "a", set the append mode
if (modeString.contains("a"))
modeFlags |= QIODevice::Append;
return modeFlags;
}

View file

@ -0,0 +1,11 @@
#pragma once
#include <QIODevice>
/**
* convert a string representing a file mode to the corresponding Qt flags.
* @param modeString the mode as a string
* @return the mode as a flag
*/
QIODevice::OpenMode fileModeFromString(const QString& modeString);