diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f0f2e4501..435f5e75a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,45 +2,59 @@ cmake_minimum_required(VERSION 3.16) project(deltachat LANGUAGES C) include(GNUInstallDirs) +option(WITH_JSONRPC_BINDINGS "Generate jsonrpc bindings" OFF) + find_program(CARGO cargo) if(APPLE) - set(DYNAMIC_EXT "dylib") + set(DYNAMIC_EXT "dylib") elseif(UNIX) - set(DYNAMIC_EXT "so") + set(DYNAMIC_EXT "so") else() - set(DYNAMIC_EXT "dll") + set(DYNAMIC_EXT "dll") endif() if(DEFINED ENV{CARGO_BUILD_TARGET}) - set(ARCH_DIR "$ENV{CARGO_BUILD_TARGET}") + set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/$ENV{CARGO_BUILD_TARGET}/release") else() - set(ARCH_DIR "./") + set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/release") endif() -add_custom_command( - OUTPUT - "${CMAKE_BINARY_DIR}/target/release/libdeltachat.a" - "${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}" - "${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc" - COMMAND - PREFIX=${CMAKE_INSTALL_PREFIX} - LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR} - INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR} - ${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi -) +if(WITH_JSONRPC_BINDINGS) + set(JSONRPC_ARGS --package deltachat-jsonrpc-bindings) +endif() add_custom_target( - lib_deltachat - ALL - DEPENDS - "${CMAKE_BINARY_DIR}/target/release/libdeltachat.a" - "${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}" - "${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc" + lib_deltachat + ALL + COMMAND + ${CMAKE_COMMAND} -E env + CARGO_TARGET_DIR="${CMAKE_BINARY_DIR}/target" + PREFIX="${CMAKE_INSTALL_PREFIX}" + LIBDIR="${CMAKE_INSTALL_FULL_LIBDIR}" + INCLUDEDIR="${CMAKE_INSTALL_FULL_INCLUDEDIR}" + ${CARGO} build --release --package deltachat_ffi ${JSONRPC_ARGS} + WORKING_DIRECTORY + "${CMAKE_CURRENT_SOURCE_DIR}" ) -install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +install(FILES "deltachat-ffi/deltachat.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +install(FILES "${CARGO_OUT_DIR}/libdeltachat.a" DESTINATION "${CMAKE_INSTALL_LIBDIR}") +install(FILES "${CARGO_OUT_DIR}/libdeltachat.${DYNAMIC_EXT}" DESTINATION "${CMAKE_INSTALL_LIBDIR}") +install(FILES "${CARGO_OUT_DIR}/pkgconfig/deltachat.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + +if(WITH_JSONRPC_BINDINGS) + install( + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/generated/types.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/generated/client.hpp" + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc/generated" + ) + install( + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp" + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc" + ) +endif() diff --git a/deltachat-jsonrpc-bindings/build.rs b/deltachat-jsonrpc-bindings/build.rs index 073e3f4e16..744a97fec0 100644 --- a/deltachat-jsonrpc-bindings/build.rs +++ b/deltachat-jsonrpc-bindings/build.rs @@ -1,6 +1,7 @@ -use deltachat_jsonrpc::api::write_ts_bindings; +use deltachat_jsonrpc::api::{write_qt_bindings, write_ts_bindings}; use std::path::Path; fn main() { write_ts_bindings(Path::new("typescript/generated")); + write_qt_bindings(Path::new("qt/generated"), "deltachat"); } diff --git a/deltachat-jsonrpc-bindings/qt/.gitignore b/deltachat-jsonrpc-bindings/qt/.gitignore new file mode 100644 index 0000000000..86d4c2dd38 --- /dev/null +++ b/deltachat-jsonrpc-bindings/qt/.gitignore @@ -0,0 +1 @@ +generated diff --git a/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp b/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp new file mode 100644 index 0000000000..ebe254b351 --- /dev/null +++ b/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include "deltachat.h" +#include "generated/client.hpp" +#include "generated/types.hpp" + +#include +#include +#include + +namespace deltachat { + +class CffiTransport : public Transport { + using CompletionHandler = Transport::CompletionHandler; + +public: + explicit CffiTransport(dc_accounts_t *accounts) + : jsonrpc_(dc_jsonrpc_init(accounts)) { + if (!jsonrpc_) + std::abort(); + thread_ = std::thread([this] { run(); }); + } + + ~CffiTransport() override { + done_ = true; + // Unblock dc_jsonrpc_next_response by sending a dummy request + if (jsonrpc_) + dc_jsonrpc_request( + jsonrpc_, + "{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"get_system_info\"}"); + if (thread_.joinable()) + thread_.join(); + std::lock_guard lk(mu_); + for (auto &[id, cb] : pending_) { + cb(Result::error(-32060, "Transport destructed")); + } + pending_.clear(); + if (jsonrpc_) + dc_jsonrpc_unref(jsonrpc_); + } + + virtual void send(const QString method, const QJsonValue params, + CompletionHandler onCompleted) override { + uint32_t id = next_id_++; + QJsonObject envelope{ + {"jsonrpc", "2.0"}, + {"id", static_cast(id)}, + {"method", method}, + {"params", params}, + }; + + { + std::lock_guard lk(mu_); + pending_[id] = std::move(onCompleted); + } + + QByteArray json = QJsonDocument(envelope).toJson(QJsonDocument::Compact); + dc_jsonrpc_request(jsonrpc_, json.constData()); + } + +private: + void run() { + while (!done_) { + char *raw_json = dc_jsonrpc_next_response(jsonrpc_); + if (!raw_json) { + break; + } + QByteArray json{raw_json}; + dc_str_unref(raw_json); + if (done_) + break; + + QJsonObject obj = QJsonDocument::fromJson(json).object(); + + if (!obj["id"].isDouble()) { + qCritical() << "No valid rpc id in" << QString{json}; + continue; + } + uint32_t id = static_cast(obj["id"].toInt()); + + CompletionHandler cb; + { + std::lock_guard lk(mu_); + if (auto nh = pending_.extract(id)) { + cb = std::move(nh.mapped()); + } else { + qCritical() << "Could not map response" << QString{json}; + continue; + } + } + cb(parseResult(obj)); + } + } + +private: + dc_jsonrpc_instance_t *jsonrpc_; + std::thread thread_; + std::mutex mu_; + std::atomic next_id_{1}; + std::atomic done_{false}; + std::unordered_map pending_; +}; + +class CffiDeltaChat : public RawClient { +public: + explicit CffiDeltaChat(dc_accounts_t *accounts) + : RawClient(std::make_unique(accounts)) {} +}; + +} // namespace deltachat +Q_DECLARE_METATYPE(deltachat::CffiDeltaChat *) diff --git a/deltachat-jsonrpc-bindings/qt/generated/.gitkeep b/deltachat-jsonrpc-bindings/qt/generated/.gitkeep new file mode 100644 index 0000000000..e69de29bb2