Статьи

Документация

Дополнительно

Внешние ресурсы

Внешние ресурсы для графического модуля представлены в виде доступа к БД, сетевого интерфейса и внешних загружаемых ресурсов.

Сетевой интерфейс

Модуль xenolith_resources_network предоставляет интерфейс для выполнения сетевых запросов в выделенном потоке (network::Controller).

Запрос реализуется классом network::Request. Пример использования:

auto req = Rc<network::Request>::create([&] (network::Handle &handle) {
    handle.init(network::Method::Get, "https://geobase.stappler.org/");
    return true;
}, this);

req->perform(controller, [this] (const network::Request &req, bool success) {
    StringStream stream;
    auto data = data::read<Interface>(req.getData());

    stream << data::EncodeFormat::Pretty << data << "\n";
});

Для работы необходимо, чтобы контроллер был подключен как расширение Application.

Базы данных

Модуль xenolith_resources_storage предоставляет интерфейс к БД в выделенном потоке (storage::Server). Параметры запуска соотвествуют передаваемым к драйверу БД. Сервер подключается к Application как расширение.

Для определения схем данных используется тип storage::Component. Для доступа к компонентам со стороны приложения используется storage::ComponentContainer. Контейнеры могут быть созданы и отключены в любой момент (например. привязаны к узлу сцены).

Пример использования:

#include "XLStorageComponent.h"

class UtilsStorageTestComponent : public storage::Component {
public:
    static constexpr auto DbPasswordSalt = "UtilsStorageTestComponent";

    virtual ~UtilsStorageTestComponent() { }

    UtilsStorageTestComponent(storage::ComponentLoader &loader)
    : Component(loader, "UtilsStorageTest") {
        using namespace db;

        loader.exportScheme(_users.define({
            Field::Text("name", MinLength(2), MaxLength(32), Transform::Identifier, Flags::Indexed),
            Field::Password("password", MinLength(2), MaxLength(32), PasswordSalt(DbPasswordSalt))
        }));
    }

    virtual void handleChildInit(const storage::Server &serv, const db::Transaction &t) override {
        Component::handleChildInit(serv, t);
    }
    virtual void handleChildRelease(const storage::Server &serv, const db::Transaction &t) override {
        Component::handleChildRelease(serv, t);
    }

    virtual void handleStorageTransaction(db::Transaction &t) override {
        Component::handleStorageTransaction(t);
    }
    virtual void handleHeartbeat(const storage::Server &serv) override {
        Component::handleHeartbeat(serv);
    }

    const db::Scheme &getUsers() const { return _users; }

protected:
    db::Scheme _users = db::Scheme("test_users");
};

class UtilsStorageTestComponentContainer : public storage::ComponentContainer {
public:
    virtual ~UtilsStorageTestComponentContainer() { }

    virtual bool init();

    virtual void handleStorageInit(storage::ComponentLoader &loader) override;
    virtual void handleStorageDisposed(const db::Transaction &t) override;

    virtual void handleComponentsLoaded(const storage::Server &serv) override;
    virtual void handleComponentsUnloaded(const storage::Server &serv) override;

    bool getAll(Function<void(Value &&)> &&, Ref *ref);
    bool createUser(StringView name, StringView password, Function<void(Value &&)> &&, Ref * = nullptr);
    bool checkUser(StringView name, StringView password, Function<void(Value &&)> &&, Ref * = nullptr);

protected:
    using storage::ComponentContainer::init;

    UtilsStorageTestComponent *_component = nullptr;
};

bool UtilsStorageTestComponentContainer::init() {
    return ComponentContainer::init("UtilsStorageTest");
}

void UtilsStorageTestComponentContainer::handleStorageInit(storage::ComponentLoader &loader) {
    std::cout << "handleStorageInit\n";
    ComponentContainer::handleStorageInit(loader);
    _component = new UtilsStorageTestComponent(loader);
}
void UtilsStorageTestComponentContainer::handleStorageDisposed(const db::Transaction &t) {
    _component = nullptr;
    ComponentContainer::handleStorageDisposed(t);
    std::cout << "handleStorageDisposed\n";
}

void UtilsStorageTestComponentContainer::handleComponentsLoaded(const storage::Server &serv) {
    ComponentContainer::handleComponentsLoaded(serv);
    std::cout << "handleComponentsLoaded\n";
}
void UtilsStorageTestComponentContainer::handleComponentsUnloaded(const storage::Server &serv) {
    std::cout << "handleComponentsUnloaded\n";
    ComponentContainer::handleComponentsUnloaded(serv);
}

bool UtilsStorageTestComponentContainer::getAll(Function<void(Value &&)> &&cb, Ref *ref) {
    return perform([this, cb = move(cb), ref] (const storage::Server &serv, const db::Transaction &t) mutable {
        db::Value val;
        auto users = _component->getUsers().select(t, db::Query());
        for (auto &it : users.asArray()) {
            val.addString(it.getString("name"));
        }

        serv.getApplication()->performOnMainThread([cb = move(cb), val = Value(val)] () mutable {
            cb(move(val));
        }, ref);

        return true;
    }, ref);
}

