test code

This commit is contained in:
Rasmus Karlsson 2018-01-19 22:45:33 +01:00
parent 66e99fd36f
commit 03ff2205fa
17 changed files with 397 additions and 221 deletions

View file

@ -162,7 +162,8 @@ SOURCES += \
src/widgets/settingspages/logspage.cpp \
src/widgets/basewindow.cpp \
src/singletons/helper/moderationaction.cpp \
src/widgets/streamview.cpp
src/widgets/streamview.cpp \
src/util/networkrequest.cpp
HEADERS += \
src/precompiled_header.hpp \
@ -262,7 +263,10 @@ HEADERS += \
src/widgets/settingspages/logspage.hpp \
src/widgets/basewindow.hpp \
src/singletons/helper/moderationaction.hpp \
src/widgets/streamview.hpp
src/widgets/streamview.hpp \
src/util/networkrequest.hpp \
src/util/networkworker.hpp \
src/util/networkrequester.hpp
RESOURCES += \
resources/resources.qrc

View file

@ -47,9 +47,10 @@ void Image::loadImage()
{
util::NetworkRequest req(this->getUrl());
req.setCaller(this);
req.get([lli = this](QNetworkReply * reply) {
QByteArray array = reply->readAll();
QBuffer buffer(&array);
req.setUseQuickLoadCache(true);
req.get([lli = this](QByteArray bytes) {
QByteArray copy = QByteArray::fromRawData(bytes.constData(), bytes.length());
QBuffer buffer(&copy);
buffer.open(QIODevice::ReadOnly);
QImage image;
@ -63,7 +64,7 @@ void Image::loadImage()
if (first) {
first = false;
lli->currentPixmap = pixmap;
lli->loadedPixmap = pixmap;
}
chatterino::messages::Image::FrameData data;
@ -78,6 +79,10 @@ void Image::loadImage()
lli->animated = true;
}
lli->currentPixmap = lli->loadedPixmap;
lli->isLoaded = true;
singletons::EmoteManager::getInstance().incGeneration();
singletons::WindowManager::getInstance().layoutVisibleChatWidgets();
@ -91,7 +96,7 @@ void Image::loadImage()
void Image::gifUpdateTimout()
{
if (animated) {
if (this->animated) {
this->currentFrameOffset += GIF_FRAME_LENGTH;
while (true) {
@ -112,9 +117,16 @@ const QPixmap *Image::getPixmap()
if (!this->isLoading) {
this->isLoading = true;
loadImage();
this->loadImage();
return nullptr;
}
if (this->isLoaded) {
return this->currentPixmap;
} else {
return nullptr;
}
return this->currentPixmap;
}
qreal Image::getScale() const
@ -157,6 +169,7 @@ int Image::getWidth() const
if (this->currentPixmap == nullptr) {
return 16;
}
return this->currentPixmap->width();
}

View file

@ -2,9 +2,11 @@
#include <QPixmap>
#include <QString>
#include <boost/noncopyable.hpp>
#include <atomic>
#include <mutex>
namespace chatterino {
namespace messages {
@ -41,6 +43,7 @@ private:
};
QPixmap *currentPixmap;
QPixmap *loadedPixmap;
std::vector<FrameData> allFrames;
int currentFrame = 0;
int currentFrameOffset = 0;
@ -53,7 +56,8 @@ private:
bool ishat;
qreal scale;
bool isLoading;
bool isLoading = false;
std::atomic<bool> isLoaded{false};
void loadImage();
void gifUpdateTimout();

View file

@ -79,6 +79,10 @@ int ImageLayoutElement::getSelectionIndexCount()
void ImageLayoutElement::paint(QPainter &painter)
{
if (this->image == nullptr) {
return;
}
const QPixmap *pixmap = this->image->getPixmap();
if (pixmap != nullptr && !this->image->isAnimated()) {
@ -89,12 +93,18 @@ void ImageLayoutElement::paint(QPainter &painter)
void ImageLayoutElement::paintAnimated(QPainter &painter, int yOffset)
{
if (this->image == nullptr) {
return;
}
if (this->image->isAnimated()) {
if (this->image->getPixmap() != nullptr) {
auto pixmap = this->image->getPixmap();
if (pixmap != nullptr) {
// fourtf: make it use qreal values
QRect rect = this->getRect();
rect.moveTop(rect.y() + yOffset);
painter.drawPixmap(QRectF(rect), *this->image->getPixmap(), QRectF());
painter.drawPixmap(QRectF(rect), *pixmap, QRectF());
}
}
}

View file

@ -121,8 +121,8 @@ public:
newChunks->at(0) = newFirstChunk;
this->chunks = newChunks;
qDebug() << acceptedItems.size();
qDebug() << this->chunks->at(0)->size();
// qDebug() << acceptedItems.size();
// qDebug() << this->chunks->at(0)->size();
if (this->chunks->size() == 1) {
this->lastChunkEnd += offset;

View file

@ -237,7 +237,7 @@ TwitchModerationElement::TwitchModerationElement()
void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
MessageElement::Flags _flags)
{
qDebug() << _flags;
// qDebug() << _flags;
if (_flags & MessageElement::ModeratorTools) {
QSize size((int)(container.scale * 16), (int)(container.scale * 16));

View file

@ -142,8 +142,7 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName,
link = link.replace("{{id}}", id).replace("{{image}}", "1x");
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [this, &code, &link] {
return util::EmoteData(
new Image(link, 1, code, code + "<br/>Channel BTTV Emote"));
return util::EmoteData(new Image(link, 1, code, code + "<br/>Channel BTTV Emote"));
});
this->bttvChannelEmotes.insert(code, emote);
@ -293,9 +292,8 @@ void EmoteManager::loadEmojis()
"emojione/2.2.6/assets/png/" +
code + ".png";
this->emojis.insert(code,
util::EmoteData(new Image(url, 0.35, ":" + shortCode + ":",
":" + shortCode + ":<br/>Emoji")));
this->emojis.insert(code, util::EmoteData(new Image(url, 0.35, ":" + shortCode + ":",
":" + shortCode + ":<br/>Emoji")));
// TODO(pajlada): The vectors in emojiFirstByte need to be sorted by
// emojiData.code.length()
@ -374,8 +372,7 @@ void EmoteManager::parseEmojis(std::vector<std::tuple<util::EmoteData, QString>>
// Create or fetch cached emoji image
auto emojiImage = this->emojis.getOrAdd(matchedEmoji.code, [this, &url] {
return util::EmoteData(
new Image(url, 0.35, "?????????", "???????????????")); //
return util::EmoteData(new Image(url, 0.35, "?????????", "???????????????")); //
});
// Push the emoji as a word to parsedWords
@ -447,7 +444,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<twitch::TwitchUser>
util::twitch::getAuthorized(
url, clientID, oauthToken, QThread::currentThread(),
[=, &emoteData](QJsonObject &root) {
[=, &emoteData](const QJsonObject &root) {
emoteData.emoteSets.clear();
emoteData.emoteCodes.clear();
auto emoticonSets = root.value("emoticon_sets").toObject();
@ -477,6 +474,7 @@ void EmoteManager::loadBTTVEmotes()
util::NetworkRequest req(url);
req.setCaller(QThread::currentThread());
req.setTimeout(30000);
req.setUseQuickLoadCache(true);
req.getJSON([this](QJsonObject &root) {
debug::Log("Got global bttv emotes");
auto emotes = root.value("emotes").toArray();
@ -489,12 +487,12 @@ void EmoteManager::loadBTTVEmotes()
QString code = emote.toObject().value("code").toString();
util::EmoteData emoteData;
emoteData.image1x = new Image(GetBTTVEmoteLink(urlTemplate, id, "1x"), 1,
code, code + "<br />Global BTTV Emote");
emoteData.image2x = new Image(GetBTTVEmoteLink(urlTemplate, id, "2x"), 0.5,
code, code + "<br />Global BTTV Emote");
emoteData.image3x = new Image(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25,
code, code + "<br />Global BTTV Emote");
emoteData.image1x = new Image(GetBTTVEmoteLink(urlTemplate, id, "1x"), 1, code,
code + "<br />Global BTTV Emote");
emoteData.image2x = new Image(GetBTTVEmoteLink(urlTemplate, id, "2x"), 0.5, code,
code + "<br />Global BTTV Emote");
emoteData.image3x = new Image(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25, code,
code + "<br />Global BTTV Emote");
this->bttvGlobalEmotes.insert(code, emoteData);
codes.push_back(code.toStdString());
@ -548,11 +546,11 @@ util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteNa
return _twitchEmoteFromCache.getOrAdd(id, [this, &emoteName, &_emoteName, &id] {
util::EmoteData newEmoteData;
newEmoteData.image1x = new Image(GetTwitchEmoteLink(id, "1.0"), 1, emoteName,
_emoteName + "<br/>Twitch Emote 1x");
_emoteName + "<br/>Twitch Emote 1x");
newEmoteData.image2x = new Image(GetTwitchEmoteLink(id, "2.0"), .5, emoteName,
_emoteName + "<br/>Twitch Emote 2x");
_emoteName + "<br/>Twitch Emote 2x");
newEmoteData.image3x = new Image(GetTwitchEmoteLink(id, "3.0"), .25, emoteName,
_emoteName + "<br/>Twitch Emote 3x");
_emoteName + "<br/>Twitch Emote 3x");
return newEmoteData;
});

View file

@ -253,6 +253,11 @@ void IrcManager::privateMessageReceived(Communi::IrcPrivateMessage *message)
return;
}
auto xd = message->content();
auto xd2 = message->toData();
debug::Log("HEHE: {}", xd2.toStdString());
messages::MessageParseArgs args;
twitch::TwitchMessageBuilder builder(c.get(), message, args);

View file

@ -50,6 +50,13 @@ bool PathManager::init(int argc, char **argv)
return false;
}
this->cacheFolderPath = rootPath + "/Cache";
if (!QDir().mkpath(this->cacheFolderPath)) {
printf("Error creating cache directory: %s\n", qPrintable(this->cacheFolderPath));
return false;
}
return true;
}

View file

@ -16,6 +16,7 @@ public:
QString settingsFolderPath;
QString customFolderPath;
QString cacheFolderPath;
};
} // namespace singletons

View file

@ -106,11 +106,6 @@ void TwitchChannel::setMod(bool value)
bool TwitchChannel::isBroadcaster()
{
QString xD = this->name;
QString xD2 = singletons::AccountManager::getInstance().Twitch.getCurrent()->getUserName();
qDebug() << xD << xD2;
return this->name ==
singletons::AccountManager::getInstance().Twitch.getCurrent()->getUserName();
}
@ -144,7 +139,7 @@ void TwitchChannel::refreshLiveStatus()
std::weak_ptr<Channel> weak = this->shared_from_this();
util::twitch::get2(url, QThread::currentThread(), [weak](rapidjson::Document &d) {
util::twitch::get2(url, QThread::currentThread(), [weak](const rapidjson::Document &d) {
SharedChannel shared = weak.lock();
if (!shared) {

View file

@ -1,6 +1,8 @@
#pragma once
#include "debug/log.hpp"
#include "util/networkrequester.hpp"
#include "util/networkworker.hpp"
#include <QJsonDocument>
#include <QJsonObject>
@ -29,22 +31,6 @@ static QJsonObject parseJSONFromReplyxD(QNetworkReply *reply)
return jsonDoc.object();
}
class NetworkWorker : public QObject
{
Q_OBJECT
signals:
void doneUrl(QNetworkReply *);
};
class NetworkRequester : public QObject
{
Q_OBJECT
signals:
void requestUrl();
};
class NetworkManager : public QObject
{
Q_OBJECT
@ -183,122 +169,5 @@ public:
}
};
class NetworkRequest
{
struct Data {
QNetworkRequest request;
const QObject *caller = nullptr;
std::function<void(QNetworkReply *)> onReplyCreated;
int timeoutMS = -1;
} data;
public:
NetworkRequest() = delete;
explicit NetworkRequest(const char *url)
{
this->data.request.setUrl(QUrl(url));
}
explicit NetworkRequest(const std::string &url)
{
this->data.request.setUrl(QUrl(QString::fromStdString(url)));
}
explicit NetworkRequest(const QString &url)
{
this->data.request.setUrl(QUrl(url));
}
void setCaller(const QObject *_caller)
{
this->data.caller = _caller;
}
void setOnReplyCreated(std::function<void(QNetworkReply *)> f)
{
this->data.onReplyCreated = f;
}
void setRawHeader(const QByteArray &headerName, const QByteArray &value)
{
this->data.request.setRawHeader(headerName, value);
}
void setTimeout(int ms)
{
this->data.timeoutMS = ms;
}
template <typename FinishedCallback>
void get(FinishedCallback onFinished)
{
QTimer *timer = nullptr;
if (this->data.timeoutMS > 0) {
timer = new QTimer;
}
NetworkRequester requester;
NetworkWorker *worker = new NetworkWorker;
worker->moveToThread(&NetworkManager::workerThread);
if (this->data.caller != nullptr) {
QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller,
[onFinished](auto reply) {
onFinished(reply);
reply->deleteLater();
});
}
if (timer != nullptr) {
timer->start(this->data.timeoutMS);
}
QObject::connect(
&requester, &NetworkRequester::requestUrl, worker,
[ timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)} ]() {
QNetworkReply *reply = NetworkManager::NaM.get(data.request);
if (timer != nullptr) {
QObject::connect(timer, &QTimer::timeout, worker, [reply, timer]() {
debug::Log("Aborted!");
reply->abort();
timer->deleteLater();
});
}
if (data.onReplyCreated) {
data.onReplyCreated(reply);
}
QObject::connect(reply, &QNetworkReply::finished, worker, [
data = std::move(data), worker, reply, onFinished = std::move(onFinished)
]() {
if (data.caller == nullptr) {
onFinished(reply);
reply->deleteLater();
} else {
emit worker->doneUrl(reply);
}
delete worker;
});
});
emit requester.requestUrl();
}
template <typename FinishedCallback>
void getJSON(FinishedCallback onFinished)
{
this->get([onFinished{std::move(onFinished)}](auto reply) {
auto object = parseJSONFromReplyxD(reply);
onFinished(object);
});
}
};
} // namespace util
} // namespace chatterino

View file

@ -0,0 +1,42 @@
#include "util/networkrequest.hpp"
namespace chatterino {
namespace util {
NetworkRequest::NetworkRequest(const char *url)
{
this->data.request.setUrl(QUrl(url));
}
NetworkRequest::NetworkRequest(const std::string &url)
{
this->data.request.setUrl(QUrl(QString::fromStdString(url)));
}
NetworkRequest::NetworkRequest(const QString &url)
{
this->data.request.setUrl(QUrl(url));
}
void NetworkRequest::setUseQuickLoadCache(bool value)
{
this->data.useQuickLoadCache = value;
}
void NetworkRequest::Data::writeToCache(const QByteArray &bytes)
{
if (this->useQuickLoadCache) {
auto &pathManager = singletons::PathManager::getInstance();
QFile cachedFile(pathManager.cacheFolderPath + "/" + this->getHash());
if (cachedFile.open(QIODevice::WriteOnly)) {
cachedFile.write(bytes);
cachedFile.close();
}
}
}
} // namespace util
} // namespace chatterino

228
src/util/networkrequest.hpp Normal file
View file

@ -0,0 +1,228 @@
#pragma once
#include "singletons/pathmanager.hpp"
#include "util/networkmanager.hpp"
#include "util/networkrequester.hpp"
#include "util/networkworker.hpp"
#include <QCryptographicHash>
#include <QFile>
namespace chatterino {
namespace util {
static QJsonObject parseJSONFromData(const QByteArray &data)
{
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
if (jsonDoc.isNull()) {
return QJsonObject();
}
return jsonDoc.object();
}
static rapidjson::Document parseJSONFromData2(const QByteArray &data)
{
rapidjson::Document ret(rapidjson::kNullType);
rapidjson::ParseResult result = ret.Parse(data.data(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) {
debug::Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()),
result.Offset());
return ret;
}
return ret;
}
static rapidjson::Document parseJSONFromReply2(QNetworkReply *reply)
{
rapidjson::Document ret(rapidjson::kNullType);
if (reply->error() != QNetworkReply::NetworkError::NoError) {
return ret;
}
QByteArray data = reply->readAll();
rapidjson::ParseResult result = ret.Parse(data.data(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) {
debug::Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()),
result.Offset());
return ret;
}
return ret;
}
class NetworkRequest
{
struct Data {
QNetworkRequest request;
const QObject *caller = nullptr;
std::function<void(QNetworkReply *)> onReplyCreated;
int timeoutMS = -1;
bool useQuickLoadCache = false;
QString getHash()
{
if (this->hash.isEmpty()) {
QByteArray bytes;
bytes.append(this->request.url().toString());
for (const auto &header : this->request.rawHeaderList()) {
bytes.append(header);
}
QByteArray hashBytes(QCryptographicHash::hash(bytes, QCryptographicHash::Sha256));
this->hash = hashBytes.toHex();
}
return this->hash;
}
void writeToCache(const QByteArray &bytes);
private:
QString hash;
} data;
public:
NetworkRequest() = delete;
explicit NetworkRequest(const char *url);
explicit NetworkRequest(const std::string &url);
explicit NetworkRequest(const QString &url);
void setUseQuickLoadCache(bool value);
void setCaller(const QObject *_caller)
{
this->data.caller = _caller;
}
void setOnReplyCreated(std::function<void(QNetworkReply *)> f)
{
this->data.onReplyCreated = f;
}
void setRawHeader(const QByteArray &headerName, const QByteArray &value)
{
this->data.request.setRawHeader(headerName, value);
}
void setTimeout(int ms)
{
this->data.timeoutMS = ms;
}
template <typename FinishedCallback>
void get(FinishedCallback onFinished)
{
if (this->data.useQuickLoadCache) {
auto &pathManager = singletons::PathManager::getInstance();
QFile cachedFile(pathManager.cacheFolderPath + "/" + this->data.getHash());
if (cachedFile.exists()) {
if (cachedFile.open(QIODevice::ReadOnly)) {
QByteArray bytes = cachedFile.readAll();
onFinished(bytes);
cachedFile.close();
}
}
}
QTimer *timer = nullptr;
if (this->data.timeoutMS > 0) {
timer = new QTimer;
}
NetworkRequester requester;
NetworkWorker *worker = new NetworkWorker;
worker->moveToThread(&NetworkManager::workerThread);
if (this->data.caller != nullptr) {
QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller,
[ onFinished, data = this->data ](auto reply) mutable {
if (reply->error() != QNetworkReply::NetworkError::NoError) {
// TODO: We might want to call an onError callback here
return;
}
QByteArray bytes = reply->readAll();
data.writeToCache(bytes);
// onFinished(bytes);
reply->deleteLater();
});
}
if (timer != nullptr) {
timer->start(this->data.timeoutMS);
}
QObject::connect(
&requester, &NetworkRequester::requestUrl, worker,
[ timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)} ]() {
QNetworkReply *reply = NetworkManager::NaM.get(data.request);
if (timer != nullptr) {
QObject::connect(timer, &QTimer::timeout, worker, [reply, timer]() {
debug::Log("Aborted!");
reply->abort();
timer->deleteLater();
});
}
if (data.onReplyCreated) {
data.onReplyCreated(reply);
}
QObject::connect(reply, &QNetworkReply::finished, worker, [
data = std::move(data), worker, reply, onFinished = std::move(onFinished)
]() mutable {
if (data.caller == nullptr) {
QByteArray bytes = reply->readAll();
data.writeToCache(bytes);
// onFinished(bytes);
reply->deleteLater();
} else {
emit worker->doneUrl(reply);
}
delete worker;
});
});
emit requester.requestUrl();
}
template <typename FinishedCallback>
void getJSON(FinishedCallback onFinished)
{
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) {
auto object = parseJSONFromData(bytes);
onFinished(object);
});
}
template <typename FinishedCallback>
void getJSON2(FinishedCallback onFinished)
{
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) {
auto object = parseJSONFromData2(bytes);
onFinished(object);
});
}
};
} // namespace util
} // namespace chatterino

