refactor: Turn link-info into its own element and class (#5178)
This commit is contained in:
@@ -1,64 +0,0 @@
|
||||
#include "providers/LinkResolver.hpp"
|
||||
|
||||
#include "common/Env.hpp"
|
||||
#include "common/network/NetworkRequest.hpp"
|
||||
#include "common/network/NetworkResult.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "messages/Link.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void LinkResolver::getLinkInfo(
|
||||
const QString url, QObject *caller,
|
||||
std::function<void(QString, Link, ImagePtr)> successCallback)
|
||||
{
|
||||
if (!getSettings()->linkInfoTooltip)
|
||||
{
|
||||
successCallback("No link info loaded", Link(Link::Url, url), nullptr);
|
||||
return;
|
||||
}
|
||||
// Uncomment to test crashes
|
||||
// QTimer::singleShot(3000, [=]() {
|
||||
NetworkRequest(Env::get().linkResolverUrl.arg(QString::fromUtf8(
|
||||
QUrl::toPercentEncoding(url, "", "/:"))))
|
||||
.caller(caller)
|
||||
.timeout(30000)
|
||||
.onSuccess([successCallback, url](NetworkResult result) mutable {
|
||||
auto root = result.parseJson();
|
||||
auto statusCode = root.value("status").toInt();
|
||||
QString response;
|
||||
QString linkString = url;
|
||||
ImagePtr thumbnail = nullptr;
|
||||
if (statusCode == 200)
|
||||
{
|
||||
response = root.value("tooltip").toString();
|
||||
|
||||
if (root.contains("thumbnail"))
|
||||
{
|
||||
thumbnail =
|
||||
Image::fromUrl({root.value("thumbnail").toString()});
|
||||
}
|
||||
if (getSettings()->unshortLinks)
|
||||
{
|
||||
linkString = root.value("link").toString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response = root.value("message").toString();
|
||||
}
|
||||
successCallback(QUrl::fromPercentEncoding(response.toUtf8()),
|
||||
Link(Link::Url, linkString), thumbnail);
|
||||
})
|
||||
.onError([successCallback, url](auto /*result*/) {
|
||||
successCallback("No link info found", Link(Link::Url, url),
|
||||
nullptr);
|
||||
})
|
||||
.execute();
|
||||
// });
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Image;
|
||||
struct Link;
|
||||
using ImagePtr = std::shared_ptr<Image>;
|
||||
|
||||
class LinkResolver
|
||||
{
|
||||
public:
|
||||
static void getLinkInfo(
|
||||
const QString url, QObject *caller,
|
||||
std::function<void(QString, Link, ImagePtr)> callback);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "providers/links/LinkInfo.hpp"
|
||||
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
LinkInfo::LinkInfo(QString url)
|
||||
: QObject(nullptr)
|
||||
, originalUrl_(url)
|
||||
, url_(std::move(url))
|
||||
, tooltip_(this->url_)
|
||||
{
|
||||
}
|
||||
|
||||
LinkInfo::~LinkInfo() = default;
|
||||
|
||||
LinkInfo::State LinkInfo::state() const
|
||||
{
|
||||
return this->state_;
|
||||
}
|
||||
|
||||
QString LinkInfo::url() const
|
||||
{
|
||||
return this->url_;
|
||||
}
|
||||
|
||||
QString LinkInfo::originalUrl() const
|
||||
{
|
||||
return this->originalUrl_;
|
||||
}
|
||||
|
||||
bool LinkInfo::isPending() const
|
||||
{
|
||||
return this->state_ == State::Created;
|
||||
}
|
||||
|
||||
bool LinkInfo::isLoading() const
|
||||
{
|
||||
return this->state_ == State::Loading;
|
||||
}
|
||||
|
||||
bool LinkInfo::isLoaded() const
|
||||
{
|
||||
return this->state_ > State::Loading;
|
||||
}
|
||||
|
||||
bool LinkInfo::isResolved() const
|
||||
{
|
||||
return this->state_ == State::Resolved;
|
||||
}
|
||||
|
||||
bool LinkInfo::hasError() const
|
||||
{
|
||||
return this->state_ == State::Errored;
|
||||
}
|
||||
|
||||
bool LinkInfo::hasThumbnail() const
|
||||
{
|
||||
return this->thumbnail_ && !this->thumbnail_->url().string.isEmpty();
|
||||
}
|
||||
|
||||
QString LinkInfo::tooltip() const
|
||||
{
|
||||
return this->tooltip_;
|
||||
}
|
||||
|
||||
ImagePtr LinkInfo::thumbnail() const
|
||||
{
|
||||
return this->thumbnail_;
|
||||
}
|
||||
|
||||
void LinkInfo::setState(State state)
|
||||
{
|
||||
assertInGuiThread();
|
||||
assert(state >= this->state_);
|
||||
|
||||
if (this->state_ == state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->state_ = state;
|
||||
this->stateChanged(state);
|
||||
}
|
||||
|
||||
void LinkInfo::setResolvedUrl(QString resolvedUrl)
|
||||
{
|
||||
assertInGuiThread();
|
||||
this->url_ = std::move(resolvedUrl);
|
||||
}
|
||||
|
||||
void LinkInfo::setTooltip(QString tooltip)
|
||||
{
|
||||
assertInGuiThread();
|
||||
this->tooltip_ = std::move(tooltip);
|
||||
}
|
||||
|
||||
void LinkInfo::setThumbnail(ImagePtr thumbnail)
|
||||
{
|
||||
assertInGuiThread();
|
||||
this->thumbnail_ = std::move(thumbnail);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
|
||||
#include "messages/Image.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
/// @brief Rich info about a URL with tooltip and thumbnail
|
||||
///
|
||||
/// This is only a data class - it doesn't do the resolving.
|
||||
/// It can only be used from the GUI thread.
|
||||
class LinkInfo : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/// @brief the state of a link info
|
||||
///
|
||||
/// The state of a link can only increase. For example, it's not possible
|
||||
/// for the link to change from "Resolved" to "Loading".
|
||||
enum class State {
|
||||
/// @brief The object was created, no info is resolved
|
||||
///
|
||||
/// This is the initial state
|
||||
Created,
|
||||
/// Info is currently loading
|
||||
Loading,
|
||||
/// Info has been resolved and the properties have been updated
|
||||
Resolved,
|
||||
/// There has been an error resolving the link info (e.g. timeout)
|
||||
Errored,
|
||||
};
|
||||
|
||||
/// @brief Constructs a new link info for a URL
|
||||
///
|
||||
/// This doesn't load any link info.
|
||||
/// @see #ensureLoadingStarted()
|
||||
[[nodiscard]] explicit LinkInfo(QString url);
|
||||
|
||||
~LinkInfo() override;
|
||||
|
||||
LinkInfo(const LinkInfo &) = delete;
|
||||
LinkInfo(LinkInfo &&) = delete;
|
||||
LinkInfo &operator=(const LinkInfo &) = delete;
|
||||
LinkInfo &operator=(LinkInfo &&) = delete;
|
||||
|
||||
/// @brief The URL of this link
|
||||
///
|
||||
/// If the "unshortLinks" setting is enabled, this can change after the
|
||||
/// link is resolved.
|
||||
[[nodiscard]] QString url() const;
|
||||
|
||||
/// @brief The URL of this link as seen in the message
|
||||
///
|
||||
/// If the "unshortLinks" setting doesn't affect this URL.
|
||||
[[nodiscard]] QString originalUrl() const;
|
||||
|
||||
/// Returns the current state
|
||||
[[nodiscard]] State state() const;
|
||||
|
||||
/// Returns true if this link has not yet been resolved (it's "Created")
|
||||
[[nodiscard]] bool isPending() const;
|
||||
|
||||
/// Returns true if the info is loading
|
||||
[[nodiscard]] bool isLoading() const;
|
||||
|
||||
/// Returns true if the info is loaded (resolved or errored)
|
||||
[[nodiscard]] bool isLoaded() const;
|
||||
|
||||
/// Returns true if this link has been resolved
|
||||
[[nodiscard]] bool isResolved() const;
|
||||
|
||||
/// Returns true if the info failed to resolve
|
||||
[[nodiscard]] bool hasError() const;
|
||||
|
||||
/// Returns true if this link has a thumbnail
|
||||
[[nodiscard]] bool hasThumbnail() const;
|
||||
|
||||
/// @brief Returns the tooltip of this link
|
||||
///
|
||||
/// The tooltip contains the URL of the link and any info added by the
|
||||
/// resolver. Resolvers must include the URL.
|
||||
[[nodiscard]] QString tooltip() const;
|
||||
|
||||
/// @brief Returns the thumbnail of this link
|
||||
///
|
||||
/// The thumbnail is provided by the resolver and might not have been
|
||||
/// loaded yet.
|
||||
///
|
||||
/// @pre The caller must check #hasThumbnail() before calling this method
|
||||
[[nodiscard]] ImagePtr thumbnail() const;
|
||||
|
||||
/// @brief Updates the state and emits #stateChanged accordingly
|
||||
///
|
||||
/// @pre The caller must be in the GUI thread.
|
||||
/// @pre @a state must be greater or equal to the current state.
|
||||
/// @see #state(), #stateChanged
|
||||
void setState(State state);
|
||||
|
||||
/// @brief Updates the resolved url of this link
|
||||
///
|
||||
/// @pre The caller must be in the GUI thread.
|
||||
/// @see #url()
|
||||
void setResolvedUrl(QString resolvedUrl);
|
||||
|
||||
/// @brief Updates the tooltip of this link
|
||||
///
|
||||
/// @pre The caller must be in the GUI thread.
|
||||
/// @see #tooltip()
|
||||
void setTooltip(QString tooltip);
|
||||
|
||||
/// @brief Updates the thumbnail of this link
|
||||
///
|
||||
/// The thumbnail is allowed to be empty or nullptr.
|
||||
///
|
||||
/// @pre The caller must be in the GUI thread.
|
||||
/// @see #hasThumbnail(), #thumbnail()
|
||||
void setThumbnail(ImagePtr thumbnail);
|
||||
|
||||
signals:
|
||||
/// @brief Emitted when this link's state changes
|
||||
///
|
||||
/// @param state The new state
|
||||
void stateChanged(State state);
|
||||
|
||||
private:
|
||||
const QString originalUrl_;
|
||||
QString url_;
|
||||
|
||||
QString tooltip_;
|
||||
ImagePtr thumbnail_;
|
||||
|
||||
State state_ = State::Created;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "providers/links/LinkResolver.hpp"
|
||||
|
||||
#include "common/Env.hpp"
|
||||
#include "common/network/NetworkRequest.hpp"
|
||||
#include "common/network/NetworkResult.hpp"
|
||||
#include "providers/links/LinkInfo.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <QStringBuilder>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void LinkResolver::resolve(LinkInfo *info)
|
||||
{
|
||||
using State = LinkInfo::State;
|
||||
|
||||
assert(info);
|
||||
|
||||
if (info->state() != State::Created)
|
||||
{
|
||||
// The link is already resolved or is currently loading
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getSettings()->linkInfoTooltip)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
info->setTooltip("Loading...");
|
||||
info->setState(State::Loading);
|
||||
|
||||
NetworkRequest(Env::get().linkResolverUrl.arg(QString::fromUtf8(
|
||||
QUrl::toPercentEncoding(info->originalUrl(), {}, "/:"))))
|
||||
.caller(info)
|
||||
.timeout(30000)
|
||||
.onSuccess([info](const NetworkResult &result) {
|
||||
const auto root = result.parseJson();
|
||||
QString response;
|
||||
QString url;
|
||||
ImagePtr thumbnail = nullptr;
|
||||
if (root["status"].toInt() == 200)
|
||||
{
|
||||
response = root["tooltip"].toString();
|
||||
|
||||
if (root.contains("thumbnail"))
|
||||
{
|
||||
info->setThumbnail(
|
||||
Image::fromUrl({root["thumbnail"].toString()}));
|
||||
}
|
||||
if (getSettings()->unshortLinks && root.contains("link"))
|
||||
{
|
||||
info->setResolvedUrl(root["link"].toString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response = root["message"].toString();
|
||||
}
|
||||
|
||||
info->setTooltip(QUrl::fromPercentEncoding(response.toUtf8()));
|
||||
info->setState(State::Resolved);
|
||||
})
|
||||
.onError([info](const auto &result) {
|
||||
info->setTooltip(u"No link info found (" % result.formatError() %
|
||||
u')');
|
||||
info->setState(State::Errored);
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class LinkInfo;
|
||||
|
||||
class ILinkResolver
|
||||
{
|
||||
public:
|
||||
ILinkResolver() = default;
|
||||
virtual ~ILinkResolver() = default;
|
||||
ILinkResolver(const ILinkResolver &) = delete;
|
||||
ILinkResolver(ILinkResolver &&) = delete;
|
||||
ILinkResolver &operator=(const ILinkResolver &) = delete;
|
||||
ILinkResolver &operator=(ILinkResolver &&) = delete;
|
||||
|
||||
virtual void resolve(LinkInfo *info) = 0;
|
||||
};
|
||||
|
||||
class LinkResolver : public ILinkResolver
|
||||
{
|
||||
public:
|
||||
LinkResolver() = default;
|
||||
|
||||
/// @brief Loads and updates the link info
|
||||
///
|
||||
/// Calling this with an already resolved or currently loading info is a
|
||||
/// no-op. Loading can be blocked by disabling the "linkInfoTooltip"
|
||||
/// setting. URLs will be unshortened if the "unshortLinks" setting is
|
||||
/// enabled. The resolver is set through Env::linkResolverUrl.
|
||||
///
|
||||
/// @pre @a info must not be nullptr
|
||||
void resolve(LinkInfo *info) override;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
Reference in New Issue
Block a user