bool UtilsStorageTestComponentContainer::createUser(StringView name, StringView password, Function<void(Value &&)> &&cb, Ref *ref) {
    return perform([this, cb = move(cb), name = name.str<Interface>(), password = password.str<Interface>(), ref] (const storage::Server &serv, const db::Transaction &t) mutable {
        db::Value val;
        auto u = _component->getUsers().select(t, db::Query().select("name", db::Value(name))).getValue(0);
        if (u) {
            val = _component->getUsers().update(t, u, db::Value({
                pair("password", db::Value(password)),
            }));
        } else {
            val = _component->getUsers().create(t, db::Value({
                pair("name", db::Value(name)),
                pair("password", db::Value(password)),
            }));
        }

        serv.getApplication()->performOnMainThread([cb = move(cb), val = Value(val)] () mutable {
            cb(move(val));
        }, ref);

        return true;
    }, ref);
}

bool UtilsStorageTestComponentContainer::checkUser(StringView name, StringView password, Function<void(Value &&)> &&cb, Ref *ref) {
    return perform([this, cb = move(cb), name = name.str<Interface>(), password = password.str<Interface>(), ref] (const storage::Server &serv, const db::Transaction &t) mutable {
        db::Value val;
        auto u = _component->getUsers().select(t, db::Query().select("name", db::Value(name))).getValue(0);
        if (u) {
            if (!valid::validatePassord(password, u.getBytes("password"), UtilsStorageTestComponent::DbPasswordSalt)) {
                val = db::Value("invalid_password");
            } else {
                val = move(u);
            }
        }

        serv.getApplication()->performOnMainThread([cb = move(cb), val = Value(val)] () mutable {
            cb(move(val));
        }, ref);

        return true;
    }, ref);
}

Добавление и удаление контейнера:

bool UtilsStorageTest::init() {
    if (!LayoutTest::init(LayoutName::UtilsStorageTest, "")) {
        return false;
    }

    _container = Rc<UtilsStorageTestComponentContainer>::create();

    // ...

    return true;
}

void UtilsStorageTest::onEnter(Scene *scene) {
    LayoutTest::onEnter(scene);

    auto serv = _director->getApplication()->getExtension<storage::Server>();
    serv->addComponentContainer(_container);
}

void UtilsStorageTest::onExit() {
    auto serv = _director->getApplication()->getExtension<storage::Server>();
    serv->removeComponentContainer(_container);

    LayoutTest::onExit();
}

Внешние загружаемые ресурсы

Модуль xenolith_resources_assets определяет интерфейс загружаемых ресурсов на основе сетевого интерфейса и интерфейса БД. Используется встроенная БД, в которой хранятся данные ресурсов.

Для работы необходимо создать и добавить расширение storage::AssetLibrary.

Для создания используется storage::AssetLibrary::acquireAsset. Ресурсы хранятся в БД по их url и id.

После получения storage::Asset, можно подписаться на его обновления, начать загрузку, попробовать получить загружаемую версию.

Загруженную версию можно заблокировать через получение и хранение storage::AssetLock. Пока приложение хранит блокировку, эта версия ресурса будет существовать и будет доступна для чтения. ресурсы автоматически запрашивают обновление по сети после того, как были единожды загружены.

Пример использования:

class UtilsAssetTest : public LayoutTest {
public:
    virtual ~UtilsAssetTest() { }

    virtual bool init() override;

    virtual void onEnter(Scene *) override;
    virtual void onExit() override;

protected:
    using LayoutTest::init;

    void handleAssetUpdate(SubscriptionFlags);

    // ...

    DataListener<storage::Asset> *_listener = nullptr;
};

bool UtilsAssetTest::init() {
    if (!LayoutTest::init(LayoutName::UtilsAssetTest, "")) {
        return false;
    }

    // ...

    _listener = addComponent(Rc<DataListener<storage::Asset>>::create([this] (SubscriptionFlags flags) {
        handleAssetUpdate(flags);
    }));

    return true;
}

void UtilsAssetTest::onEnter(Scene *scene) {
    LayoutTest::onEnter(scene);

    auto lib = _director->getApplication()->getExtension<storage::AssetLibrary>();
    lib->acquireAsset("https://apps.stappler.org/api/v2/issues/id80417/content", [this] (const Rc<storage::Asset> &asset) {
        if (_running) {
            // ...
        }
    }, TimeInterval::seconds(60 * 60), this);
}

void UtilsAssetTest::onExit() {
    LayoutTest::onExit();

    _listener->setSubscription(nullptr);
}

void UtilsAssetTest::handleAssetUpdate(SubscriptionFlags flags) {
    if (storage::Asset *a = _listener->getSubscription()) {
        if (a->isDownloadInProgress()) {
            // ...
        } else {
            if (a->lockReadableVersion()) {
                // ...
            } else {
                // ...
            }
        }
    }
}