diff --git a/CMakeLists.txt b/CMakeLists.txt index 976b68d691f8a5d2e9488313e18e87bc145cac4a..78f117060d398f43d6a4e7eb90eaf7e62a273dd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,6 +316,7 @@ SET( libringclient_LIB_SRCS src/newaccountmodel.cpp src/callbackshandler.cpp src/behaviorcontroller.cpp + src/datatransfermodel.cpp #Data collections src/transitionalpersonbackend.cpp @@ -482,6 +483,8 @@ SET(libringclient_api_LIB_HDRS src/api/conversationmodel.h src/api/profile.h src/api/behaviorcontroller.h + src/api/datatransfermodel.h + src/api/datatransfer.h ) diff --git a/src/api/datatransfer.h b/src/api/datatransfer.h new file mode 100644 index 0000000000000000000000000000000000000000..f9bf4b35e8d5940bd88fccd172ae231d4169f234 --- /dev/null +++ b/src/api/datatransfer.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2018 Savoir-faire Linux * + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + ***************************************************************************/ +#pragma once + +// LRC +#include "typedefs.h" + +namespace lrc { namespace api { + +namespace datatransfer { + +enum class Status { + on_connection, // outgoing tx: wait for connection/acceptance, incoming tx: wait for local acceptance + on_progress, // connected, data transfer progress reporting + success, // transfer finished with success, all data sent + stop_by_peer, // error: transfer terminated by peer + stop_by_host, // eror: transfer terminated by local host + unjoinable_peer, // error: (outgoing only) peer connection failed + invalid_pathname, // error: (file transfer only) given file is not a valid + unsupported, // error: unable to do the transfer (generic error) +}; + +struct Info +{ + std::string uid; ///< long-term and unique identifier (used for historic) + Status status; + bool isOutgoing; + std::size_t totalSize; + std::size_t progress; ///< if status >= on_progress, gives number of bytes tx/rx until now + std::string path; + std::string displayName; +}; + +} // namespace lrc::api::datatransfer + +}} // namespace lrc::api diff --git a/src/api/datatransfermodel.h b/src/api/datatransfermodel.h new file mode 100644 index 0000000000000000000000000000000000000000..bc5d542b36f97869eb9fc571636e5751d07f4a59 --- /dev/null +++ b/src/api/datatransfermodel.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * Copyright (C) 2018 Savoir-faire Linux * + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + ***************************************************************************/ +#pragma once + +// Std +#include <string> +#include <memory> +#include <ios> + +// Qt +#include <qobject.h> + +// Data +#include "api/datatransfer.h" +#include "api/account.h" + +// LRC +#include "typedefs.h" + +namespace lrc { + +class CallbacksHandler; +class Database; + +namespace api { + +class BehaviorController; + +/** + * @brief Class that manages data transfer. + */ +class LIB_EXPORT DataTransferModel : public QObject { + Q_OBJECT + +public: + DataTransferModel(Database& database, + const CallbacksHandler& callbacksHandler, + const api::BehaviorController& behaviorController); + ~DataTransferModel(); + + std::vector<std::string> transferIdList() const; + + std::string sendFile(const std::string& account_id, const std::string& peer_uri, + const std::string& file_path, const std::string& display_name); + + datatransfer::Info transferInfo(const std::string& uid); + + std::streamsize bytesProgress(const std::string& id); + + void acceptFile(const std::string& id, const std::string& file_path, std::size_t offset); + + void cancel(const std::string& id); + +Q_SIGNALS: + /** + * Connect this signal to know when a data transfer is incoming. + * \note the unique identification is generated by the libring and its unicity scope is limited + * to the libring process life. + * + * @param transfer_id unique identification of incoming data transfer. + * @param display_name a free identification string given by sender. + * @oaram size total number of bytes of the transfer (including offset). + * @oaram offset offset of first given bytes for continued transfer. + */ + void incomingTransfer(const std::string& uid, const std::string& display_name, + const std::size_t size, const std::size_t offset); + + /** + * Connect this signal to know when an existing data transfer has changed of status. + * @param transfer_id unique identification of incoming data transfer. + * @param status reported status. + */ + void transferStatusChanged(const std::string& uid, datatransfer::Status status); + +private: + class Impl; + std::unique_ptr<Impl> pimpl_; +}; + +}} // namespace lrc::api diff --git a/src/datatransfermodel.cpp b/src/datatransfermodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5b3f37b6f8bbcaedaf031c1a37928c4d4a7aa828 --- /dev/null +++ b/src/datatransfermodel.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** + * Copyright (C) 2018 Savoir-faire Linux * + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +// LRC +#include "api/datatransfermodel.h" +#include "api/behaviorcontroller.h" +#include "callbackshandler.h" +#include "database.h" + +// Dbus +#include "dbus/configurationmanager.h" + +// DRing +#include <datatransfer_interface.h> + +// Std +#include <map> +#include <stdexcept> + +// Qt +#include <QUuid> + +namespace lrc { namespace api { + +/// DRING to LRC event code conversion +static inline +datatransfer::Status +convertDataTransferEvent(DRing::DataTransferEventCode event) +{ + switch (event) { + case DRing::DataTransferEventCode::created: return datatransfer::Status::on_connection; + case DRing::DataTransferEventCode::unsupported: return datatransfer::Status::unsupported; + case DRing::DataTransferEventCode::wait_peer_acceptance: return datatransfer::Status::on_connection; + case DRing::DataTransferEventCode::wait_host_acceptance: return datatransfer::Status::on_connection; + case DRing::DataTransferEventCode::ongoing: return datatransfer::Status::on_progress; + case DRing::DataTransferEventCode::finished: return datatransfer::Status::success; + case DRing::DataTransferEventCode::closed_by_host: return datatransfer::Status::stop_by_host; + case DRing::DataTransferEventCode::closed_by_peer: return datatransfer::Status::stop_by_peer; + case DRing::DataTransferEventCode::invalid_pathname: return datatransfer::Status::invalid_pathname; + case DRing::DataTransferEventCode::unjoinable_peer: return datatransfer::Status::unjoinable_peer; + } + throw std::runtime_error("BUG: broken convertDataTransferEvent() switch"); +} + +class DataTransferModel::Impl : public QObject +{ + Q_OBJECT + +public: + Impl(DataTransferModel& up_link, + Database& database, + const CallbacksHandler& callbacksHandler, + const api::BehaviorController& behaviorController); + + DataTransferModel& upLink; + std::map<DRing::DataTransferId, std::string> dring2lrcIdMap; + std::map<std::string, DRing::DataTransferId> lrc2dringIdMap; // stricly the reverse map of dring2lrcIdMap + Database& database; + const CallbacksHandler& callbacksHandler; + const BehaviorController& behaviorController; + + std::string registerTransferId(DRing::DataTransferId id); + +public Q_SLOTS: + void slotDataTransferEvent(qulonglong id, uint code); +}; + +DataTransferModel::Impl::Impl(DataTransferModel& up_link, + Database& database, + const CallbacksHandler& callbacksHandler, + const api::BehaviorController& behaviorController) + : QObject {} + , behaviorController {behaviorController} + , callbacksHandler {callbacksHandler} + , database {database} + , upLink {up_link} +{ + connect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::dataTransferEvent, + this, &DataTransferModel::Impl::slotDataTransferEvent); +} + +std::string +DataTransferModel::Impl::registerTransferId(DRing::DataTransferId dring_id) +{ + const auto& iter = dring2lrcIdMap.find(dring_id); + if (iter != std::cend(dring2lrcIdMap)) + return iter->second; + while (true) { + auto res = dring2lrcIdMap.emplace(dring_id, QUuid::createUuid().toString().toStdString()); + if (res.second) { + lrc2dringIdMap.emplace(res.first->second, dring_id); + return res.first->second; + } + } +} + +void +DataTransferModel::Impl::slotDataTransferEvent(qulonglong dring_id, uint code) +{ + auto lrc_id = registerTransferId(dring_id); + auto event = DRing::DataTransferEventCode(code); + if (event == DRing::DataTransferEventCode::created) { + auto info = static_cast<DataTransferInfo>(ConfigurationManager::instance().dataTransferInfo(dring_id)); + if (!info.isOutgoing) { + emit upLink.incomingTransfer(lrc_id, info.displayName.toStdString(), info.totalSize, info.bytesProgress); + return; + } + } + + emit upLink.transferStatusChanged(lrc_id, convertDataTransferEvent(event)); +} + +DataTransferModel::DataTransferModel(Database& database, + const CallbacksHandler& callbacksHandler, + const api::BehaviorController& behaviorController) + : QObject() + , pimpl_ { std::make_unique<Impl>(*this, database, callbacksHandler, behaviorController) } +{} + +DataTransferModel::~DataTransferModel() = default; + +std::vector<std::string> +DataTransferModel::transferIdList() const +{ + VectorULongLong dring_list = ConfigurationManager::instance().dataTransferList(); + for (auto dring_id : dring_list) { + pimpl_->registerTransferId(dring_id); + } + std::vector<std::string> result; + result.reserve(dring_list.size()); + for (auto& item : pimpl_->lrc2dringIdMap) { + result.push_back(item.first); + } + return result; +} + +std::string +DataTransferModel::sendFile(const std::string& account_id, const std::string& peer_uri, + const std::string& file_path, const std::string& display_name) +{ + auto dring_id = static_cast<DRing::DataTransferId>(ConfigurationManager::instance().sendFile( + QString::fromStdString(account_id), + QString::fromStdString(peer_uri), + QString::fromStdString(file_path), + QString::fromStdString(display_name))); + return pimpl_->registerTransferId(dring_id); +} + +datatransfer::Info +DataTransferModel::transferInfo(const std::string& lrc_id) +{ + auto dring_id = pimpl_->lrc2dringIdMap.at(lrc_id); + auto dring_info = static_cast<DataTransferInfo>(ConfigurationManager::instance().dataTransferInfo(dring_id)); + datatransfer::Info lrc_info; + lrc_info.uid = lrc_id; + lrc_info.isOutgoing = dring_info.lastEvent; + lrc_info.progress = dring_info.lastEvent; + lrc_info.path = dring_info.displayName.toStdString(); + lrc_info.displayName = dring_info.displayName.toStdString(); + lrc_info.status = convertDataTransferEvent(DRing::DataTransferEventCode(dring_info.lastEvent)); + return lrc_info; +} + +std::streamsize +DataTransferModel::bytesProgress(const std::string& lrc_id) +{ + return ConfigurationManager::instance().dataTransferBytesSent(pimpl_->lrc2dringIdMap.at(lrc_id)); +} + +void +DataTransferModel::acceptFile(const std::string& lrc_id, + const std::string& file_path, + std::size_t offset) +{ + auto dring_id = pimpl_->lrc2dringIdMap.at(lrc_id); + ConfigurationManager::instance().acceptFileTransfer(dring_id, QString::fromStdString(file_path), offset); +} + +void +DataTransferModel::cancel(const std::string& lrc_id) +{ + auto dring_id = pimpl_->lrc2dringIdMap.at(lrc_id); + ConfigurationManager::instance().cancelDataTransfer(dring_id); +} + +}} // namespace lrc::api + +#include "api/moc_datatransfermodel.cpp" +#include "datatransfermodel.moc" diff --git a/src/dbus/metatypes.h b/src/dbus/metatypes.h index 363d451c0cdfb69ca3f2499611759e14a11e0f15..29df7c3dc0f6e8d6daad8ac86f5f29f7462ec2cf 100644 --- a/src/dbus/metatypes.h +++ b/src/dbus/metatypes.h @@ -37,6 +37,7 @@ Q_DECLARE_METATYPE(VectorMapStringString) Q_DECLARE_METATYPE(MapStringMapStringStringList) Q_DECLARE_METATYPE(VectorInt) Q_DECLARE_METATYPE(VectorUInt) +Q_DECLARE_METATYPE(VectorULongLong) Q_DECLARE_METATYPE(VectorString) Q_DECLARE_METATYPE(MapStringVectorString) Q_DECLARE_METATYPE(VectorVectorByte) @@ -53,6 +54,7 @@ inline void registerCommTypes() { qDBusRegisterMetaType<MapStringMapStringVectorString>(); qDBusRegisterMetaType<VectorInt> (); qDBusRegisterMetaType<VectorUInt> (); + qDBusRegisterMetaType<VectorULongLong> (); qDBusRegisterMetaType<VectorString> (); qDBusRegisterMetaType<MapStringVectorString> (); qDBusRegisterMetaType<VectorVectorByte> (); diff --git a/src/qtwrapper/configurationmanager_wrap.h b/src/qtwrapper/configurationmanager_wrap.h index 4c661162735b1abb71ccc3982a175883cdea21c3..c3c5eb4b72c3d0cbd447c75aa313e07e9ff8001c 100644 --- a/src/qtwrapper/configurationmanager_wrap.h +++ b/src/qtwrapper/configurationmanager_wrap.h @@ -30,6 +30,7 @@ #include <future> #include <configurationmanager_interface.h> +#include <datatransfer_interface.h> #include <account_const.h> #include "typedefs.h" @@ -44,12 +45,14 @@ class ConfigurationManagerInterface: public QObject public: std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; + std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> dataXferHandlers; ConfigurationManagerInterface() { setObjectName("ConfigurationManagerInterface"); using DRing::exportable_callback; using DRing::ConfigurationSignal; using DRing::AudioSignal; + using DRing::DataTransferSignal; setObjectName("ConfigurationManagerInterface"); confHandlers = { @@ -151,6 +154,13 @@ public: Q_EMIT this->contactRemoved(QString(account_id.c_str()), QString(uri.c_str()), banned); }), }; + + dataXferHandlers = { + exportable_callback<DataTransferSignal::DataTransferEvent>( + [this] (const uint64_t& transfer_id, const uint32_t& code) { + Q_EMIT this->dataTransferEvent(transfer_id, code); + }), + }; } ~ConfigurationManagerInterface() {} @@ -576,6 +586,38 @@ public Q_SLOTS: // METHODS return convertMap(DRing::getContactDetails(accountID.toStdString(), uri.toStdString())); } + VectorULongLong dataTransferList() { + return convertVectorULongLong(DRing::dataTransferList()); + } + + uint64_t sendFile(const QString& account_id, const QString& peer_uri, const QString& file_path, const QString& display_name) { + return DRing::sendFile(account_id.toStdString(), peer_uri.toStdString(), file_path.toStdString(), display_name.toStdString()); + } + + DataTransferInfo dataTransferInfo(uint64_t transfer_id) { + auto dring_info = DRing::dataTransferInfo(transfer_id); + DataTransferInfo lrc_info; + lrc_info.isOutgoing = dring_info.isOutgoing; + lrc_info.lastEvent = uint(dring_info.lastEvent); + lrc_info.totalSize = dring_info.totalSize; + lrc_info.bytesProgress = dring_info.bytesProgress; + lrc_info.displayName = QString::fromStdString(dring_info.displayName); + lrc_info.path = QString::fromStdString(dring_info.path); + return lrc_info; + } + + uint64_t dataTransferBytesSent(uint64_t transfer_id) { + return DRing::dataTransferBytesSent(transfer_id); + } + + void acceptFileTransfer(uint64_t transfer_id, const QString& file_path, uint64_t offset) { + DRing::acceptFileTransfer(transfer_id, file_path.toStdString(), offset); + } + + void cancelDataTransfer(uint64_t transfer_id) { + DRing::cancelDataTransfer(transfer_id); + } + Q_SIGNALS: // SIGNALS void volumeChanged(const QString& device, double value); void accountsChanged(); @@ -601,8 +643,7 @@ Q_SIGNALS: // SIGNALS void migrationEnded(const QString &accountID, const QString &result); void contactAdded(const QString &accountID, const QString &uri, bool banned); void contactRemoved(const QString &accountID, const QString &uri, bool banned); - - + void dataTransferEvent(qulonglong transfer_id, uint code); }; namespace org { namespace ring { namespace Ring { diff --git a/src/qtwrapper/conversions_wrap.hpp b/src/qtwrapper/conversions_wrap.hpp index e495fa9d2a809636ae6b796968598ec6ce1fd974..f20030423e5a6edc17c653302c7f29251a00c3d6 100644 --- a/src/qtwrapper/conversions_wrap.hpp +++ b/src/qtwrapper/conversions_wrap.hpp @@ -79,6 +79,14 @@ inline VectorString convertVectorString(const std::vector<std::string>& v) { return temp; } +inline VectorULongLong convertVectorULongLong(const std::vector<uint64_t>& v) { + VectorULongLong temp; + for (const auto& x : v) { + temp.push_back(x); + } + return temp; +} + inline std::vector<std::string> convertStringList(const QStringList& v) { std::vector<std::string> temp; for (const auto& x : v) { diff --git a/src/qtwrapper/instancemanager.cpp b/src/qtwrapper/instancemanager.cpp index 509d53096837ae4cecb50ae8ad6e32b5939697b1..ef7e656f5c09a25cdf386ede692c17ec90a6d684 100644 --- a/src/qtwrapper/instancemanager.cpp +++ b/src/qtwrapper/instancemanager.cpp @@ -38,6 +38,7 @@ InstanceManagerInterface::InstanceManagerInterface() : m_pTimer(nullptr) using DRing::CallSignal; using DRing::ConfigurationSignal; using DRing::PresenceSignal; + using DRing::DataTransferSignal; #ifdef ENABLE_VIDEO using DRing::VideoSignal; @@ -62,6 +63,7 @@ InstanceManagerInterface::InstanceManagerInterface() : m_pTimer(nullptr) registerCallHandlers(CallManager::instance().callHandlers); registerConfHandlers(ConfigurationManager::instance().confHandlers); registerPresHandlers(PresenceManager::instance().presHandlers); + registerDataXferHandlers(ConfigurationManager::instance().dataXferHandlers); #ifdef ENABLE_VIDEO registerVideoHandlers(VideoManager::instance().videoHandlers); #endif diff --git a/src/typedefs.h b/src/typedefs.h index df1d542da09d1e47872eb150ee0c854cbd6fc676..8dedadcdea5ef01d4cd59b887c2cd448dd1344cc 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -30,6 +30,7 @@ typedef QMap<QString, QString> MapStringString typedef QMap<QString, int> MapStringInt ; typedef QVector<int> VectorInt ; typedef QVector<uint> VectorUInt ; +typedef QVector<qulonglong> VectorULongLong ; typedef QVector< QMap<QString, QString> > VectorMapStringString ; typedef QVector< QString > VectorString ; typedef QMap< QString, QMap< QString, QVector<QString> > > MapStringMapStringVectorString; diff --git a/test/mocks/configurationmanager_mock.h b/test/mocks/configurationmanager_mock.h index dfed8e01150ab7e99b514a63496ea6a280a008e6..e2c403cd003e73e0455521c4918bf80c26b18b29 100644 --- a/test/mocks/configurationmanager_mock.h +++ b/test/mocks/configurationmanager_mock.h @@ -648,6 +648,38 @@ public Q_SLOTS: // METHODS emit incomingTrustRequest(accountId, from, payload, timestamp); } + VectorULongLong dataTransferList() { + return {}; + } + + uint64_t sendFile(const QString& account_id, const QString& peer_uri, const QString& file_path, const QString& display_name) { + (void)account_id; + (void)peer_uri; + (void)file_path; + (void)display_name; + return 0; + } + + DataTransferInfo dataTransferInfo(uint64_t transfer_id) { + (void)transfer_id; + return {}; + } + + uint64_t dataTransferBytesSent(uint64_t transfer_id) { + (void)transfer_id; + return 0; + } + + void acceptFileTransfer(uint64_t transfer_id, const QString& file_path, uint64_t offset) { + (void)transfer_id; + (void)file_path; + (void)offset; + } + + void cancelDataTransfer(uint64_t transfer_id) { + (void)transfer_id; + } + Q_SIGNALS: // SIGNALS void volumeChanged(const QString& device, double value); void accountsChanged(); @@ -673,8 +705,7 @@ Q_SIGNALS: // SIGNALS void migrationEnded(const QString &accountId, const QString &result); void contactAdded(const QString &accountId, const QString &uri, bool banned); void contactRemoved(const QString &accountId, const QString &uri, bool banned); - - + void dataTransferEvent(uint64_t transfer_id, uint32_t code); }; namespace org {