View file

@ -0,0 +1,17 @@
#pragma once
#include <QObject>
namespace chatterino {
namespace util {
class NetworkRequester : public QObject
{
Q_OBJECT
signals:
void requestUrl();
};
} // namespace util
} // namespace chatterino

View file

@ -0,0 +1,17 @@
#pragma once
#include <QObject>
namespace chatterino {
namespace util {
class NetworkWorker : public QObject
{
Q_OBJECT
signals:
void doneUrl(QNetworkReply *);
};
} // namespace util
} // namespace chatterino

View file

@ -4,6 +4,7 @@
#include "debug/log.hpp"
#include "singletons/accountmanager.hpp"
#include "util/networkmanager.hpp"
#include "util/networkrequest.hpp"
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
@ -21,82 +22,47 @@
namespace chatterino {
namespace util {
static QJsonObject parseJSONFromReply(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NetworkError::NoError) {
return QJsonObject();
}
QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
if (jsonDoc.isNull()) {
return QJsonObject();
}
return jsonDoc.object();
}
static rapidjson::Document parseJSONFromReply2(QNetworkReply *reply)
{
rapidjson::Document ret(rapidjson::kNullType);
if (reply->error() != QNetworkReply::NetworkError::NoError) {
return ret;
}
QByteArray data = reply->readAll();
rapidjson::ParseResult result = ret.Parse(data.data(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) {
debug::Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()),
result.Offset());
return ret;
}
return ret;
}
namespace twitch {
static void get(QString url, const QObject *caller,
std::function<void(QJsonObject &)> successCallback)
std::function<void(const QJsonObject &)> successCallback)
{
util::NetworkRequest req(url);
req.setCaller(caller);
req.setRawHeader("Client-ID", getDefaultClientID());
req.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
req.get([=](QNetworkReply *reply) {
auto node = parseJSONFromReply(reply);
successCallback(node);
req.getJSON([=](const QJsonObject &node) {
successCallback(node); //
});
}
static void get2(QString url, const QObject *caller,
std::function<void(rapidjson::Document &)> successCallback)
std::function<void(const rapidjson::Document &)> successCallback)
{
util::NetworkRequest req(url);
req.setCaller(caller);
req.setRawHeader("Client-ID", getDefaultClientID());
req.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
req.get([=](QNetworkReply *reply) {
auto document = parseJSONFromReply2(reply);
successCallback(document);
req.setUseQuickLoadCache(true);
req.getJSON2([=](const rapidjson::Document &document) {
successCallback(document); //
});
}
static void getAuthorized(QString url, const QString &clientID, const QString &oauthToken,
const QObject *caller, std::function<void(QJsonObject &)> successCallback)
const QObject *caller,
std::function<void(const QJsonObject &)> successCallback)
{
util::NetworkRequest req(url);
req.setCaller(caller);
req.setRawHeader("Client-ID", clientID.toUtf8());
req.setRawHeader("Authorization", "OAuth " + oauthToken.toUtf8());
req.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
req.get([=](QNetworkReply *reply) {
auto node = parseJSONFromReply(reply);
successCallback(node);
req.getJSON([=](const QJsonObject &node) {
successCallback(node); //
});
}