Page MenuHomePhorge

No OneTemporary

Size
29 KB
Referenced Files
None
Subscribers
None
diff --git a/src/base/basejob.cpp b/src/base/basejob.cpp
index 0c6b46f..fcd7c8c 100644
--- a/src/base/basejob.cpp
+++ b/src/base/basejob.cpp
@@ -1,292 +1,296 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "libkazv-config.hpp"
#include <lager/util.hpp>
#include <vector>
#include <tuple>
#include "basejob.hpp"
namespace Kazv
{
BaseJob::Get BaseJob::GET{};
BaseJob::Post BaseJob::POST{};
BaseJob::Put BaseJob::PUT{};
BaseJob::Delete BaseJob::DELETE{};
struct BaseJob::Private
{
Private(std::string serverUrl,
std::string requestUrl,
Method method,
std::string token,
ReturnType returnType,
Body body,
Query query,
Header header,
std::string jobId,
std::optional<FileDesc> responseFile);
std::string fullRequestUrl;
Method method;
ReturnType returnType;
Body body;
Query query;
Header header;
JsonWrap data;
std::string jobId;
std::optional<std::string> queueId;
JobQueuePolicy queuePolicy;
std::optional<FileDesc> responseFile;
};
BaseJob::Private::Private(std::string serverUrl,
std::string requestUrl,
Method method,
std::string token,
ReturnType returnType,
Body body,
Query query,
Header header,
std::string jobId,
std::optional<FileDesc> responseFile)
: fullRequestUrl(serverUrl + requestUrl)
, method(std::move(method))
, returnType(returnType)
, body()
, query(std::move(query))
, jobId(std::move(jobId))
, responseFile(std::move(responseFile))
{
auto header_ = header.get();
if (token.size()) {
header_["Authorization"] = "Bearer " + token;
}
// convert to BytesBody, if possible
if (isBodyJson(body)) {
JsonBody j = std::get<JsonBody>(std::move(body));
header_["Content-Type"] = "application/json";
this->body = j.get().dump();
} else if (std::holds_alternative<EmptyBody>(body)) {
this->body = BytesBody();
} else {
this->body = std::move(body);
}
this->header = header_;
}
BaseJob::BaseJob(std::string serverUrl,
std::string requestUrl,
Method method,
std::string jobId,
std::string token,
ReturnType returnType,
Body body,
Query query,
Header header,
std::optional<FileDesc> responseFile)
- : m_d(std::move(Private(serverUrl, requestUrl, method, token,
- returnType, body, query, header, jobId, responseFile)))
+ : m_d(std::make_unique<Private>(serverUrl, requestUrl, method, token,
+ returnType, body, query, header, jobId, responseFile))
{
}
+ KAZV_DEFINE_COPYABLE_UNIQUE_PTR(BaseJob, m_d)
+
+ BaseJob::~BaseJob() = default;
+
bool BaseJob::shouldReturnJson() const
{
return m_d->returnType == ReturnType::Json;
};
std::string BaseJob::url() const
{
return m_d->fullRequestUrl;
};
auto BaseJob::requestBody() const -> Body
{
return m_d->body;
}
auto BaseJob::requestHeader() const -> Header
{
return m_d->header;
}
auto BaseJob::returnType() const -> ReturnType
{
return m_d->returnType;
}
auto BaseJob::requestQuery() const -> Query
{
return m_d->query;
}
auto BaseJob::requestMethod() const -> Method
{
return m_d->method;
}
JsonWrap Response::jsonBody() const
{
return std::get<JsonWrap>(body);
}
json Response::dataJson(const std::string &key) const
{
return extraData.get()[key];
}
std::string Response::dataStr(const std::string &key) const
{
return dataJson(key);
}
std::string Response::jobId() const
{
return dataStr("-job-id");
}
bool BaseJob::contentTypeMatches(immer::array<std::string> expected, std::string actual)
{
for (const auto &i : expected) {
if (i == "*/*"s) {
return true;
} else {
std::size_t pos = i.find("/*"s);
if (pos != std::string::npos) {
std::string majorType(i.data(), i.data() + pos + 1); // includes `/'
if (actual.find(majorType) == 0) {
return true;
}
} else if (i == actual) {
return true;
}
}
}
return false;
}
Response BaseJob::genResponse(Response r) const
{
auto j = m_d->data.get();
j["-job-id"] = m_d->jobId;
r.extraData = j;
return r;
}
void BaseJob::attachData(JsonWrap j)
{
m_d->data = j;
}
BaseJob BaseJob::withData(JsonWrap j) &&
{
auto ret = BaseJob(std::move(*this));
ret.attachData(j);
return ret;
}
BaseJob BaseJob::withData(JsonWrap j) const &
{
auto ret = BaseJob(*this);
ret.attachData(j);
return ret;
}
BaseJob BaseJob::withQueue(std::string id, JobQueuePolicy policy) &&
{
auto ret = BaseJob(std::move(*this));
ret.m_d->queueId = id;
ret.m_d->queuePolicy = policy;
return ret;
}
BaseJob BaseJob::withQueue(std::string id, JobQueuePolicy policy) const &
{
auto ret = BaseJob(*this);
ret.m_d->queueId = id;
ret.m_d->queuePolicy = policy;
return ret;
}
json BaseJob::dataJson(const std::string &key) const
{
return m_d->data.get()[key];
}
std::string BaseJob::dataStr(const std::string &key) const
{
return dataJson(key);
}
std::string BaseJob::jobId() const
{
return m_d->jobId;
}
std::optional<std::string> BaseJob::queueId() const
{
return m_d->queueId;
}
JobQueuePolicy BaseJob::queuePolicy() const
{
return m_d->queuePolicy;
}
std::optional<FileDesc> BaseJob::responseFile() const
{
return m_d->responseFile;
}
std::string Response::errorCode() const
{
// https://matrix.org/docs/spec/client_server/latest#api-standards
if (isBodyJson(body)) {
auto jb = jsonBody();
if (jb.get().contains("errcode")) {
auto code = jb.get()["errcode"].get<std::string>();
if (code != "M_UNKNOWN") {
return code;
}
}
}
return std::to_string(statusCode);
}
std::string Response::errorMessage() const
{
if (isBodyJson(body)) {
auto jb = jsonBody();
if (jb.get().contains("error")) {
auto msg = jb.get()["error"].get<std::string>();
return msg;
}
}
return "";
}
bool operator==(BaseJob a, BaseJob b)
{
return a.m_d->fullRequestUrl == b.m_d->fullRequestUrl
&& a.m_d->method == b.m_d->method
&& a.m_d->returnType == b.m_d->returnType
&& a.m_d->body == b.m_d->body
&& a.m_d->query == b.m_d->query
&& a.m_d->header == b.m_d->header
&& a.m_d->data == b.m_d->data
&& a.m_d->jobId == b.m_d->jobId;
}
bool operator!=(BaseJob a, BaseJob b)
{
return !(a == b);
}
}
diff --git a/src/base/basejob.hpp b/src/base/basejob.hpp
index d575668..91281b8 100644
--- a/src/base/basejob.hpp
+++ b/src/base/basejob.hpp
@@ -1,269 +1,272 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include "libkazv-config.hpp"
#include <optional>
#include <variant>
#include <tuple>
#include <functional>
#include <string>
#include <map>
#include <future>
#include <immer/map.hpp>
#include <immer/array.hpp>
#include <immer/box.hpp>
#include "types.hpp"
-#include "descendent.hpp"
-
+#include "copy-helper.hpp"
#include "file-desc.hpp"
namespace Kazv
{
using Header = immer::box<std::map<std::string, std::string>>;
using BytesBody = Bytes;
using JsonBody = JsonWrap;
using FileBody = FileDesc;
struct EmptyBody {};
using Body = std::variant<EmptyBody, JsonBody, BytesBody, FileBody>;
inline bool operator==(EmptyBody, EmptyBody)
{
return true;
}
inline bool isBodyJson(Body body) {
return std::holds_alternative<JsonBody>(body);
};
enum JobQueuePolicy
{
AlwaysContinue,
CancelFutureIfFailed
};
struct Response {
using StatusCode = int;
StatusCode statusCode;
Body body;
Header header;
JsonWrap extraData;
std::string errorCode() const;
std::string errorMessage() const;
JsonWrap jsonBody() const;
constexpr bool success() const {
return statusCode < 400;
}
json dataJson(const std::string &key) const;
std::string dataStr(const std::string &key) const;
std::string jobId() const;
};
inline bool operator==(Response a, Response b)
{
return a.statusCode == b.statusCode
&& a.body == b.body
&& a.header == b.header
&& a.extraData == b.extraData;
}
class BaseJob
{
public:
struct Get {};
struct Post {};
struct Put {};
struct Delete {};
using Method = std::variant<Get, Post, Put, Delete>;
static Get GET;
static Post POST;
static Put PUT;
static Delete DELETE;
class Query : public std::vector<std::pair<std::string, std::string>>
{
using BaseT = std::vector<std::pair<std::string, std::string>>;
public:
using BaseT::BaseT;
void add(std::string k, std::string v) {
push_back({k, v});
}
};
using Body = ::Kazv::Body;
using BytesBody = ::Kazv::BytesBody;
using JsonBody = ::Kazv::JsonBody;
using EmptyBody = ::Kazv::EmptyBody;
using Header = ::Kazv::Header;
using Response = ::Kazv::Response;
enum ReturnType {
Json,
File,
};
BaseJob(std::string serverUrl,
std::string requestUrl,
Method method,
std::string jobId,
std::string token = {},
ReturnType returnType = ReturnType::Json,
Body body = EmptyBody{},
Query query = {},
Header header = {},
std::optional<FileDesc> responseFile = std::nullopt);
+ KAZV_DECLARE_COPYABLE(BaseJob)
+
+ ~BaseJob();
+
bool shouldReturnJson() const;
std::string url() const;
Body requestBody() const;
Header requestHeader() const;
ReturnType returnType() const;
/// returns the non-encoded query as an array of pairs
Query requestQuery() const;
Method requestMethod() const;
static bool contentTypeMatches(immer::array<std::string> expected, std::string actual);
Response genResponse(Response r) const;
BaseJob withData(JsonWrap j) &&;
BaseJob withData(JsonWrap j) const &;
BaseJob withQueue(std::string id, JobQueuePolicy policy = AlwaysContinue) &&;
BaseJob withQueue(std::string id, JobQueuePolicy policy = AlwaysContinue) const &;
json dataJson(const std::string &key) const;
std::string dataStr(const std::string &key) const;
std::string jobId() const;
std::optional<std::string> queueId() const;
JobQueuePolicy queuePolicy() const;
std::optional<FileDesc> responseFile() const;
protected:
void attachData(JsonWrap data);
private:
friend bool operator==(BaseJob a, BaseJob b);
struct Private;
- Descendent<Private> m_d;
+ std::unique_ptr<Private> m_d;
};
bool operator==(BaseJob a, BaseJob b);
bool operator!=(BaseJob a, BaseJob b);
inline bool operator==(BaseJob::Get, BaseJob::Get) { return true; }
inline bool operator==(BaseJob::Post, BaseJob::Post) { return true; }
inline bool operator==(BaseJob::Put, BaseJob::Put) { return true; }
inline bool operator==(BaseJob::Delete, BaseJob::Delete) { return true; }
namespace detail
{
template<class T>
struct AddToQueryT
{
template<class U>
static void call(BaseJob::Query &q, std::string name, U &&arg) {
q.add(name, std::to_string(std::forward<U>(arg)));
}
};
template<>
struct AddToQueryT<std::string>
{
template<class U>
static void call(BaseJob::Query &q, std::string name, U &&arg) {
q.add(name, std::forward<U>(arg));
}
};
template<>
struct AddToQueryT<bool>
{
template<class U>
static void call(BaseJob::Query &q, std::string name, U &&arg) {
q.add(name, std::forward<U>(arg) ? "true"s : "false"s);
}
};
template<class T>
struct AddToQueryT<immer::array<T>>
{
template<class U>
static void call(BaseJob::Query &q, std::string name, U &&arg) {
for (auto v : std::forward<U>(arg)) {
q.add(name, v);
}
}
};
template<>
struct AddToQueryT<json>
{
// https://github.com/nlohmann/json/issues/2040
static void call(BaseJob::Query &q, std::string /* name */, const json &arg) {
// assume v is string type
for (auto [k, v] : arg.items()) {
q.add(k, v);
}
}
};
}
template<class T>
inline void addToQuery(BaseJob::Query &q, std::string name, T &&arg)
{
detail::AddToQueryT<std::decay_t<T>>::call(q, name, std::forward<T>(arg));
}
namespace detail
{
template<class T>
struct AddToQueryIfNeededT
{
template<class U>
static void call(BaseJob::Query &q, std::string name, U &&arg) {
using ArgT = std::decay_t<U>;
if constexpr (detail::hasEmptyMethod(boost::hana::type_c<ArgT>)) {
if (! arg.empty()) {
addToQuery(q, name, std::forward<U>(arg));
}
} else {
addToQuery(q, name, std::forward<U>(arg));
}
}
};
template<class T>
struct AddToQueryIfNeededT<std::optional<T>>
{
template<class U>
static void call(BaseJob::Query &q, std::string name, U &&arg) {
if (arg.has_value()) {
addToQuery(q, name, std::forward<U>(arg).value());
}
}
};
}
template<class T>
inline void addToQueryIfNeeded(BaseJob::Query &q, std::string name, T &&arg)
{
detail::AddToQueryIfNeededT<std::decay_t<T>>::call(q, name, std::forward<T>(arg));
}
}
diff --git a/src/base/copy-helper.hpp b/src/base/copy-helper.hpp
index 70e8d43..ab7514b 100644
--- a/src/base/copy-helper.hpp
+++ b/src/base/copy-helper.hpp
@@ -1,40 +1,40 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
-#include <libkazv-config.hpp>
+#include "libkazv-config.hpp"
#define KAZV_DECLARE_COPYABLE(typeName) \
typeName(const typeName &that); \
typeName(typeName &&that); \
typeName &operator=(const typeName &that); \
typeName &operator=(typeName &&that);
#define KAZV_DEFINE_COPYABLE_UNIQUE_PTR(typeName, privateName) \
typeName::typeName(const typeName &that) \
: privateName(std::make_unique<decltype(privateName)::element_type>(*(that.privateName))) \
{ \
} \
typeName::typeName(typeName &&that) \
: privateName(std::move(that.privateName)) \
{ \
} \
typeName &typeName::operator=(const typeName &that) \
{ \
if (privateName != that.privateName) { \
privateName.reset(); \
privateName = std::make_unique<decltype(privateName)::element_type>(*(that.privateName)); \
} \
return *this; \
} \
typeName &typeName::operator=(typeName &&that) \
{ \
if (privateName != that.privateName) { \
privateName.reset(); \
privateName = std::move(that.privateName); \
} \
return *this; \
}
diff --git a/src/base/descendent.hpp b/src/base/descendent.hpp
deleted file mode 100644
index 960fd50..0000000
--- a/src/base/descendent.hpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * This file is part of libkazv.
- * SPDX-FileCopyrightText: 2020 Tusooa Zhu
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-
-#pragma once
-#include "libkazv-config.hpp"
-
-#include <memory>
-
-namespace Kazv
-{
- template<typename T>
- class Descendent
- {
- struct concept
- {
- virtual ~concept() = default;
- virtual const T *ptr() const = 0;
- virtual T *ptr() = 0;
- virtual const T &ref() const = 0;
- virtual T &ref() = 0;
- virtual T &&rref() && = 0;
- virtual std::unique_ptr<concept> clone() const = 0;
- };
- template<typename U>
- struct model : public concept
- {
- model(U x) : instance(std::move(x)) {}
- const T *ptr() const { return &instance; }
- T *ptr() { return &instance; }
- const T &ref() const { return instance; }
- T &ref() { return instance; }
- T &&rref() && { return std::move(instance); }
- // or std::unique_ptr<model<U> >(new model<U>(U(instance))) if you do not have C++14
- std::unique_ptr<concept> clone() const { return std::make_unique<model<U> >(U(instance)); }
- U instance;
- };
-
- std::unique_ptr<concept> m_d;
- public:
- Descendent() : m_d(std::make_unique<model<T>>(T())) {}
-
- template<typename U>
- Descendent(U x) : m_d(std::make_unique<model<U> >(std::move(x))) {}
-
- Descendent(const Descendent & that) : m_d(std::move(that.m_d->clone())) {}
- Descendent(Descendent && that) : m_d(std::move(that.m_d)) {}
-
- Descendent & operator=(const Descendent &that) { Descendent t(that); *this = std::move(t); return *this; }
- Descendent & operator=(Descendent && that) { m_d = std::move(that.m_d); return *this; }
-
- const T &ref() const { return m_d->ref(); }
- const T &constRef() const { return m_d->ref(); }
- const T &ref() { return m_d->ref(); }
- T &&rref() && { return m_d->rref(); }
-
- const T *data() const { return m_d->ptr(); }
- const T *constData() const { return m_d->ptr(); }
- T *data() { return m_d->ptr(); }
- const T *operator->() const { return m_d->ptr(); }
- T *operator->() { return m_d->ptr(); }
-
- template<class Derived> bool isa() const {
- return dynamic_cast<Derived *>(constData());
- }
-
- template<class Derived> Derived &cast() {
- return dynamic_cast<Derived &>(ref());
- }
- template<class Derived> const Derived &cast() const {
- return dynamic_cast<const Derived &>(ref());
- }
- template<class Derived> const Derived &constCast() const {
- return dynamic_cast<const Derived &>(ref());
- }
- template<class Derived> const Derived &rCast() && {
- return dynamic_cast<Derived &&>(rref());
- }
- };
-}
diff --git a/src/base/types.hpp b/src/base/types.hpp
index df6470c..60cb542 100644
--- a/src/base/types.hpp
+++ b/src/base/types.hpp
@@ -1,223 +1,222 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include "libkazv-config.hpp"
#include <optional>
#include <string>
#include <variant>
#include <nlohmann/json.hpp>
#include <immer/array.hpp>
#include <immer/flex_vector.hpp>
#include <immer/map.hpp>
#include <boost/hana/type.hpp>
#include <lager/util.hpp>
#include "jsonwrap.hpp"
#include "event.hpp"
-#include "descendent.hpp"
namespace Kazv
{
using Bytes = std::string;
enum Status : bool
{
FAIL,
SUCC,
};
namespace detail
{
constexpr auto hasEmptyMethod = boost::hana::is_valid(
[](auto t) -> decltype((void)std::declval<typename decltype(t)::type>().empty()) {});
template<class U>
struct AddToJsonIfNeededT
{
template<class T>
static void call(json &j, std::string name, T &&arg) {
using Type = std::decay_t<T>;
if constexpr (detail::hasEmptyMethod(boost::hana::type_c<Type>)) {
if (! arg.empty()) {
j[name] = std::forward<T>(arg);
}
} else {
j[name] = std::forward<T>(arg);
}
}
};
template<class U>
struct AddToJsonIfNeededT<std::optional<U>>
{
template<class T>
static void call(json &j, std::string name, T &&arg) {
if (arg.has_value()) {
j[name] = std::forward<T>(arg).value();
}
}
};
}
template<class T>
inline void addToJsonIfNeeded(json &j, std::string name, T &&arg)
{
detail::AddToJsonIfNeededT<std::decay_t<T>>::call(j, name, std::forward<T>(arg));
};
// Provide a non-destructive way to add the map
// to json.
template<class MapT,
// disallow json object here
std::enable_if_t<!std::is_same_v<std::decay_t<MapT>, json>
&& !std::is_same_v<std::decay_t<MapT>, JsonWrap>, int> = 0>
inline void addPropertyMapToJson(json &j, MapT &&arg)
{
for (auto kv : std::forward<MapT>(arg)) {
auto [k, v] = kv;
j[k] = v;
}
};
inline void addPropertyMapToJson(json &j, const json &arg)
{
for (auto kv : arg.items()) {
auto [k, v] = kv;
j[k] = v;
}
};
using EventList = immer::flex_vector<Event>;
using namespace std::string_literals;
struct Null {};
using Variant = std::variant<std::string, JsonWrap, Null>;
namespace detail
{
struct DefaultValT
{
template<class T>
constexpr operator T() const {
return T();
}
};
}
constexpr detail::DefaultValT DEFVAL;
enum RoomMembership
{
Invite, Join, Leave
};
namespace detail
{
// emulates declval() but returns lvalue reference
template<class T>
typename std::add_lvalue_reference<T>::type declref() noexcept;
}
}
namespace nlohmann {
template <class T, class V>
struct adl_serializer<immer::map<T, V>> {
static void to_json(json& j, immer::map<T, V> map) {
if constexpr (std::is_same_v<T, std::string>) {
j = json::object();
for (auto [k, v] : map) {
j[k] = v;
}
} else {
j = json::array();
for (auto [k, v] : map) {
j.push_back(k);
j.push_back(v);
}
}
}
static void from_json(const json& j, immer::map<T, V> &m) {
immer::map<T, V> ret;
if constexpr (std::is_same_v<T, std::string>) {
for (const auto &[k, v] : j.items()) {
ret = std::move(ret).set(k, v);
}
} else {
for (std::size_t i = 0; i < j.size(); i += 2) {
ret = std::move(ret).set(j[i], j[i+1]);
}
}
m = ret;
}
};
template <class T>
struct adl_serializer<immer::array<T>> {
static void to_json(json& j, immer::array<T> arr) {
j = json::array();
for (auto i : arr) {
j.push_back(json(i));
}
}
static void from_json(const json& j, immer::array<T> &a) {
immer::array<T> ret;
if (j.is_array()) {
for (const auto &i : j) {
ret = std::move(ret).push_back(i);
}
}
a = ret;
}
};
template <class T>
struct adl_serializer<immer::flex_vector<T>> {
static void to_json(json& j, immer::flex_vector<T> arr) {
j = json::array();
for (auto i : arr) {
j.push_back(json(i));
}
}
static void from_json(const json& j, immer::flex_vector<T> &a) {
immer::flex_vector<T> ret;
if (j.is_array()) {
for (const auto &i : j) {
ret = std::move(ret).push_back(i.get<T>());
}
}
a = ret;
}
};
template <>
struct adl_serializer<Kazv::Variant> {
static void to_json(json& j, const Kazv::Variant &var) {
std::visit(lager::visitor{
[&j](std::string i) { j = i; },
[&j](Kazv::JsonWrap i) { j = i; },
[&j](Kazv::Null) { j = nullptr; }
}, var);
}
static void from_json(const json& j, Kazv::Variant &var) {
if (j.is_string()) {
var = j.get<std::string>();
} else if (j.is_null()) {
var = Kazv::Null{};
} else { // is object
var = Kazv::Variant(Kazv::JsonWrap(j));
}
}
};
}
diff --git a/src/job/cprjobhandler.hpp b/src/job/cprjobhandler.hpp
index eba4497..07306e0 100644
--- a/src/job/cprjobhandler.hpp
+++ b/src/job/cprjobhandler.hpp
@@ -1,39 +1,38 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020 Tusooa Zhu
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <memory>
#include <boost/asio.hpp>
#include "jobinterface.hpp"
-#include "descendent.hpp"
namespace Kazv
{
struct CprJobHandler : public JobInterface
{
CprJobHandler(boost::asio::io_context::executor_type executor);
~CprJobHandler() override;
void async(std::function<void()> func) override;
void setTimeout(std::function<void()> func, int ms,
std::optional<std::string> timerId = std::nullopt) override;
void setInterval(std::function<void()> func, int ms,
std::optional<std::string> timerId = std::nullopt) override;
void cancel(std::string timerId) override;
void submit(BaseJob job,
std::function<void(Response)> callback) override;
void stop();
private:
struct Private;
std::unique_ptr<Private> m_d;
};
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 8:11 PM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55431
Default Alt Text
(29 KB)

Event Timeline